diff --git a/app/components/index.js b/app/components/index.js index f6aeb776..6c7396f3 100644 --- a/app/components/index.js +++ b/app/components/index.js @@ -1,3 +1,8 @@ +import { addPolyfill } from 'get-box-quads-polyfill' + +// The polyfill preserves native GeometryUtils methods and only fills in missing ones. +addPolyfill(window) + export { Handles } from './selection/handles.element' export { Handle } from './selection/handle.element' export { Hover } from './selection/hover.element' diff --git a/app/components/selection/box-model.element.css b/app/components/selection/box-model.element.css index 7d116455..fc664d37 100644 --- a/app/components/selection/box-model.element.css +++ b/app/components/selection/box-model.element.css @@ -1,18 +1,13 @@ :host [mask] { + inset: 0; + height: 100%; pointer-events: none; position: absolute; + width: 100%; z-index: var(--layer-5); - width: var(--width); - height: var(--height); - top: var(--top); - left: var(--left); - background-color: var(--bg); - clip-path: polygon( - 0% 0%, 0% 100%, var(--target-left) 100%, - var(--target-left) var(--target-top), - var(--offset-right) var(--target-top), - var(--offset-right) var(--offset-bottom), - 0 var(--offset-bottom), 0 100%, - 100% 100%, 100% 0% - ); -} \ No newline at end of file +} + +:host svg { + display: block; + overflow: visible; +} diff --git a/app/components/selection/box-model.element.js b/app/components/selection/box-model.element.js index dbeb8c0f..89182149 100644 --- a/app/components/selection/box-model.element.js +++ b/app/components/selection/box-model.element.js @@ -1,4 +1,5 @@ import { BoxModelStyles } from '../styles.store' +import { getBoxQuad, quadBounds, quadPath, sideMidpoint } from './quad' export class BoxModel extends HTMLElement { @@ -16,15 +17,21 @@ export class BoxModel extends HTMLElement { set position(payload) { this.$shadow.innerHTML = this.render(payload) - if (!this.drawable.measurements) // && payload.color === 'pink' - this.createMeasurements(payload) + this.createMeasurements({...payload, ...this.drawable.measurementQuads}) } - render({mode, bounds, sides, color = 'pink'}) { + render({mode, bounds, sides, color = 'pink', element}) { const total_height = bounds.height + sides.bottom + sides.top const total_width = bounds.width + sides.right + sides.left + const borderQuad = getBoxQuad(element) + const borderBounds = quadBounds(borderQuad) + const origin = {x: borderBounds.left, y: borderBounds.top} + let outerQuad + let innerQuad if (mode === 'padding') { + outerQuad = getBoxQuad(element, 'padding') + innerQuad = getBoxQuad(element, 'content') this.drawable = { height: bounds.height - (sides.borders.top + sides.borders.bottom), width: bounds.width - (sides.borders.right + sides.borders.left), @@ -34,6 +41,8 @@ export class BoxModel extends HTMLElement { } } else if (mode === 'margin') { + outerQuad = getBoxQuad(element, 'margin') + innerQuad = borderQuad this.drawable = { height: total_height, width: total_width, @@ -43,6 +52,9 @@ export class BoxModel extends HTMLElement { } } + this.drawable.d = `${quadPath(outerQuad, origin)} ${quadPath(innerQuad, origin)}` + this.drawable.measurementQuads = {outerQuad, innerQuad, origin} + if (color === 'pink') { this.drawable.bg = 'color(display-p3 1 0 1 / 15%)' this.drawable.stripe = 'color(display-p3 1 0 1 / 80%)' @@ -56,13 +68,14 @@ export class BoxModel extends HTMLElement { return `
- + - + +
` @@ -84,94 +97,27 @@ export class BoxModel extends HTMLElement { this.style.setProperty('--offset-bottom', `${this.drawable.height - sides.bottom}px`) } - createMeasurements({mode, bounds, sides, color}) { - const win_width = window.innerWidth - const pill_height = 18 - const offset = 3 - - if (mode === 'margin') { - if (sides.top) { - this.createMeasurement({ - x: (bounds.width / 2) - offset, - y: (window.scrollY * -1) - sides.top, - d: sides.top, - q: 'top', - v: true, - color, - }) - } - if (sides.bottom) { - this.createMeasurement({ - x: (bounds.width / 2) - offset, - y: (window.scrollY * -1) + bounds.height, - d: sides.bottom, - q: 'bottom', - v: true, - color, - }) - } - if (sides.right) { - this.createMeasurement({ - x: bounds.width, - y: (window.scrollY * -1) + (bounds.height / 2) - offset, - d: sides.right, - q: 'right', - v: false, - color, - }) - } - if (sides.left) { - this.createMeasurement({ - x: sides.left * -1, - y: (window.scrollY * -1) + (bounds.height / 2) - offset, - d: sides.left, - q: 'left', - v: false, - color, - }) - } - } - else if (mode === 'padding') { - if (sides.top) { - this.createMeasurement({ - x: (bounds.width / 2) - offset, - y: (window.scrollY * -1) + sides.borders.top, - d: sides.top, - q: 'top', - v: true, - color, - }) - } - if (sides.bottom) { - this.createMeasurement({ - x: (bounds.width / 2) - offset, - y: (window.scrollY * -1) + (bounds.height - sides.bottom - sides.borders.bottom), - d: sides.bottom, - q: 'bottom', - v: true, - color, - }) - } - if (sides.right) { - this.createMeasurement({ - x: bounds.width - sides.right - sides.borders.right, - y: (window.scrollY * -1) + (bounds.height / 2) - offset, - d: sides.right, - q: 'right', - v: false, - color, - }) - } - if (sides.left) { - this.createMeasurement({ - x: 0 + sides.borders.left, - y: (window.scrollY * -1) + (bounds.height / 2) - offset, - d: sides.left, - q: 'left', - v: false, - color, - }) - } + createMeasurements({sides, color, outerQuad, innerQuad, origin}) { + for (const side of ['top', 'right', 'bottom', 'left']) { + if (!sides[side]) continue + + const start = sideMidpoint(outerQuad, side) + const end = sideMidpoint(innerQuad, side) + const dx = end.x - start.x + const dy = end.y - start.y + + this.createMeasurement({ + x: start.x - origin.x, + y: start.y - origin.y, + d: sides[side], + length: Math.hypot(dx, dy), + angle: Math.atan2(dy, dx), + q: side, + v: false, + local: true, + centered: true, + color, + }) } } diff --git a/app/components/selection/distance.element.css b/app/components/selection/distance.element.css index 5c0d959c..f89f7013 100644 --- a/app/components/selection/distance.element.css +++ b/app/components/selection/distance.element.css @@ -30,6 +30,8 @@ align-items: center; justify-content: var(--justify, 'flex-start'); flex-direction: var(--direction); + transform: rotate(var(--angle, 0rad)); + transform-origin: top left; } :host > figure figcaption { @@ -49,6 +51,7 @@ line-height: 1.1; font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif; padding: 0 1ex; + transform: rotate(var(--caption-angle, 0rad)); } :host > figure span { diff --git a/app/components/selection/distance.element.js b/app/components/selection/distance.element.js index 36a68e89..81d95200 100644 --- a/app/components/selection/distance.element.js +++ b/app/components/selection/distance.element.js @@ -21,19 +21,23 @@ export class Distance extends HTMLElement { this.$shadow.innerHTML = this.render(line_model, node_label_id) } - set styleProps({y,x,d,q,v = false, color}) { - this.style.setProperty('--top', `${Math.round(y + window.scrollY)}px`) + set styleProps({y,x,d,q,v = false, color, local = false, length = d, angle = 0, centered = false}) { + this.style.setProperty('--top', `${Math.round(y + (local ? 0 : window.scrollY))}px`) this.style.setProperty('--right', 'auto') this.style.setProperty('--left', `${x}px`) this.style.setProperty('--direction', v ? 'column' : 'row') this.style.setProperty('--quadrant', q) + this.style.setProperty('--angle', `${angle}rad`) + this.style.setProperty('--caption-angle', `${angle * -1}rad`) - if (q === 'left') + if (centered) + this.style.setProperty('--justify', 'center') + else if (q === 'left') this.style.setProperty('--justify', 'flex-end') v - ? this.style.setProperty('--distance-h', `${d}px`) - : this.style.setProperty('--distance-w', `${d}px`) + ? this.style.setProperty('--distance-h', `${length}px`) + : this.style.setProperty('--distance-w', `${length}px`) v ? this.style.setProperty('--line-h', `var(--line-w)`) diff --git a/app/components/selection/handle.element.css b/app/components/selection/handle.element.css index 9d26d5a0..7c039f6b 100644 --- a/app/components/selection/handle.element.css +++ b/app/components/selection/handle.element.css @@ -4,6 +4,7 @@ display: grid; grid-area: 1 / -1; place-self: var(--align-self, center) var(--justify-self, center); + position: absolute; transform: translate(var(--translate-x, 0), var(--translate-y, 0)); } @@ -30,24 +31,8 @@ } } -:host([placement^="top"]) { - --align-self: start; - --translate-y: -50%; -} - -:host([placement^="bottom"]) { - --align-self: end; - --translate-y: 50%; -} - -:host([placement$="start"]) { - --justify-self: start; - --translate-x: -50%; -} - -:host([placement$="end"]) { - --justify-self: end; - --translate-x: 50%; +:host { + translate: -50% -50%; } :host([placement^="top"]), diff --git a/app/components/selection/handle.element.js b/app/components/selection/handle.element.js index 6721aaf9..607b9710 100644 --- a/app/components/selection/handle.element.js +++ b/app/components/selection/handle.element.js @@ -1,6 +1,5 @@ import $ from 'blingblingjs' import { HandleStyles } from '../styles.store' -import { clamp } from '../../utilities/numbers' export class Handle extends HTMLElement { @@ -8,14 +7,15 @@ export class Handle extends HTMLElement { super() this.$shadow = this.attachShadow({mode: 'closed'}) this.styles = [HandleStyles] + this.on_resize_start = this.on_element_resize_start.bind(this) } connectedCallback() { this.$shadow.adoptedStyleSheets = this.styles this.$shadow.innerHTML = this.render() - + this.button = this.$shadow.querySelector('button') - this.button.addEventListener('pointerdown', this.on_element_resize_start.bind(this)) + this.button.addEventListener('pointerdown', this.on_resize_start) this.placement = this.getAttribute('placement') } @@ -30,6 +30,9 @@ export class Handle extends HTMLElement { } } + /** + * @param {PointerEvent} e + */ on_element_resize_start(e) { e.preventDefault() e.stopPropagation() @@ -39,11 +42,14 @@ export class Handle extends HTMLElement { const placement = this.placement const handlesEl = e.composedPath().find(el => el.tagName === 'VISBUG-HANDLES') const nodeLabelId = handlesEl.getAttribute('data-label-id') + /** @type {Element[]} */ const [sourceEl] = $(`[data-label-id="${nodeLabelId}"]`) if (!sourceEl) return - const { x: initialX, y: initialY } = e + const t = sourceEl.convertPointFromNode(e, document.documentElement) + + const { x: initialX, y: initialY } = t const initialStyle = getComputedStyle(sourceEl) const initialWidth = parseFloat(initialStyle.width) const initialHeight = parseFloat(initialStyle.height) @@ -62,92 +68,82 @@ export class Handle extends HTMLElement { e.preventDefault() e.stopPropagation() - const newX = clamp(0, e.clientX, document.documentElement.clientWidth) - const newY = clamp(0, e.clientY, document.documentElement.clientHeight) - + const {x: newX, y: newY} = sourceEl.convertPointFromNode(e, document.documentElement) + const diffX = newX - initialX const diffY = newY - initialY + const leftWidth = Math.max(0, initialWidth - diffX) + const rightWidth = Math.max(0, initialWidth + diffX) + const topHeight = Math.max(0, initialHeight - diffY) + const bottomHeight = Math.max(0, initialHeight + diffY) + const leftShift = initialWidth - leftWidth + const topShift = initialHeight - topHeight switch (placement) { case 'top-start': { - const newWidth = initialWidth - diffX - const newHeight = initialHeight - diffY - const newTranslate = initialTransform.translate(diffX, diffY).transformPoint() + const newTransform = initialTransform.translate(leftShift, topShift) requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` - sourceEl.style.height = `${newHeight}px` - sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` + sourceEl.style.width = `${leftWidth}px` + sourceEl.style.height = `${topHeight}px` + sourceEl.style.transform = newTransform.toString() }) break } case 'top-center': { - const newHeight = initialHeight - diffY - const newTranslate = initialTransform.translate(0, diffY).transformPoint() + const newTransform = initialTransform.translate(0, topShift) requestAnimationFrame(() => { - sourceEl.style.height = `${newHeight}px` - sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` + sourceEl.style.height = `${topHeight}px` + sourceEl.style.transform = newTransform.toString() }) break } case 'top-end': { - const newWidth = initialWidth + diffX - const newHeight = initialHeight - diffY - const newTranslate = initialTransform.translate(0, diffY).transformPoint() + const newTransform = initialTransform.translate(0, topShift) requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` - sourceEl.style.height = `${newHeight}px` - sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` + sourceEl.style.width = `${rightWidth}px` + sourceEl.style.height = `${topHeight}px` + sourceEl.style.transform = newTransform.toString() }) break } case 'middle-start': { - const newWidth = initialWidth - diffX - const newTranslate = initialTransform.translate(diffX).transformPoint() + const newTransform = initialTransform.translate(leftShift) requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` - sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` + sourceEl.style.width = `${leftWidth}px` + sourceEl.style.transform = newTransform.toString() }) break } case 'middle-end': { - const newWidth = initialWidth + diffX - requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` + sourceEl.style.width = `${rightWidth}px` }) break } case 'bottom-start': { - const newWidth = initialWidth - diffX - const newHeight = initialHeight + diffY - const newTranslate = initialTransform.translate(diffX, 0).transformPoint() + const newTransform = initialTransform.translate(leftShift, 0) requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` - sourceEl.style.height = `${newHeight}px` - sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` + sourceEl.style.width = `${leftWidth}px` + sourceEl.style.height = `${bottomHeight}px` + sourceEl.style.transform = newTransform.toString() }) break } case 'bottom-center': { - const newHeight = initialHeight + diffY - requestAnimationFrame(() => { - sourceEl.style.height = `${newHeight}px` + sourceEl.style.height = `${bottomHeight}px` }) break } case 'bottom-end': { - const newWidth = initialWidth + diffX - const newHeight = initialHeight + diffY - requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` - sourceEl.style.height = `${newHeight}px` + sourceEl.style.width = `${rightWidth}px` + sourceEl.style.height = `${bottomHeight}px` }) break } @@ -166,7 +162,7 @@ export class Handle extends HTMLElement { } disconnectedCallback() { - this.button.removeEventListener('pointerdown', this.on_element_resize_start.bind(this)) + this.button && this.button.removeEventListener('pointerdown', this.on_resize_start) } render() { diff --git a/app/components/selection/handles.element.css b/app/components/selection/handles.element.css index 25085966..575c5d65 100644 --- a/app/components/selection/handles.element.css +++ b/app/components/selection/handles.element.css @@ -1,7 +1,7 @@ @import "../_variables.css"; :host { - position: var(--position, 'absolute'); + position: var(--position, absolute); inset: var(--top) auto auto var(--left); background: transparent; border: none; diff --git a/app/components/selection/handles.element.js b/app/components/selection/handles.element.js index 90785ea2..d7a890c7 100644 --- a/app/components/selection/handles.element.js +++ b/app/components/selection/handles.element.js @@ -1,6 +1,6 @@ -import $ from 'blingblingjs' import { HandlesStyles } from '../styles.store' -import { isFixed } from '../../utilities/'; +import { isFixed } from '../../utilities/' +import { getBoxQuad, quadBounds, quadPath, sideMidpoint } from './quad' export class Handles extends HTMLElement { @@ -8,39 +8,43 @@ export class Handles extends HTMLElement { super() this.$shadow = this.attachShadow({mode: 'closed'}) this.styles = [HandlesStyles] - this.on_resize = this.on_window_resize.bind(this) + this.on_position_change = this.on_position_change.bind(this) + this.position_frame = null + this.source_el = null } connectedCallback() { this.$shadow.adoptedStyleSheets = this.styles this.setAttribute('popover', 'manual') this.showPopover && this.showPopover() - window.addEventListener('resize', this.on_window_resize) + window.addEventListener('resize', this.on_position_change) + window.addEventListener('scroll', this.on_position_change, true) } disconnectedCallback() { - if (this.hidePopover && this.hidePopover()) this.hidePopover && this.hidePopover() - window.removeEventListener('resize', this.on_window_resize) + this.hidePopover && this.hidePopover() + window.removeEventListener('resize', this.on_position_change) + window.removeEventListener('scroll', this.on_position_change, true) + this.position_frame && window.cancelAnimationFrame(this.position_frame) } - on_window_resize() { - if (!this.$shadow) return - window.requestAnimationFrame(() => { - const node_label_id = this.$shadow.host.getAttribute('data-label-id') - const [source_el] = $(`[data-label-id="${node_label_id}"]`) + on_position_change() { + if (this.position_frame || !this.source_el) return - if (!source_el) return + this.position_frame = window.requestAnimationFrame(() => { + this.position_frame = null + if (!this.source_el.isConnected) return this.position = { - node_label_id, - el: source_el, - isFixed: isFixed(source_el), + node_label_id: this.getAttribute('data-label-id'), + el: this.source_el, } }) } set position({el, node_label_id}) { - this.$shadow.innerHTML = this.render(el.getBoundingClientRect(), node_label_id, isFixed(el)) + this.source_el = el + this.$shadow.innerHTML = this.render(getBoxQuad(el), node_label_id, isFixed(el)) if (this._backdrop) { this.backdrop = { @@ -60,11 +64,31 @@ export class Handles extends HTMLElement { : this.$shadow.appendChild(bd.element) } - render({ x, y, width, height, top, left }, node_label_id, isFixed) { + /** + * + * @param {DOMQuad} quad + * @param {string} node_label_id + * @param {boolean} isFixed + * @returns + */ + render(quad, node_label_id, isFixed) { this.$shadow.host.setAttribute('data-label-id', node_label_id) + const {left, top, width, height} = quadBounds(quad) + const origin = {x: left, y: top} + const positions = { + 'top-start': quad.p1, + 'top-center': sideMidpoint(quad, 'top'), + 'top-end': quad.p2, + 'middle-start': sideMidpoint(quad, 'left'), + 'middle-end': sideMidpoint(quad, 'right'), + 'bottom-start': quad.p4, + 'bottom-center': sideMidpoint(quad, 'bottom'), + 'bottom-end': quad.p3, + } + this.style.setProperty('--top', `${top + (isFixed ? 0 : window.scrollY)}px`) - this.style.setProperty('--left', `${left}px`) + this.style.setProperty('--left', `${left + (isFixed ? 0 : window.scrollX)}px`) this.style.setProperty('--position', isFixed ? 'fixed' : 'absolute') this.style.setProperty('--width', `${width}px`) this.style.setProperty('--height', `${height}px`) @@ -76,16 +100,13 @@ export class Handles extends HTMLElement { viewBox="0 0 ${width} ${height}" version="1.1" xmlns="http://www.w3.org/2000/svg" > - + - - - - - - - - + ${Object.entries(positions).map(([placement, point]) => ` + `).join('')} ` } } diff --git a/app/components/selection/hover.element.css b/app/components/selection/hover.element.css index 82f824d9..24d16609 100644 --- a/app/components/selection/hover.element.css +++ b/app/components/selection/hover.element.css @@ -1,6 +1,7 @@ @import "../_variables.css"; -:host rect { +:host rect, +:host path { width: 100%; height: 100%; vector-effect: non-scaling-stroke; diff --git a/app/components/selection/hover.element.js b/app/components/selection/hover.element.js index ee4175de..412b6373 100644 --- a/app/components/selection/hover.element.js +++ b/app/components/selection/hover.element.js @@ -1,5 +1,6 @@ import { Handles } from './handles.element' import { HandlesStyles, HoverStyles } from '../styles.store' +import { quadBounds, quadPath } from './quad' export class Hover extends Handles { @@ -14,17 +15,22 @@ export class Hover extends Handles { disconnectedCallback() {} - render({ width, height, top, left }, node_label_id, isFixed) { + render(quad, node_label_id, isFixed) { + const {width, height, top, left} = quadBounds(quad) + this.style.setProperty('--top', `${top + (isFixed ? 0 : window.scrollY)}px`) - this.style.setProperty('--left', `${left}px`) + this.style.setProperty('--left', `${left + (isFixed ? 0 : window.scrollX)}px`) this.style.setProperty('--position', isFixed ? 'fixed' : 'absolute') + this.style.setProperty('--width', `${width}px`) + this.style.setProperty('--height', `${height}px`) return ` - + + ` } } diff --git a/app/components/selection/quad.js b/app/components/selection/quad.js new file mode 100644 index 00000000..01b94033 --- /dev/null +++ b/app/components/selection/quad.js @@ -0,0 +1,60 @@ +import { getBoxQuads as getPolyfillBoxQuads } from 'get-box-quads-polyfill' + +export const quadBounds = quad => { + const points = [quad.p1, quad.p2, quad.p3, quad.p4] + const left = Math.min(...points.map(point => point.x)) + const right = Math.max(...points.map(point => point.x)) + const top = Math.min(...points.map(point => point.y)) + const bottom = Math.max(...points.map(point => point.y)) + + return { + left, + right, + top, + bottom, + width: right - left, + height: bottom - top, + } +} + +export const quadPath = (quad, origin = {x: 0, y: 0}) => + `M${[quad.p1, quad.p2, quad.p3, quad.p4] + .map(point => `${point.x - origin.x},${point.y - origin.y}`) + .join(' ')}Z` + +export const sideMidpoint = (quad, side) => { + const [start, end] = { + top: [quad.p1, quad.p2], + right: [quad.p2, quad.p3], + bottom: [quad.p4, quad.p3], + left: [quad.p1, quad.p4], + }[side] + + return { + x: start.x + ((end.x - start.x) / 2), + y: start.y + ((end.y - start.y) / 2), + } +} + +const rectToQuad = ({left, right, top, bottom}) => ({ + p1: {x: left, y: top}, + p2: {x: right, y: top}, + p3: {x: right, y: bottom}, + p4: {x: left, y: bottom}, +}) + +export const getBoxQuad = (element, box = 'border') => { + try { + const [quad] = element.getBoxQuads({box}) + if (quad) return quad + } + catch {} + + try { + const [quad] = getPolyfillBoxQuads(element, {box}) + if (quad) return quad + } + catch {} + + return rectToQuad(element.getBoundingClientRect()) +} diff --git a/app/components/selection/quad.test.js b/app/components/selection/quad.test.js new file mode 100644 index 00000000..f68da220 --- /dev/null +++ b/app/components/selection/quad.test.js @@ -0,0 +1,56 @@ +import test from 'ava' +import { getBoxQuad, quadBounds, quadPath, sideMidpoint } from './quad' + +const quad = { + p1: {x: 10, y: 20}, + p2: {x: 50, y: 10}, + p3: {x: 60, y: 40}, + p4: {x: 20, y: 50}, +} + +test('calculates the axis-aligned bounds of a transformed quad', t => { + t.deepEqual(quadBounds(quad), { + left: 10, + right: 60, + top: 10, + bottom: 50, + width: 50, + height: 40, + }) +}) + +test('creates a local SVG path for a transformed quad', t => { + t.is(quadPath(quad, {x: 10, y: 10}), 'M0,10 40,0 50,30 10,40Z') +}) + +test('calculates transformed side midpoints', t => { + t.deepEqual(sideMidpoint(quad, 'top'), {x: 30, y: 15}) + t.deepEqual(sideMidpoint(quad, 'right'), {x: 55, y: 25}) + t.deepEqual(sideMidpoint(quad, 'bottom'), {x: 40, y: 45}) + t.deepEqual(sideMidpoint(quad, 'left'), {x: 15, y: 35}) +}) + +test('uses getBoxQuads when the element provides a quad', t => { + const element = { + getBoxQuads: options => { + t.deepEqual(options, {box: 'padding'}) + return [quad] + }, + } + + t.is(getBoxQuad(element, 'padding'), quad) +}) + +test('falls back to getBoundingClientRect for an empty quad list', t => { + const element = { + getBoxQuads: () => [], + getBoundingClientRect: () => ({left: 1, right: 5, top: 2, bottom: 8}), + } + + t.deepEqual(getBoxQuad(element), { + p1: {x: 1, y: 2}, + p2: {x: 5, y: 2}, + p3: {x: 5, y: 8}, + p4: {x: 1, y: 8}, + }) +}) diff --git a/app/features/margin.js b/app/features/margin.js index 8b66ff30..23dc1b52 100644 --- a/app/features/margin.js +++ b/app/features/margin.js @@ -108,11 +108,12 @@ export function createMarginVisual(el, hover = false) { sides[side] = Math.round(val.toFixed(1) * 100) / 100 }) - boxdisplay.position = { + boxdisplay.position = { mode: 'margin', color: hover ? 'purple' : 'pink', - bounds, + bounds, sides, + element: el } } diff --git a/app/features/padding.js b/app/features/padding.js index 9f672b12..94c5e5ee 100644 --- a/app/features/padding.js +++ b/app/features/padding.js @@ -109,14 +109,15 @@ export function createPaddingVisual(el, hover = false) { sides[side] = Math.round(val.toFixed(1) * 100) / 100 }) - boxdisplay.position = { + boxdisplay.position = { mode: 'padding', color: hover ? 'purple' : 'pink', - bounds, + bounds, sides: { ...sides, borders: calculatedBorder, }, + element: el } } diff --git a/package-lock.json b/package-lock.json index 7f890141..3a01fd51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "blingblingjs": "^2.3.0", "colorjs.io": "^0.5.0", "construct-style-sheets-polyfill": "^2.4.2", + "get-box-quads-polyfill": "^4.47.0", "hotkeys-js": "^3.13.7", "query-selector-shadow-dom": "^1.0.1" }, @@ -8234,6 +8235,12 @@ "loader-utils": "^1.1.0" } }, + "node_modules/get-box-quads-polyfill": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/get-box-quads-polyfill/-/get-box-quads-polyfill-4.47.0.tgz", + "integrity": "sha512-e/zXXHWm0wYuizK9XsWHOjyxdrPNwv0qIkMAaJIOQ1eovlGWecjHGlNAjkLO4cWlABZdVN6XC3OncaRgoY/ong==", + "license": "MIT" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", diff --git a/package.json b/package.json index dc0b3c3b..fce3bfcc 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "blingblingjs": "^2.3.0", "colorjs.io": "^0.5.0", "construct-style-sheets-polyfill": "^2.4.2", + "get-box-quads-polyfill": "^4.47.0", "hotkeys-js": "^3.13.7", "query-selector-shadow-dom": "^1.0.1" },