diff --git a/app/components/selection/handle.element.css b/app/components/selection/handle.element.css index 852f6113..0e3a9679 100644 --- a/app/components/selection/handle.element.css +++ b/app/components/selection/handle.element.css @@ -1,77 +1,77 @@ @import "../_variables.css"; :host { - display: grid; - grid-area: 1 / -1; - place-self: var(--align-self, center) var(--justify-self, center); - transform: translate(var(--translate-x, 0), var(--translate-y, 0)); + display: grid; + grid-area: 1 / -1; + place-self: var(--align-self, center) var(--justify-self, center); + transform: translate(var(--translate-x, 0), var(--translate-y, 0)); } :host([hidden]) { - display: none; + display: none; } :host > button { - pointer-events: auto; - background-color: white; - border: 1px solid hotpink; - padding: 0; - width: 0.5rem; - height: 0.5rem; - border-radius: 50%; - position: relative; - cursor: var(--cursor); + pointer-events: auto; + background-color: white; + border: 1px solid hotpink; + padding: 0; + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + position: relative; + cursor: var(--cursor); - /* increase tap target size */ - &::before { - content: ''; - position: absolute; - inset: -0.5rem; - } + /* increase tap target size */ + &::before { + content: ''; + position: absolute; + inset: -0.5rem; + } } :host([placement^="top"]) { - --align-self: start; - --translate-y: -50%; + --align-self: start; + --translate-y: -50%; } :host([placement^="bottom"]) { - --align-self: end; - --translate-y: 50%; + --align-self: end; + --translate-y: 50%; } :host([placement$="start"]) { - --justify-self: start; - --translate-x: -50%; + --justify-self: start; + --translate-x: -50%; } :host([placement$="end"]) { - --justify-self: end; - --translate-x: 50%; + --justify-self: end; + --translate-x: 50%; } :host([placement^="top"]), :host([placement^="bottom"]) { - --cursor: ns-resize; + --cursor: ns-resize; } :host([placement$="start"]), :host([placement$="end"]) { - --cursor: ew-resize; + --cursor: ew-resize; } :host([placement="top-start"]) { - --cursor: nw-resize; + --cursor: nw-resize; } :host([placement="top-end"]) { - --cursor: ne-resize; + --cursor: ne-resize; } :host([placement="bottom-start"]) { - --cursor: sw-resize; + --cursor: sw-resize; } :host([placement="bottom-end"]) { - --cursor: se-resize; + --cursor: se-resize; } diff --git a/app/components/selection/handle.element.js b/app/components/selection/handle.element.js index 375ed141..eab7548d 100644 --- a/app/components/selection/handle.element.js +++ b/app/components/selection/handle.element.js @@ -3,176 +3,176 @@ import { HandleStyles } from '../styles.store' export class Handle extends HTMLElement { - constructor() { - super() + constructor() { + super() this.$shadow = this.attachShadow({mode: 'closed'}) this.styles = [HandleStyles] - } + } 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.$shadow.innerHTML = this.render() + + this.button = this.$shadow.querySelector('button') + this.button.addEventListener('pointerdown', this.on_element_resize_start.bind(this)) - this.placement = this.getAttribute('placement') - } + this.placement = this.getAttribute('placement') + } - static get observedAttributes() { + static get observedAttributes() { return ['placement'] } - attributeChangedCallback(name, oldValue, newValue) { + attributeChangedCallback(name, oldValue, newValue) { if (name === 'placement') { - this.placement = newValue - } + this.placement = newValue + } + } + + on_element_resize_start(e) { + e.preventDefault() + e.stopPropagation() + + if (e.button !== 0) return + + const placement = this.placement + const handlesEl = e.path.find(el => el.tagName === 'VISBUG-HANDLES') + const nodeLabelId = handlesEl.getAttribute('data-label-id') + const [sourceEl] = $(`[data-label-id="${nodeLabelId}"]`) + + if (!sourceEl) return + + const { x: initialX, y: initialY } = e + const initialStyle = getComputedStyle(sourceEl) + const initialWidth = parseFloat(initialStyle.width) + const initialHeight = parseFloat(initialStyle.height) + const initialTransform = new DOMMatrix(initialStyle.transform) + + const originalElTransition = sourceEl.style.transition + const originalDocumentCursor = document.body.style.cursor + const originalDocumentUserSelect = document.body.style.userSelect + sourceEl.style.transition = 'none' + document.body.style.cursor = getComputedStyle(this).getPropertyValue('--cursor') + document.body.style.userSelect = 'none' + + document.addEventListener('pointermove', on_element_resize_move) + + function on_element_resize_move(e) { + e.preventDefault() + e.stopPropagation() + + const newX = clamp(0, e.clientX, document.documentElement.clientWidth) + const newY = clamp(0, e.clientY, document.documentElement.clientHeight) + + const diffX = newX - initialX + const diffY = newY - initialY + + switch (placement) { + case 'top-start': { + const newWidth = initialWidth - diffX + const newHeight = initialHeight - diffY + const newTranslate = initialTransform.translate(diffX, diffY).transformPoint() + + requestAnimationFrame(() => { + sourceEl.style.width = `${newWidth}px` + sourceEl.style.height = `${newHeight}px` + sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` + }) + break + } + case 'top-center': { + const newHeight = initialHeight - diffY + const newTranslate = initialTransform.translate(0, diffY).transformPoint() + + requestAnimationFrame(() => { + sourceEl.style.height = `${newHeight}px` + sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` + }) + break + } + case 'top-end': { + const newWidth = initialWidth + diffX + const newHeight = initialHeight - diffY + const newTranslate = initialTransform.translate(0, diffY).transformPoint() + + requestAnimationFrame(() => { + sourceEl.style.width = `${newWidth}px` + sourceEl.style.height = `${newHeight}px` + sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` + }) + break + } + case 'middle-start': { + const newWidth = initialWidth - diffX + const newTranslate = initialTransform.translate(diffX).transformPoint() + + requestAnimationFrame(() => { + sourceEl.style.width = `${newWidth}px` + sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` + }) + break + } + case 'middle-end': { + const newWidth = initialWidth + diffX + + requestAnimationFrame(() => { + sourceEl.style.width = `${newWidth}px` + }) + break + } + case 'bottom-start': { + const newWidth = initialWidth - diffX + const newHeight = initialHeight + diffY + const newTranslate = initialTransform.translate(diffX, 0).transformPoint() + + requestAnimationFrame(() => { + sourceEl.style.width = `${newWidth}px` + sourceEl.style.height = `${newHeight}px` + sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` + }) + break + } + case 'bottom-center': { + const newHeight = initialHeight + diffY + + requestAnimationFrame(() => { + sourceEl.style.height = `${newHeight}px` + }) + break + } + case 'bottom-end': { + const newWidth = initialWidth + diffX + const newHeight = initialHeight + diffY + + requestAnimationFrame(() => { + sourceEl.style.width = `${newWidth}px` + sourceEl.style.height = `${newHeight}px` + }) + break + } + } + } + + document.addEventListener('pointerup', on_element_resize_end, { once: true }) + document.addEventListener('mouseleave', on_element_resize_end, { once: true }) + + function on_element_resize_end() { + document.removeEventListener('pointermove', on_element_resize_move) + document.body.style.cursor = originalDocumentCursor + document.body.style.userSelect = originalDocumentUserSelect + sourceEl.style.transition = originalElTransition + } } - on_element_resize_start(e) { - e.preventDefault() - e.stopPropagation() - - if (e.button !== 0) return - - const placement = this.placement - const handlesEl = e.path.find(el => el.tagName === 'VISBUG-HANDLES') - const nodeLabelId = handlesEl.getAttribute('data-label-id') - const [sourceEl] = $(`[data-label-id="${nodeLabelId}"]`) - - if (!sourceEl) return - - const { x: initialX, y: initialY } = e - const initialStyle = getComputedStyle(sourceEl) - const initialWidth = parseFloat(initialStyle.width) - const initialHeight = parseFloat(initialStyle.height) - const initialTransform = new DOMMatrix(initialStyle.transform) - - const originalElTransition = sourceEl.style.transition - const originalDocumentCursor = document.body.style.cursor - const originalDocumentUserSelect = document.body.style.userSelect - sourceEl.style.transition = 'none' - document.body.style.cursor = getComputedStyle(this).getPropertyValue('--cursor') - document.body.style.userSelect = 'none' - - document.addEventListener('pointermove', on_element_resize_move) - - function on_element_resize_move(e) { - e.preventDefault() - e.stopPropagation() - - const newX = clamp(0, e.clientX, document.documentElement.clientWidth) - const newY = clamp(0, e.clientY, document.documentElement.clientHeight) - - const diffX = newX - initialX - const diffY = newY - initialY - - switch (placement) { - case 'top-start': { - const newWidth = initialWidth - diffX - const newHeight = initialHeight - diffY - const newTranslate = initialTransform.translate(diffX, diffY).transformPoint() - - requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` - sourceEl.style.height = `${newHeight}px` - sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` - }) - break - } - case 'top-center': { - const newHeight = initialHeight - diffY - const newTranslate = initialTransform.translate(0, diffY).transformPoint() - - requestAnimationFrame(() => { - sourceEl.style.height = `${newHeight}px` - sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` - }) - break - } - case 'top-end': { - const newWidth = initialWidth + diffX - const newHeight = initialHeight - diffY - const newTranslate = initialTransform.translate(0, diffY).transformPoint() - - requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` - sourceEl.style.height = `${newHeight}px` - sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` - }) - break - } - case 'middle-start': { - const newWidth = initialWidth - diffX - const newTranslate = initialTransform.translate(diffX).transformPoint() - - requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` - sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` - }) - break - } - case 'middle-end': { - const newWidth = initialWidth + diffX - - requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` - }) - break - } - case 'bottom-start': { - const newWidth = initialWidth - diffX - const newHeight = initialHeight + diffY - const newTranslate = initialTransform.translate(diffX, 0).transformPoint() - - requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` - sourceEl.style.height = `${newHeight}px` - sourceEl.style.transform = `translate(${newTranslate.x}px, ${newTranslate.y}px)` - }) - break - } - case 'bottom-center': { - const newHeight = initialHeight + diffY - - requestAnimationFrame(() => { - sourceEl.style.height = `${newHeight}px` - }) - break - } - case 'bottom-end': { - const newWidth = initialWidth + diffX - const newHeight = initialHeight + diffY - - requestAnimationFrame(() => { - sourceEl.style.width = `${newWidth}px` - sourceEl.style.height = `${newHeight}px` - }) - break - } - } - } - - document.addEventListener('pointerup', on_element_resize_end, { once: true }) - document.addEventListener('mouseleave', on_element_resize_end, { once: true }) - - function on_element_resize_end() { - document.removeEventListener('pointermove', on_element_resize_move) - document.body.style.cursor = originalDocumentCursor - document.body.style.userSelect = originalDocumentUserSelect - sourceEl.style.transition = originalElTransition - } - } - - disconnectedCallback() { - this.button.removeEventListener('pointerdown', this.on_element_resize_start.bind(this)) - } - - render() { - return ` - - ` - } + disconnectedCallback() { + this.button.removeEventListener('pointerdown', this.on_element_resize_start.bind(this)) + } + + render() { + return ` + + ` + } } customElements.define('visbug-handle', Handle) @@ -180,5 +180,5 @@ customElements.define('visbug-handle', Handle) // utility functions function clamp(min, val, max) { - return Math.max(min, Math.min(val, max)) + return Math.max(min, Math.min(val, max)) }