--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1122,18 +1122,17 @@ Inspector.prototype = {
return;
}
let onExpand = this.markup.expandNode(this.selection.nodeFront);
// Restore the highlighter states prior to emitting "new-root".
await Promise.all([
this.highlighters.restoreFlexboxState(),
- this.highlighters.restoreGridState(),
- this.highlighters.restoreShapeState()
+ this.highlighters.restoreGridState()
]);
this.emit("new-root");
// Wait for full expand of the selected node in order to ensure
// the markup view is fully emitted before firing 'reloaded'.
// 'reloaded' is used to know when the panel is fully updated
// after a page reload.
@@ -1356,17 +1355,17 @@ Inspector.prototype = {
this.teardownToolbar();
this.breadcrumbs.destroy();
this.selection.off("new-node-front", this.onNewSelection);
this.selection.off("detached-front", this.onDetached);
let markupDestroyer = this._destroyMarkup();
- this.highlighters.destroy();
+ let highlighterDestroyer = this.highlighters.destroy();
this.prefsObserver.destroy();
this.reflowTracker.destroy();
this.styleChangeTracker.destroy();
this.search.destroy();
this._toolbox = null;
this.breadcrumbs = null;
this.highlighters = null;
@@ -1379,16 +1378,17 @@ Inspector.prototype = {
this.search = null;
this.searchBox = null;
this.show3PaneToggle = null;
this.sidebar = null;
this.store = null;
this.target = null;
this._panelDestroyer = promise.all([
+ highlighterDestroyer,
cssPropertiesDestroyer,
markupDestroyer,
sidebarDestroyer,
ruleViewSideBarDestroyer
]);
return this._panelDestroyer;
},
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -19,16 +19,17 @@ const ClassListPreviewer = require("devt
const {getCssProperties} = require("devtools/shared/fronts/css-properties");
const {
VIEW_NODE_SELECTOR_TYPE,
VIEW_NODE_PROPERTY_TYPE,
VIEW_NODE_VALUE_TYPE,
VIEW_NODE_IMAGE_URL_TYPE,
VIEW_NODE_LOCATION_TYPE,
VIEW_NODE_SHAPE_POINT_TYPE,
+ VIEW_NODE_SHAPE_SWATCH,
VIEW_NODE_VARIABLE_TYPE,
VIEW_NODE_FONT_TYPE,
} = require("devtools/client/inspector/shared/node-types");
const StyleInspectorMenu = require("devtools/client/inspector/shared/style-inspector-menu");
const TooltipsOverlay = require("devtools/client/inspector/shared/tooltips-overlay");
const {createChild, promiseWarn} = require("devtools/client/inspector/shared/utils");
const {debounce} = require("devtools/shared/debounce");
const EventEmitter = require("devtools/shared/event-emitter");
@@ -356,16 +357,23 @@ CssRuleView.prototype = {
enabled: prop.enabled,
overridden: prop.overridden,
pseudoElement: prop.rule.pseudoElement,
sheetHref: prop.rule.domRule.href,
textProperty: prop,
toggleActive: getShapeToggleActive(node),
point: getShapePoint(node)
};
+ } else if (classes.contains("ruleview-shapeswatch") && prop) {
+ type = VIEW_NODE_SHAPE_SWATCH;
+ value = {
+ enabled: prop.enabled,
+ overridden: prop.overridden,
+ textProperty: prop,
+ };
} else if ((classes.contains("ruleview-variable") ||
classes.contains("ruleview-unmatched-variable")) && prop) {
type = VIEW_NODE_VARIABLE_TYPE;
value = {
property: getPropertyNameAndValue(node).name,
value: node.textContent,
enabled: prop.enabled,
overridden: prop.overridden,
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -22,17 +22,16 @@ const Services = require("Services");
const HTML_NS = "http://www.w3.org/1999/xhtml";
const SHARED_SWATCH_CLASS = "ruleview-swatch";
const COLOR_SWATCH_CLASS = "ruleview-colorswatch";
const BEZIER_SWATCH_CLASS = "ruleview-bezierswatch";
const FILTER_SWATCH_CLASS = "ruleview-filterswatch";
const ANGLE_SWATCH_CLASS = "ruleview-angleswatch";
-const INSET_POINT_TYPES = ["top", "right", "bottom", "left"];
const FONT_FAMILY_CLASS = "ruleview-font-family";
const SHAPE_SWATCH_CLASS = "ruleview-shapeswatch";
/*
* An actionable element is an element which on click triggers a specific action
* (e.g. shows a color tooltip, opens a link, …).
*/
const ACTIONABLE_ELEMENTS_SELECTORS = [
@@ -91,17 +90,16 @@ function TextPropertyEditor(ruleEditor,
this._onNameDone = this._onNameDone.bind(this);
this._onValueDone = this._onValueDone.bind(this);
this._onSwatchCommit = this._onSwatchCommit.bind(this);
this._onSwatchPreview = this._onSwatchPreview.bind(this);
this._onSwatchRevert = this._onSwatchRevert.bind(this);
this._onValidate = this.ruleView.debounce(this._previewValue, 10, this);
this.update = this.update.bind(this);
this.updatePropertyState = this.updatePropertyState.bind(this);
- this._onHoverShapePoint = this._onHoverShapePoint.bind(this);
this._create();
this.update();
}
TextPropertyEditor.prototype = {
/**
* Boolean indicating if the name or value is being currently edited.
@@ -314,18 +312,16 @@ TextPropertyEditor.prototype = {
property: this.prop,
defaultIncrement: this.prop.name === "opacity" ? 0.1 : 1,
popup: this.popup,
multiline: true,
maxWidth: () => this.container.getBoundingClientRect().width,
cssProperties: this.cssProperties,
cssVariables: this.rule.elementStyle.variables,
});
-
- this.ruleView.highlighters.on("hover-shape-point", this._onHoverShapePoint);
}
},
/**
* Get the path from which to resolve requests for this
* rule's stylesheet.
*
* @return {String} the stylesheet's href.
@@ -510,23 +506,16 @@ TextPropertyEditor.prototype = {
}
let shapeToggle = this.valueSpan.querySelector(".ruleview-shapeswatch");
if (shapeToggle) {
let mode = "css" + name.split("-").map(s => {
return s[0].toUpperCase() + s.slice(1);
}).join("");
shapeToggle.setAttribute("data-mode", mode);
-
- let { highlighters, inspector } = this.ruleView;
- if (highlighters.shapesHighlighterShown === inspector.selection.nodeFront &&
- highlighters.state.shapes.options.mode === mode) {
- shapeToggle.classList.add("active");
- highlighters.highlightRuleViewShapePoint(highlighters.state.shapes.hoverPoint);
- }
}
// Now that we have updated the property's value, we might have a pending
// click on the value container. If we do, we have to trigger a click event
// on the right element.
if (this._hasPendingClick) {
this._hasPendingClick = false;
let elToClick;
@@ -1040,72 +1029,11 @@ TextPropertyEditor.prototype = {
* Returns true if the property is a `display: [inline-]grid` declaration.
*
* @return {Boolean} true if the property is a `display: [inline-]grid` declaration.
*/
isDisplayGrid: function() {
return this.prop.name === "display" &&
(this.prop.value === "grid" || this.prop.value === "inline-grid");
},
-
- /**
- * Highlight the given shape point in the rule view. Called when "hover-shape-point"
- * event is emitted.
- *
- * @param {Event} event
- * The "hover-shape-point" event.
- * @param {String} point
- * The point to highlight.
- */
- _onHoverShapePoint: function(point) {
- // If there is no shape toggle, or it is not active, return.
- let shapeToggle = this.valueSpan.querySelector(".ruleview-shapeswatch.active");
- if (!shapeToggle) {
- return;
- }
-
- let view = this.ruleView;
- let { highlighters } = view;
- let ruleViewEl = view.element;
- let selector = `.ruleview-shape-point.active`;
- for (let pointNode of ruleViewEl.querySelectorAll(selector)) {
- this._toggleShapePointActive(pointNode, false);
- }
-
- if (typeof point === "string") {
- if (point.includes(",")) {
- point = point.split(",")[0];
- }
- // Because one inset value can represent multiple points, inset points use classes
- // instead of data.
- selector = (INSET_POINT_TYPES.includes(point)) ?
- `.ruleview-shape-point.${point}` :
- `.ruleview-shape-point[data-point='${point}']`;
- for (let pointNode of this.valueSpan.querySelectorAll(selector)) {
- let nodeInfo = view.getNodeInfo(pointNode);
- if (highlighters.isRuleViewShapePoint(nodeInfo)) {
- this._toggleShapePointActive(pointNode, true);
- }
- }
- }
- },
-
- /**
- * Toggle the class "active" on the given shape point in the rule view if the current
- * inspector selection is highlighted by the shapes highlighter.
- *
- * @param {NodeFront} node
- * The NodeFront of the shape point to toggle
- * @param {Boolean} active
- * Whether the shape point should be active
- */
- _toggleShapePointActive: function(node, active) {
- let { highlighters } = this.ruleView;
- if (highlighters.inspector.selection.nodeFront !=
- highlighters.shapesHighlighterShown) {
- return;
- }
-
- node.classList.toggle("active", active);
- },
};
module.exports = TextPropertyEditor;
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -7,31 +7,39 @@
"use strict";
const Services = require("Services");
const EventEmitter = require("devtools/shared/event-emitter");
const {
VIEW_NODE_VALUE_TYPE,
VIEW_NODE_SHAPE_POINT_TYPE
} = require("devtools/client/inspector/shared/node-types");
-
const DEFAULT_GRID_COLOR = "#4B0082";
-const INSET_POINT_TYPES = ["top", "right", "bottom", "left"];
/**
* Highlighters overlay is a singleton managing all highlighters in the Inspector.
*/
class HighlightersOverlay {
/**
* @param {Inspector} inspector
* Inspector toolbox panel.
*/
constructor(inspector) {
+ /*
+ * Collection of instantiated highlighter actors like FlexboxHighlighter,
+ * CssGridHighlighter, ShapesHighlighter and GeometryEditorHighlighter.
+ */
+ this.highlighters = {};
+ /*
+ * Collection of instantiated in-context editors, like ShapesInContextEditor, which
+ * behave like highlighters but with added editing capabilities that need to map value
+ * changes to properties in the Rule view.
+ */
+ this.editors = {};
this.inspector = inspector;
- this.highlighters = {};
this.highlighterUtils = this.inspector.toolbox.highlighterUtils;
// Only initialize the overlay if at least one of the highlighter types is supported.
this.supportsHighlighters = this.highlighterUtils.supportsCustomHighlighters();
// NodeFront of the flexbox container that is highlighted.
this.flexboxHighlighterShown = null;
// NodeFront of element that is highlighted by the geometry editor.
@@ -58,17 +66,18 @@ class HighlightersOverlay {
this.onWillNavigate = this.onWillNavigate.bind(this);
this.hideFlexboxHighlighter = this.hideFlexboxHighlighter.bind(this);
this.hideGridHighlighter = this.hideGridHighlighter.bind(this);
this.hideShapesHighlighter = this.hideShapesHighlighter.bind(this);
this.showFlexboxHighlighter = this.showFlexboxHighlighter.bind(this);
this.showGridHighlighter = this.showGridHighlighter.bind(this);
this.showShapesHighlighter = this.showShapesHighlighter.bind(this);
this._handleRejection = this._handleRejection.bind(this);
- this._onHighlighterEvent = this._onHighlighterEvent.bind(this);
+ this.onShapesHighlighterShown = this.onShapesHighlighterShown.bind(this);
+ this.onShapesHighlighterHidden = this.onShapesHighlighterHidden.bind(this);
// Add inspector events, not specific to a given view.
this.inspector.on("markupmutation", this.onMarkupMutation);
this.inspector.target.on("will-navigate", this.onWillNavigate);
EventEmitter.decorate(this);
}
@@ -116,91 +125,88 @@ class HighlightersOverlay {
let el = view.element;
el.removeEventListener("click", this.onClick, true);
el.removeEventListener("mousemove", this.onMouseMove);
el.removeEventListener("mouseout", this.onMouseOut);
}
/**
- * Toggle the shapes highlighter for the given element with a shape.
- *
+ * Toggle the shapes highlighter for the given node.
+
* @param {NodeFront} node
* The NodeFront of the element with a shape to highlight.
* @param {Object} options
* Object used for passing options to the shapes highlighter.
+ * @param {TextProperty} textProperty
+ * TextProperty where to write changes.
*/
- async toggleShapesHighlighter(node, options = {}) {
- options.transformMode = options.ctrlOrMetaPressed;
-
- if (node == this.shapesHighlighterShown &&
- options.mode === this.state.shapes.options.mode) {
- // If meta/ctrl is not pressed, hide the highlighter.
- if (!options.ctrlOrMetaPressed) {
- await this.hideShapesHighlighter(node);
- return;
- }
-
- // If meta/ctrl is pressed, toggle transform mode on the highlighter.
- options.transformMode = !this.state.shapes.options.transformMode;
+ async toggleShapesHighlighter(node, options, textProperty) {
+ let shapesEditor = await this.getInContextEditor("shapesEditor");
+ if (!shapesEditor) {
+ return;
}
-
- await this.showShapesHighlighter(node, options);
+ shapesEditor.toggle(node, options, textProperty);
}
/**
- * Show the shapes highlighter for the given element with a shape.
+ * Show the shapes highlighter for the given node.
+ * This method delegates to the in-context shapes editor.
*
* @param {NodeFront} node
* The NodeFront of the element with a shape to highlight.
* @param {Object} options
* Object used for passing options to the shapes highlighter.
*/
async showShapesHighlighter(node, options) {
- let highlighter = await this._getHighlighter("ShapesHighlighter");
- if (!highlighter) {
- return;
- }
-
- let isShown = await highlighter.show(node, options);
- if (!isShown) {
+ let shapesEditor = await this.getInContextEditor("shapesEditor");
+ if (!shapesEditor) {
return;
}
-
- this.shapesHighlighterShown = node;
- let { mode } = options;
- this._toggleRuleViewIcon(node, false, ".ruleview-shapeswatch");
- this._toggleRuleViewIcon(node, true, `.ruleview-shapeswatch[data-mode='${mode}']`);
-
- try {
- // Save shapes highlighter state.
- let { url } = this.inspector.target;
- let selector = await node.getUniqueSelector();
- this.state.shapes = { selector, options, url };
- this.shapesHighlighterShown = node;
- this.emit("shapes-highlighter-shown", node, options);
- } catch (e) {
- this._handleRejection(e);
- }
+ shapesEditor.show(node, options);
}
/**
- * Hide the shapes highlighter for the given element with a shape.
+ * Called after the shape highlighter was shown.
*
- * @param {NodeFront} node
- * The NodeFront of the element with a shape to unhighlight.
+ * @param {Object} data
+ * Data associated with the event.
+ * Contains:
+ * - {NodeFront} node: The NodeFront of the element that is highlighted.
+ * - {Object} options: Options that were passed to ShapesHighlighter.show()
*/
- async hideShapesHighlighter(node) {
- if (!this.shapesHighlighterShown || !this.highlighters.ShapesHighlighter) {
+ onShapesHighlighterShown(data) {
+ let { node, options } = data;
+ this.shapesHighlighterShown = node;
+ this.state.shapes.options = options;
+ this.emit("shapes-highlighter-shown", node, options);
+ }
+
+ /**
+ * Hide the shapes highlighter if visible.
+ * This method delegates the to the in-context shapes editor which wraps
+ * the shapes highlighter with additional functionality.
+ */
+ async hideShapesHighlighter() {
+ let shapesEditor = await this.getInContextEditor("shapesEditor");
+ if (!shapesEditor) {
return;
}
+ shapesEditor.hide();
+ }
- this._toggleRuleViewIcon(node, false, ".ruleview-shapeswatch");
-
- await this.highlighters.ShapesHighlighter.hide();
+ /**
+ * Called after the shapes highlighter was hidden.
+ *
+ * @param {Object} data
+ * Data associated with the event.
+ * Contains:
+ * - {NodeFront} node: The NodeFront of the element that was highlighted.
+ */
+ onShapesHighlighterHidden(data) {
this.emit("shapes-highlighter-hidden", this.shapesHighlighterShown,
this.state.shapes.options);
this.shapesHighlighterShown = null;
this.state.shapes = {};
}
/**
* Show the shapes highlighter for the given element, with the given point highlighted.
@@ -214,47 +220,16 @@ class HighlightersOverlay {
if (node == this.shapesHighlighterShown) {
let options = Object.assign({}, this.state.shapes.options);
options.hoverPoint = point;
await this.showShapesHighlighter(node, options);
}
}
/**
- * Highlight the given shape point in the rule view.
- *
- * @param {String} point
- * The point to highlight.
- */
- highlightRuleViewShapePoint(point) {
- let view = this.inspector.getPanel("ruleview").view;
- let ruleViewEl = view.element;
- let selector = `.ruleview-shape-point.active`;
-
- for (let pointNode of ruleViewEl.querySelectorAll(selector)) {
- this._toggleShapePointActive(pointNode, false);
- }
-
- if (point !== null && point !== undefined) {
- // Because one inset value can represent multiple points, inset points use classes
- // instead of data.
- selector = (INSET_POINT_TYPES.includes(point)) ?
- `.ruleview-shape-point.${point}` :
- `.ruleview-shape-point[data-point='${point}']`;
-
- for (let pointNode of ruleViewEl.querySelectorAll(selector)) {
- let nodeInfo = view.getNodeInfo(pointNode);
- if (this.isRuleViewShapePoint(nodeInfo)) {
- this._toggleShapePointActive(pointNode, true);
- }
- }
- }
- }
-
- /**
* Toggle the flexbox highlighter for the given flexbox container element.
*
* @param {NodeFront} node
* The NodeFront of the flexbox container element to highlight.
* @param {Object} options
* Object used for passing options to the flexbox highlighter.
*/
async toggleFlexboxHighlighter(node, options = {}) {
@@ -460,34 +435,16 @@ class HighlightersOverlay {
await this.highlighters.GeometryEditorHighlighter.hide();
this.emit("geometry-editor-highlighter-hidden");
this.geometryEditorHighlighterShown = null;
}
/**
- * Handle events emitted by the highlighter.
- *
- * @param {Object} data
- * The data object sent in the event.
- */
- _onHighlighterEvent(data) {
- if (data.type === "shape-hover-on") {
- this.state.shapes.hoverPoint = data.point;
- this.emit("hover-shape-point", data.point);
- } else if (data.type === "shape-hover-off") {
- this.state.shapes.hoverPoint = null;
- this.emit("hover-shape-point", null);
- }
-
- this.emit("highlighter-event-handled");
- }
-
- /**
* Restores the saved flexbox highlighter state.
*/
async restoreFlexboxState() {
try {
await this.restoreState("flexbox", this.state.flexbox, this.showFlexboxHighlighter);
} catch (e) {
this._handleRejection(e);
}
@@ -500,29 +457,18 @@ class HighlightersOverlay {
try {
await this.restoreState("grid", this.state.grid, this.showGridHighlighter);
} catch (e) {
this._handleRejection(e);
}
}
/**
- * Restores the saved shape highlighter state.
- */
- async restoreShapeState() {
- try {
- await this.restoreState("shapes", this.state.shapes, this.showShapesHighlighter);
- } catch (e) {
- this._handleRejection(e);
- }
- }
-
- /**
- * Helper function called by restoreFlexboxState, restoreGridState and
- * restoreShapeState. Restores the saved highlighter state for the given highlighter
+ * Helper function called by restoreFlexboxState, restoreGridState.
+ * Restores the saved highlighter state for the given highlighter
* and their state.
*
* @param {String} name
* The name of the highlighter to be restored
* @param {Object} state
* The state of the highlighter to be restored
* @param {Function} showFunction
* The function that shows the highlighter
@@ -550,16 +496,57 @@ class HighlightersOverlay {
await showFunction(nodeFront, options);
this.emit(`${name}-state-restored`, { restored: true });
}
this.emit(`${name}-state-restored`, { restored: false });
}
/**
+ * Get an instance of an in-context editor for the given type.
+ *
+ * In-context editors behave like highlighters but with added editing capabilities which
+ * need to write value changes back to something, like to properties in the Rule view.
+ * They typically exist in the context of the page, like the ShapesInContextEditor.
+ *
+ * @param {String} type
+ * Type of in-context editor. Currently supported: "shapesEditor"
+ *
+ * @return {Object|null}
+ * Reference to instance for given type of in-context editor or null.
+ */
+ async getInContextEditor(type) {
+ if (this.editors[type]) {
+ return this.editors[type];
+ }
+
+ let editor;
+
+ switch (type) {
+ case "shapesEditor":
+ let highlighter = await this._getHighlighter("ShapesHighlighter");
+ if (!highlighter) {
+ return null;
+ }
+ const ShapesInContextEditor = require("devtools/client/shared/widgets/ShapesInContextEditor");
+
+ editor = new ShapesInContextEditor(highlighter, this.inspector, this.state);
+ editor.on("show", this.onShapesHighlighterShown);
+ editor.on("hide", this.onShapesHighlighterHidden);
+ break;
+ default:
+ throw new Error(`Unsupported in-context editor '${name}'`);
+ }
+
+ this.editors[type] = editor;
+
+ return editor;
+ }
+
+ /**
* Get a highlighter front given a type. It will only be initialized once.
*
* @param {String} type
* The highlighter type. One of this.highlighters.
* @return {Promise} that resolves to the highlighter
*/
async _getHighlighter(type) {
let utils = this.highlighterUtils;
@@ -575,17 +562,16 @@ class HighlightersOverlay {
} catch (e) {
// Ignore any error
}
if (!highlighter) {
return null;
}
- highlighter.on("highlighter-event", this._onHighlighterEvent);
this.highlighters[type] = highlighter;
return highlighter;
}
_handleRejection(error) {
if (!this.destroyed) {
console.error(error);
}
@@ -717,17 +703,17 @@ class HighlightersOverlay {
/**
* Does the current clicked node have the shapes highlighter toggle in the
* rule-view.
*
* @param {DOMNode} node
* @return {Boolean}
*/
- _isRuleViewShape(node) {
+ _isRuleViewShapeSwatch(node) {
return this.isRuleView(node) && node.classList.contains("ruleview-shapeswatch");
}
/**
* Is the current hovered node a css transform property value in the rule-view.
*
* @param {Object} nodeInfo
* @return {Boolean}
@@ -771,28 +757,34 @@ class HighlightersOverlay {
let { store } = this.inspector;
let { grids, highlighterSettings } = store.getState();
let grid = grids.find(g => g.nodeFront == this.inspector.selection.nodeFront);
highlighterSettings.color = grid ? grid.color : DEFAULT_GRID_COLOR;
this.toggleGridHighlighter(this.inspector.selection.nodeFront, highlighterSettings,
"rule");
- } else if (this._isRuleViewDisplayFlex(event.target)) {
+ }
+
+ if (this._isRuleViewDisplayFlex(event.target)) {
event.stopPropagation();
this.toggleFlexboxHighlighter(this.inspector.selection.nodeFront);
- } else if (this._isRuleViewShape(event.target)) {
+ }
+
+ if (this._isRuleViewShapeSwatch(event.target)) {
event.stopPropagation();
- let settings = {
+ const view = this.inspector.getPanel("ruleview").view;
+ const nodeInfo = view.getNodeInfo(event.target);
+
+ this.toggleShapesHighlighter(this.inspector.selection.nodeFront, {
mode: event.target.dataset.mode,
- ctrlOrMetaPressed: event.metaKey || event.ctrlKey
- };
- this.toggleShapesHighlighter(this.inspector.selection.nodeFront, settings);
+ transformMode: event.metaKey || event.ctrlKey
+ }, nodeInfo.value.textProperty);
}
}
onMouseMove(event) {
// Bail out if the target is the same as for the last mousemove.
if (event.target === this._lastHovered) {
return;
}
@@ -808,17 +800,16 @@ class HighlightersOverlay {
let nodeInfo = view.getNodeInfo(event.target);
if (!nodeInfo) {
return;
}
if (this.isRuleViewShapePoint(nodeInfo)) {
let { point } = nodeInfo.value;
this.hoverPointShapesHighlighter(this.inspector.selection.nodeFront, point);
- this.emit("hover-shape-point", point);
return;
}
// Choose the type of highlighter required for the hovered node.
let type;
if (this._isRuleViewTransform(nodeInfo) ||
this._isComputedViewTransform(nodeInfo)) {
type = "CssTransformHighlighter";
@@ -846,17 +837,16 @@ class HighlightersOverlay {
// Otherwise, hide the highlighter.
let view = this.isRuleView(this._lastHovered) ?
this.inspector.getPanel("ruleview").view :
this.inspector.getPanel("computedview").computedView;
let nodeInfo = view.getNodeInfo(this._lastHovered);
if (nodeInfo && this.isRuleViewShapePoint(nodeInfo)) {
this.hoverPointShapesHighlighter(this.inspector.selection.nodeFront, null);
- this.emit("hover-shape-point", null);
}
this._lastHovered = null;
this._hideHoveredHighlighter();
}
/**
* Handler function for "markupmutation" events. Hides the flexbox/grid/shapes
* highlighter if the flexbox/grid/shapes container is no longer in the DOM tree.
@@ -882,41 +872,61 @@ class HighlightersOverlay {
*/
onWillNavigate() {
this.flexboxHighlighterShown = null;
this.geometryEditorHighlighterShown = null;
this.gridHighlighterShown = null;
this.hoveredHighlighterShown = null;
this.selectorHighlighterShown = null;
this.shapesHighlighterShown = null;
+ this.destroyEditors();
+ }
+
+ /**
+ * Destroy and clean-up all instances of in-context editors.
+ */
+ destroyEditors() {
+ for (let type in this.editors) {
+ this.editors[type].off("show");
+ this.editors[type].off("hide");
+ this.editors[type].destroy();
+ }
+
+ this.editors = {};
+ }
+
+ /**
+ * Destroy and clean-up all instances of highlighters.
+ */
+ destroyHighlighters() {
+ for (let type in this.highlighters) {
+ if (this.highlighters[type]) {
+ this.highlighters[type].finalize();
+ this.highlighters[type] = null;
+ }
+ }
+
+ this.highlighters = null;
}
/**
* Destroy this overlay instance, removing it from the view and destroying
* all initialized highlighters.
*/
- destroy() {
- for (let type in this.highlighters) {
- if (this.highlighters[type]) {
- if (this.highlighters[type].off) {
- this.highlighters[type].off("highlighter-event", this._onHighlighterEvent);
- }
- this.highlighters[type].finalize();
- this.highlighters[type] = null;
- }
- }
+ async destroy() {
+ this.destroyHighlighters();
+ this.destroyEditors();
// Remove inspector events.
this.inspector.off("markupmutation", this.onMarkupMutation);
this.inspector.target.off("will-navigate", this.onWillNavigate);
this._lastHovered = null;
this.inspector = null;
- this.highlighters = null;
this.highlighterUtils = null;
this.supportsHighlighters = null;
this.state = null;
this.flexboxHighlighterShown = null;
this.geometryEditorHighlighterShown = null;
this.gridHighlighterShown = null;
this.hoveredHighlighterShown = null;
--- a/devtools/client/inspector/shared/node-types.js
+++ b/devtools/client/inspector/shared/node-types.js
@@ -13,8 +13,9 @@
exports.VIEW_NODE_SELECTOR_TYPE = 1;
exports.VIEW_NODE_PROPERTY_TYPE = 2;
exports.VIEW_NODE_VALUE_TYPE = 3;
exports.VIEW_NODE_IMAGE_URL_TYPE = 4;
exports.VIEW_NODE_LOCATION_TYPE = 5;
exports.VIEW_NODE_SHAPE_POINT_TYPE = 6;
exports.VIEW_NODE_VARIABLE_TYPE = 7;
exports.VIEW_NODE_FONT_TYPE = 8;
+exports.VIEW_NODE_SHAPE_SWATCH = 9;
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/widgets/ShapesInContextEditor.js
@@ -0,0 +1,298 @@
+/* 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/. */
+
+"use strict";
+
+const EventEmitter = require("devtools/shared/event-emitter");
+const { debounce } = require("devtools/shared/debounce");
+
+/**
+ * The ShapesInContextEditor:
+ * - communicates with the ShapesHighlighter actor from the server;
+ * - listens to events for shape change and hover point coming from the shape-highlighter;
+ * - writes shape value changes to the CSS declaration it was triggered from;
+ * - synchronises highlighting coordinate points on mouse over between the shapes
+ * highlighter and the shape value shown in the Rule view.
+ *
+ * It is instantiated once in HighlightersOverlay by calls to .getInContextEditor().
+ */
+class ShapesInContextEditor {
+ constructor(highlighter, inspector, state) {
+ EventEmitter.decorate(this);
+
+ this.inspector = inspector;
+ this.highlighter = highlighter;
+ // Refence to the NodeFront currently being highlighted.
+ this.highlighterTargetNode = null;
+ this.highligherEventHandlers = {};
+ this.highligherEventHandlers["shape-change"] = this.onShapeChange;
+ this.highligherEventHandlers["shape-hover-on"] = this.onShapeHover;
+ this.highligherEventHandlers["shape-hover-off"] = this.onShapeHover;
+ // Mode for shapes highlighter: shape-outside or clip-path. Used to discern
+ // when toggling the highlighter on the same node for different CSS properties.
+ this.mode = null;
+ // Reference to Rule view used to listen for changes
+ this.ruleView = this.inspector.getPanel("ruleview").view;
+ // Reference of |state| from HighlightersOverlay.
+ this.state = state;
+ // Reference to DOM node of the toggle icon for shapes highlighter.
+ this.swatch = null;
+ // Reference to TextProperty where shape changes will be written.
+ this.textProperty = null;
+
+ // Commit triggers expensive DOM changes in TextPropertyEditor.update()
+ // so we debounce it.
+ this.commit = debounce(this.commit, 200, this);
+ this.onChangesApplied = this.onChangesApplied.bind(this);
+ this.onHighlighterEvent = this.onHighlighterEvent.bind(this);
+ this.onNodeFrontChanged = this.onNodeFrontChanged.bind(this);
+ this.onRuleViewChanged = this.onRuleViewChanged.bind(this);
+
+ this.highlighter.on("highlighter-event", this.onHighlighterEvent);
+ this.ruleView.on("ruleview-changed", this.onRuleViewChanged);
+ }
+
+ /**
+ * Called when the element style changes from the Rule view.
+ * If the TextProperty we're acting on isn't enabled anymore or overridden,
+ * turn off the shapes highlighter.
+ */
+ async onRuleViewChanged() {
+ if (this.textProperty &&
+ (!this.textProperty.enabled || this.textProperty.overridden)) {
+ await this.hide();
+ }
+ }
+
+ /**
+ * Toggle the shapes highlighter for the given element.
+ *
+ * @param {NodeFront} node
+ * The NodeFront of the element with a shape to highlight.
+ * @param {Object} options
+ * Object used for passing options to the shapes highlighter.
+ */
+ async toggle(node, options, prop) {
+ // Same target node, same mode -> hide and exit OR switch to toggle transform mode.
+ if ((node == this.highlighterTargetNode) && (this.mode === options.mode)) {
+ if (!options.transformMode) {
+ await this.hide();
+ return;
+ }
+
+ options.transformMode = !this.state.shapes.options.transformMode;
+ }
+
+ // Same target node, dfferent modes -> toggle between shape-outside and clip-path.
+ // Hide highlighter for previous property, but continue and show for other property.
+ if ((node == this.highlighterTargetNode) && (this.mode !== options.mode)) {
+ await this.hide();
+ }
+
+ this.textProperty = prop;
+ this.findSwatch();
+ await this.show(node, options);
+ }
+
+ /**
+ * Show the shapes highlighter for the given element.
+ *
+ * @param {NodeFront} node
+ * The NodeFront of the element with a shape to highlight.
+ * @param {Object} options
+ * Object used for passing options to the shapes highlighter.
+ */
+ async show(node, options) {
+ let isShown = await this.highlighter.show(node, options);
+ if (!isShown) {
+ return;
+ }
+
+ this.inspector.selection.on("detached-front", this.onNodeFrontChanged);
+ this.inspector.selection.on("new-node-front", this.onNodeFrontChanged);
+ this.highlighterTargetNode = node;
+ this.mode = options.mode;
+ this.emit("show", { node, options });
+ }
+
+ /**
+ * Hide the shapes highlighter.
+ */
+ async hide() {
+ try {
+ await this.highlighter.hide();
+ } catch (err) {
+ // silent error
+ }
+
+ if (this.swatch) {
+ this.swatch.classList.remove("active");
+ }
+ this.swatch = null;
+ this.textProperty = null;
+
+ this.emit("hide", { node: this.highlighterTargetNode });
+ this.inspector.selection.off("detached-front", this.onNodeFrontChanged);
+ this.inspector.selection.off("new-node-front", this.onNodeFrontChanged);
+ this.highlighterTargetNode = null;
+ }
+
+ /**
+ * Identify the swatch (aka toggle icon) DOM node from the TextPropertyEditor of the
+ * TextProperty we're working with. Whenever the TextPropertyEditor is updated (i.e.
+ * when committing the shape value to the Rule view), it rebuilds its DOM and the old
+ * swatch reference becomes invalid. Call this method to identify the current swatch.
+ */
+ findSwatch() {
+ const valueSpan = this.textProperty.editor.valueSpan;
+ this.swatch = valueSpan.querySelector(".ruleview-shapeswatch");
+ this.swatch.classList.add("active");
+ }
+
+ /**
+ * Handle events emitted by the highlighter.
+ * Find any callback assigned to the event type and call it with the given data object.
+ *
+ * @param {Object} data
+ * The data object sent in the event.
+ */
+ onHighlighterEvent(data) {
+ const handler = this.highligherEventHandlers[data.type];
+ if (!handler || typeof handler !== "function") {
+ return;
+ }
+ handler.call(this, data);
+ this.inspector.highlighters.emit("highlighter-event-handled");
+ }
+
+ /**
+ * Clean up when node selection changes because Rule view and TextPropertyEditor
+ * instances are not automatically destroyed when selection changes.
+ */
+ async onNodeFrontChanged() {
+ try {
+ await this.hide();
+ } catch (err) {
+ // Silent error.
+ }
+ }
+
+ /**
+ * Handler for "shape-change" event from the shapes highlighter.
+ *
+ * @param {Object} data
+ * Data associated with the "shape-change" event.
+ * Contains:
+ * - {String} value: the new shape value.
+ * - {String} type: the event type ("shape-change").
+ */
+ onShapeChange(data) {
+ this.preview(data.value);
+ this.commit(data.value);
+ }
+
+ /**
+ * Handler for "shape-hover-on" and "shape-hover-off" events from the shapes highlighter.
+ * Called when the mouse moves over or off of a coordinate point inside the shapes
+ * highlighter. Marks/unmarks the corresponding coordinate node in the shape value
+ * from the Rule view.
+ *
+ * @param {Object} data
+ * Data associated with the "shape-hover" event.
+ * Contains:
+ * - {String|null} point: coordinate to highlight or null if nothing to highlight
+ * - {String} type: the event type ("shape-hover-on" or "shape-hover-on").
+ */
+ onShapeHover(data) {
+ if (!this.textProperty) {
+ return;
+ }
+
+ let shapeValueEl = this.swatch.nextSibling;
+ if (!shapeValueEl) {
+ return;
+ }
+ let pointSelector = ".ruleview-shape-point";
+ // First, unmark all highlighted coordinate nodes from Rule view
+ for (let node of shapeValueEl.querySelectorAll(`${pointSelector}.active`)) {
+ node.classList.remove("active");
+ }
+
+ // Exit if there's no coordinate to highlight.
+ if (typeof data.point !== "string") {
+ return;
+ }
+
+ let point = (data.point.includes(",")) ? data.point.split(",")[0] : data.point;
+
+ /**
+ * Build selector for coordinate nodes in shape value that must be highlighted.
+ * Coordinate values for inset() use class names instead of data attributes because
+ * a single node may represent multiple coordinates in shorthand notation.
+ * Example: inset(50px); The node wrapping 50px represents all four inset coordinates.
+ */
+ const INSET_POINT_TYPES = ["top", "right", "bottom", "left"];
+ let selector = INSET_POINT_TYPES.includes(point) ?
+ `${pointSelector}.${point}` :
+ `${pointSelector}[data-point='${point}']`;
+
+ for (let node of shapeValueEl.querySelectorAll(selector)) {
+ node.classList.add("active");
+ }
+ }
+
+ /**
+ * Preview a shape value on the element without committing the changes to the Rule view.
+ *
+ * @param {String} value
+ * The shape value to set the current property to
+ */
+ preview(value) {
+ if (!this.textProperty) {
+ return;
+ }
+ // Update the element's style to see live results.
+ this.textProperty.rule.previewPropertyValue(this.textProperty, value);
+ // Update the text of CSS value in the Rule view. This makes it inert.
+ // When commit() is called, the value is reparsed and its DOM structure rebuilt.
+ this.swatch.nextSibling.textContent = value;
+ }
+
+ /**
+ * Commit a shape value change which triggers an expensive operation that rebuilds
+ * part of the DOM of the TextPropertyEditor. Called in a debounced manner; see
+ * constructor.
+ *
+ * @param {String} value
+ * The shape value for the current property
+ */
+ commit(value) {
+ if (!this.textProperty) {
+ return;
+ }
+ this.ruleView.once("ruleview-changed", this.onChangesApplied);
+ this.textProperty.setValue(value);
+ }
+
+ /**
+ * Handler for "ruleview-changed" event triggered by the Rule view.
+ * Called once after the shape value has been written to the element's style and Rule
+ * view updated. Triggers an event on the HighlightersOverlay that is expected by
+ * tests in order to check if the shape value has been correctly applied.
+ */
+ onChangesApplied() {
+ // When TextPropertyEditor updates it thrashes the previous swatch DOM node. Find and
+ // store the new swatch node.
+ this.findSwatch();
+ this.inspector.highlighters.emit("shapes-highlighter-changes-applied");
+ }
+
+ destroy() {
+ this.highlighter.off("highlighter-event", this.onHighlighterEvent);
+ this.ruleView.off("ruleview-changed", this.onRuleViewChanged);
+ this.highligherEventHandlers = {};
+ }
+}
+
+module.exports = ShapesInContextEditor;
--- a/devtools/client/shared/widgets/moz.build
+++ b/devtools/client/shared/widgets/moz.build
@@ -17,16 +17,17 @@ DevToolsModules(
'CubicBezierWidget.js',
'FastListWidget.js',
'FilterWidget.js',
'FlameGraph.js',
'Graphs.js',
'GraphsWorker.js',
'LineGraphWidget.js',
'MountainGraphWidget.js',
+ 'ShapesInContextEditor.js',
'SideMenuWidget.jsm',
'SimpleListWidget.jsm',
'Spectrum.js',
'TableWidget.js',
'TreeWidget.js',
'VariablesView.jsm',
'VariablesViewController.jsm',
'view-helpers.js',
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -412,17 +412,18 @@
background-size: 1em;
}
.ruleview-grid {
background: url("chrome://devtools/skin/images/grid.svg");
border-radius: 0;
}
-.ruleview-shape-point.active {
+.ruleview-shape-point.active,
+.ruleview-shapeswatch.active + .ruleview-shape > .ruleview-shape-point:hover {
background-color: var(--rule-highlight-background-color);
}
.ruleview-colorswatch::before {
content: '';
background-color: #eee;
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
--- a/devtools/server/actors/highlighters/shapes.js
+++ b/devtools/server/actors/highlighters/shapes.js
@@ -838,17 +838,17 @@ class ShapesHighlighter extends AutoRefr
let precisionY = getDecimalPrecision(unitY);
newX = (newX * ratioX).toFixed(precisionX);
newY = (newY * ratioY).toFixed(precisionY);
return `${newX}${unitX} ${newY}${unitY}`;
}).join(", ");
polygonDef = `polygon(${polygonDef}) ${this.geometryBox}`.trim();
- this.currentNode.style.setProperty(this.property, polygonDef, "important");
+ this.emit("highlighter-event", { type: "shape-change", value: polygonDef });
}
/**
* Transform a circle depending on the current transformation matrix.
* @param {Number} transX the number of pixels the shape is translated on the x axis
* before scaling
*/
_transformCircle(transX = null) {
@@ -917,17 +917,17 @@ class ShapesHighlighter extends AutoRefr
newBottom = `${(height - newBottom) * bottom.ratio}${bottom.unit}`;
let round = this.insetRound;
let insetDef = (round) ?
`inset(${newTop} ${newRight} ${newBottom} ${newLeft} round ${round})` :
`inset(${newTop} ${newRight} ${newBottom} ${newLeft})`;
insetDef += (this.geometryBox) ? this.geometryBox : "";
- this.currentNode.style.setProperty(this.property, insetDef, "important");
+ this.emit("highlighter-event", { type: "shape-change", value: insetDef });
}
/**
* Handle a click when highlighting a polygon.
* @param {Number} pageX the x coordinate of the click
* @param {Number} pageY the y coordinate of the click
*/
_handlePolygonClick(pageX, pageY) {
@@ -950,18 +950,18 @@ class ShapesHighlighter extends AutoRefr
let ratioY = (valueY / yComputed) || 1;
this.setCursor("grabbing");
this[_dragging] = { point, unitX, unitY, valueX, valueY,
ratioX, ratioY, x: pageX, y: pageY };
}
/**
- * Set the inline style of the polygon, replacing the given point with the given x/y
- * coords.
+ * Update the dragged polygon point with the given x/y coords and update
+ * the element style.
* @param {Number} pageX the new x coordinate of the point
* @param {Number} pageY the new y coordinate of the point
*/
_handlePolygonMove(pageX, pageY) {
let { point, unitX, unitY, valueX, valueY, ratioX, ratioY, x, y } = this[_dragging];
let deltaX = (pageX - x) * ratioX;
let deltaY = (pageY - y) * ratioY;
let precisionX = getDecimalPrecision(unitX);
@@ -971,21 +971,21 @@ class ShapesHighlighter extends AutoRefr
let polygonDef = (this.fillRule) ? `${this.fillRule}, ` : "";
polygonDef += this.coordUnits.map((coords, i) => {
return (i === point) ?
`${newX}${unitX} ${newY}${unitY}` : `${coords[0]} ${coords[1]}`;
}).join(", ");
polygonDef = `polygon(${polygonDef}) ${this.geometryBox}`.trim();
- this.currentNode.style.setProperty(this.property, polygonDef, "important");
+ this.emit("highlighter-event", { type: "shape-change", value: polygonDef });
}
/**
- * Set the inline style of the polygon, adding a new point.
+ * Add new point to the polygon defintion and update element style.
* TODO: Bug 1436054 - Do not default to percentage unit when inserting new point.
* https://bugzilla.mozilla.org/show_bug.cgi?id=1436054
*
* @param {Number} after the index of the point that the new point should be added after
* @param {Number} x the x coordinate of the new point
* @param {Number} y the y coordinate of the new point
*/
_addPolygonPoint(after, x, y) {
@@ -993,35 +993,35 @@ class ShapesHighlighter extends AutoRefr
polygonDef += this.coordUnits.map((coords, i) => {
return (i === after) ? `${coords[0]} ${coords[1]}, ${x}% ${y}%` :
`${coords[0]} ${coords[1]}`;
}).join(", ");
polygonDef = `polygon(${polygonDef}) ${this.geometryBox}`.trim();
this.hoveredPoint = after + 1;
this._emitHoverEvent(this.hoveredPoint);
- this.currentNode.style.setProperty(this.property, polygonDef, "important");
+ this.emit("highlighter-event", { type: "shape-change", value: polygonDef });
}
/**
- * Set the inline style of the polygon, deleting the given point.
+ * Remove point from polygon defintion and update the element style.
* @param {Number} point the index of the point to delete
*/
_deletePolygonPoint(point) {
let coordinates = this.coordUnits.slice();
coordinates.splice(point, 1);
let polygonDef = (this.fillRule) ? `${this.fillRule}, ` : "";
polygonDef += coordinates.map((coords, i) => {
return `${coords[0]} ${coords[1]}`;
}).join(", ");
polygonDef = `polygon(${polygonDef}) ${this.geometryBox}`.trim();
this.hoveredPoint = null;
this._emitHoverEvent(this.hoveredPoint);
- this.currentNode.style.setProperty(this.property, polygonDef, "important");
+ this.emit("highlighter-event", { type: "shape-change", value: polygonDef });
}
/**
* Handle a click when highlighting a circle.
* @param {Number} pageX the x coordinate of the click
* @param {Number} pageY the y coordinate of the click
*/
_handleCircleClick(pageX, pageY) {
let { width, height } = this.currentDimensions;
@@ -1055,18 +1055,18 @@ class ShapesHighlighter extends AutoRefr
value = (isUnitless(value)) ? radius : parseFloat(value);
let ratio = (value / radius) || 1;
this[_dragging] = { point, value, origRadius: radius, unit, ratio };
}
}
/**
- * Set the inline style of the circle, setting the center/radius according to the
- * mouse position.
+ * Set the center/radius of the circle according to the mouse position and
+ * update the element style.
* @param {String} point either "center" or "radius"
* @param {Number} pageX the x coordinate of the mouse position, in terms of %
* relative to the element
* @param {Number} pageY the y coordinate of the mouse position, in terms of %
* relative to the element
*/
_handleCircleMove(point, pageX, pageY) {
let { radius, cx, cy } = this.coordUnits;
@@ -1075,30 +1075,30 @@ class ShapesHighlighter extends AutoRefr
let { unitX, unitY, valueX, valueY, ratioX, ratioY, x, y} = this[_dragging];
let deltaX = (pageX - x) * ratioX;
let deltaY = (pageY - y) * ratioY;
let newCx = `${valueX + deltaX}${unitX}`;
let newCy = `${valueY + deltaY}${unitY}`;
// if not defined by the user, geometryBox will be an empty string; trim() cleans up
let circleDef = `circle(${radius} at ${newCx} ${newCy}) ${this.geometryBox}`.trim();
- this.currentNode.style.setProperty(this.property, circleDef, "important");
+ this.emit("highlighter-event", { type: "shape-change", value: circleDef });
} else if (point === "radius") {
let { value, unit, origRadius, ratio } = this[_dragging];
// convert center point to px, then get distance between center and mouse.
let { x: pageCx, y: pageCy } = this.convertPercentToPageCoords(this.coordinates.cx,
this.coordinates.cy);
let newRadiusPx = getDistance(pageCx, pageCy, pageX, pageY);
let delta = (newRadiusPx - origRadius) * ratio;
let newRadius = `${value + delta}${unit}`;
let circleDef = `circle(${newRadius} at ${cx} ${cy}) ${this.geometryBox}`.trim();
- this.currentNode.style.setProperty(this.property, circleDef, "important");
+ this.emit("highlighter-event", { type: "shape-change", value: circleDef });
}
}
/**
* Handle a click when highlighting an ellipse.
* @param {Number} pageX the x coordinate of the click
* @param {Number} pageY the y coordinate of the click
*/
@@ -1142,18 +1142,18 @@ class ShapesHighlighter extends AutoRefr
value = (isUnitless(value)) ? ry : parseFloat(value);
let ratio = (value / ry) || 1;
this[_dragging] = { point, value, origRadius: ry, unit, ratio };
}
}
/**
- * Set the inline style of the ellipse, setting the center/rx/ry according to the
- * mouse position.
+ * Set center/rx/ry of the ellispe according to the mouse position and update the
+ * element style.
* @param {String} point "center", "rx", or "ry"
* @param {Number} pageX the x coordinate of the mouse position, in terms of %
* relative to the element
* @param {Number} pageY the y coordinate of the mouse position, in terms of %
* relative to the element
*/
_handleEllipseMove(point, pageX, pageY) {
let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
@@ -1163,39 +1163,39 @@ class ShapesHighlighter extends AutoRefr
let { unitX, unitY, valueX, valueY, ratioX, ratioY, x, y} = this[_dragging];
let deltaX = (pageX - x) * ratioX;
let deltaY = (pageY - y) * ratioY;
let newCx = `${valueX + deltaX}${unitX}`;
let newCy = `${valueY + deltaY}${unitY}`;
let ellipseDef =
`ellipse(${rx} ${ry} at ${newCx} ${newCy}) ${this.geometryBox}`.trim();
- this.currentNode.style.setProperty(this.property, ellipseDef, "important");
+ this.emit("highlighter-event", { type: "shape-change", value: ellipseDef });
} else if (point === "rx") {
let { value, unit, origRadius, ratio } = this[_dragging];
let newRadiusPercent = Math.abs(percentX - this.coordinates.cx);
let { width } = this.currentDimensions;
let delta = ((newRadiusPercent / 100 * width) - origRadius) * ratio;
let newRadius = `${value + delta}${unit}`;
let ellipseDef =
`ellipse(${newRadius} ${ry} at ${cx} ${cy}) ${this.geometryBox}`.trim();
- this.currentNode.style.setProperty(this.property, ellipseDef, "important");
+ this.emit("highlighter-event", { type: "shape-change", value: ellipseDef });
} else if (point === "ry") {
let { value, unit, origRadius, ratio } = this[_dragging];
let newRadiusPercent = Math.abs(percentY - this.coordinates.cy);
let { height } = this.currentDimensions;
let delta = ((newRadiusPercent / 100 * height) - origRadius) * ratio;
let newRadius = `${value + delta}${unit}`;
let ellipseDef =
`ellipse(${rx} ${newRadius} at ${cx} ${cy}) ${this.geometryBox}`.trim();
- this.currentNode.style.setProperty(this.property, ellipseDef, "important");
+ this.emit("highlighter-event", { type: "shape-change", value: ellipseDef });
}
}
/**
* Handle a click when highlighting an inset.
* @param {Number} pageX the x coordinate of the click
* @param {Number} pageY the y coordinate of the click
*/
@@ -1215,18 +1215,18 @@ class ShapesHighlighter extends AutoRefr
value = (isUnitless(value)) ? computedValue : parseFloat(value);
let ratio = (value / computedValue) || 1;
let origValue = (point === "left" || point === "right") ? pageX : pageY;
this[_dragging] = { point, value, origValue, unit, ratio };
}
/**
- * Set the inline style of the inset, setting top/left/right/bottom according to the
- * mouse position.
+ * Set the top/left/right/bottom of the inset shape according to the mouse position
+ * and update the element style.
* @param {String} point "top", "left", "right", or "bottom"
* @param {Number} pageX the x coordinate of the mouse position, in terms of %
* relative to the element
* @param {Number} pageY the y coordinate of the mouse position, in terms of %
* relative to the element
* @memberof ShapesHighlighter
*/
_handleInsetMove(point, pageX, pageY) {
@@ -1248,17 +1248,17 @@ class ShapesHighlighter extends AutoRefr
bottom = `${value - delta}${unit}`;
}
let insetDef = (round) ?
`inset(${top} ${right} ${bottom} ${left} round ${round})` :
`inset(${top} ${right} ${bottom} ${left})`;
insetDef += (this.geometryBox) ? this.geometryBox : "";
- this.currentNode.style.setProperty(this.property, insetDef, "important");
+ this.emit("highlighter-event", { type: "shape-change", value: insetDef });
}
_handleMouseMoveNotDragging(pageX, pageY) {
let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
if (this.transformMode) {
let point = this.getTransformPointAt(percentX, percentY);
this.hoveredPoint = point;
this._handleMarkerHover(point);