/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** * A singleton instance of the box model controllers. * * @param {Inspector} inspector * An instance of the Inspector currently loaded in the toolbox. * @param {Window} window * The document window of the toolbox.
*/ function BoxModel(inspector, window) { this.document = window.document; this.inspector = inspector; this.store = inspector.store;
BoxModel.prototype = { /** * Destruction function called when the inspector is destroyed. Removes event listeners * and cleans up references.
*/
destroy() { this.inspector.selection.off("new-node-front", this.onNewSelection); this.inspector.sidebar.off("select", this.onSidebarSelect);
if (this._geometryEditorEventsAbortController) { this._geometryEditorEventsAbortController.abort(); this._geometryEditorEventsAbortController = null;
}
get highlighters() { if (!this._highlighters) { // highlighters is a lazy getter in the inspector. this._highlighters = this.inspector.highlighters;
}
returnthis._highlighters;
},
get rulePreviewTooltip() { if (!this._tooltip) { this._tooltip = new RulePreviewTooltip(this.inspector.toolbox.doc);
}
returnthis._tooltip;
},
/** * Returns an object containing the box model's handler functions used in the box * model's React component props.
*/
getComponentProps() { return {
onShowBoxModelEditor: this.onShowBoxModelEditor,
onShowRulePreviewTooltip: this.onShowRulePreviewTooltip,
onToggleGeometryEditor: this.onToggleGeometryEditor,
};
},
/** * Returns true if the layout panel is visible, and false otherwise.
*/
isPanelVisible() { return ( this.inspector.toolbox && this.inspector.sidebar && this.inspector.toolbox.currentToolId === "inspector" && this.inspector.sidebar.getCurrentTabID() === "layoutview"
);
},
/** * Returns true if the layout panel is visible and the current element is valid to * be displayed in the view.
*/
isPanelVisibleAndNodeValid() { return ( this.isPanelVisible() && this.inspector.selection.isConnected() && this.inspector.selection.isElementNode()
);
},
/** * Starts listening to reflows in the current tab.
*/
trackReflows() { this.inspector.on("reflow-in-selected-target", this.updateBoxModel);
},
/** * Stops listening to reflows in the current tab.
*/
untrackReflows() { this.inspector.off("reflow-in-selected-target", this.updateBoxModel);
},
/** * Updates the box model panel by dispatching the new layout data. * * @param {String} reason * Optional string describing the reason why the boxmodel is updated.
*/
updateBoxModel(reason) { this._updateReasons = this._updateReasons || []; if (reason) { this._updateReasons.push(reason);
}
const lastRequest = async function () { if (
!this.inspector ||
!this.isPanelVisible() ||
!this.inspector.selection.isConnected() ||
!this.inspector.selection.isElementNode()
) { returnnull;
}
let layout = await pageStyle.getLayout(nodeFront, {
autoMargins: true,
});
const styleEntries = await pageStyle.getApplied(nodeFront, { // We don't need styles applied to pseudo elements of the current node.
skipPseudo: true,
}); this.elementRules = styleEntries.map(e => e.rule);
// Update the layout properties with whether or not the element's position is // editable with the geometry editor. const isPositionEditable = await pageStyle.isPositionEditable(nodeFront);
// Update the redux store with the latest offset parent DOM node const offsetParent =
await inspectorFront.walker.getOffsetParent(nodeFront); this.store.dispatch(updateOffsetParent(offsetParent));
// Update the redux store with the latest layout properties and update the box // model view. this.store.dispatch(updateLayout(layout));
// If a subsequent request has been made, wait for that one instead. if (this._lastRequest != lastRequest) { returnthis._lastRequest;
}
returnnull;
}
.bind(this)()
.catch(error => { // If we failed because we were being destroyed while waiting for a request, ignore. if (this.document) {
console.error(error);
}
});
this._lastRequest = lastRequest;
},
/** * Hides the geometry editor and updates the box moodel store with the new * geometry editor enabled state.
*/
onHideGeometryEditor() { this.highlighters.hideGeometryEditor(); this.store.dispatch(updateGeometryEditorEnabled(false));
if (this._geometryEditorEventsAbortController) { this._geometryEditorEventsAbortController.abort(); this._geometryEditorEventsAbortController = null;
}
},
/** * Handler function that re-shows the geometry editor for an element that already * had the geometry editor enabled. This handler function is called on a "leave" event * on the markup view.
*/
onMarkupViewLeave() { const state = this.store.getState(); const enabled = state.boxModel.geometryEditorEnabled;
/** * Handler function that temporarily hides the geomery editor when the * markup view has a "node-hover" event.
*/
onMarkupViewNodeHover() { this.highlighters.hideGeometryEditor();
},
if ( this.inspector.selection.isConnected() && this.inspector.selection.isElementNode()
) { this.trackReflows();
}
this.updateBoxModel("new-selection");
},
/** * Shows the RulePreviewTooltip when a box model editable value is hovered on the * box model panel. * * @param {Element} target * The target element. * @param {String} property * The name of the property.
*/
onShowRulePreviewTooltip(target, property) { const { highlightProperty } = this.inspector.getPanel("ruleview").view; const isHighlighted = highlightProperty(property);
// Only show the tooltip if the property is not highlighted. // TODO: In the future, use an associated ruleId for toggling the tooltip instead of // the Boolean returned from highlightProperty. if (!isHighlighted) { this.rulePreviewTooltip.show(target);
}
},
/** * Shows the inplace editor when a box model editable value is clicked on the * box model panel. * * @param {DOMNode} element * The element that was clicked. * @param {Event} event * The event object. * @param {String} property * The name of the property.
*/
onShowBoxModelEditor(element, event, property) { const session = new EditingSession({
inspector: this.inspector,
doc: this.document,
elementRules: this.elementRules,
}); const initialValue = session.getProperty(property);
const editor = new InplaceEditor(
{
element,
initial: initialValue,
contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
property: {
name: property,
},
start: self => {
self.elt.parentNode.classList.add("boxmodel-editing");
},
change: value => { if (NUMERIC.test(value)) {
value += "px";
}
/** * Handler for the inspector sidebar select event. Starts tracking reflows if the * layout panel is visible. Otherwise, stop tracking reflows. Finally, refresh the box * model view if it is visible.
*/
onSidebarSelect() { if (!this.isPanelVisible()) { this.untrackReflows(); return;
}
if ( this.inspector.selection.isConnected() && this.inspector.selection.isElementNode()
) { this.trackReflows();
}
this.updateBoxModel();
},
/** * Toggles on/off the geometry editor for the current element when the geometry editor * toggle button is clicked.
*/
onToggleGeometryEditor() { const { markup, selection, toolbox } = this.inspector; const nodeFront = this.inspector.selection.nodeFront; const state = this.store.getState(); const enabled = !state.boxModel.geometryEditorEnabled;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.