Skip to content

Commit

Permalink
add support for resizing elements (#569)
Browse files Browse the repository at this point in the history
* Add UI

* Implement resizing 🎉

* Improve readability

* Fix user-select

* Fix formatting (semicolon and whitespace)

* Move clamp to utilities folder

Co-authored-by: Mayank <[email protected]>
  • Loading branch information
mayank99 and mayank99 authored Oct 4, 2022
1 parent eb62c95 commit 317ab7d
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 24 deletions.
1 change: 1 addition & 0 deletions app/components/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { Handles } from './selection/handles.element'
export { Handle } from './selection/handle.element'
export { Hover } from './selection/hover.element'
export { Label } from './selection/label.element'
export { Gridlines } from './selection/gridlines.element'
Expand Down
4 changes: 2 additions & 2 deletions app/components/selection/corners.element.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Handles } from './handles.element'
import { HandleStyles, CornerStyles } from '../styles.store'
import { HandlesStyles, CornerStyles } from '../styles.store'

export class Corners extends Handles {

constructor() {
super()
this.styles = [HandleStyles, CornerStyles]
this.styles = [HandlesStyles, CornerStyles]
}

render({ width, height, top, left }) {
Expand Down
4 changes: 2 additions & 2 deletions app/components/selection/grip.element.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Handles } from './handles.element'
import { HandleStyles, GripStyles } from '../styles.store'
import { HandlesStyles, GripStyles } from '../styles.store'
import { isFixed } from '../../utilities/';

export class Grip extends Handles {

constructor() {
super()
this.styles = [HandleStyles, GripStyles]
this.styles = [HandlesStyles, GripStyles]
}

toggleHovering({hovering}) {
Expand Down
77 changes: 77 additions & 0 deletions app/components/selection/handle.element.css
Original file line number Diff line number Diff line change
@@ -0,0 +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));
}

:host([hidden]) {
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);

/* increase tap target size */
&::before {
content: '';
position: absolute;
inset: -0.5rem;
}
}

: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([placement^="top"]),
:host([placement^="bottom"]) {
--cursor: ns-resize;
}

:host([placement$="start"]),
:host([placement$="end"]) {
--cursor: ew-resize;
}

:host([placement="top-start"]) {
--cursor: nw-resize;
}

:host([placement="top-end"]) {
--cursor: ne-resize;
}

:host([placement="bottom-start"]) {
--cursor: sw-resize;
}

:host([placement="bottom-end"]) {
--cursor: se-resize;
}
179 changes: 179 additions & 0 deletions app/components/selection/handle.element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import $ from 'blingblingjs'
import { HandleStyles } from '../styles.store'
import { clamp } from '../../utilities/numbers'

export class Handle extends HTMLElement {

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.placement = this.getAttribute('placement')
}

static get observedAttributes() {
return ['placement']
}

attributeChangedCallback(name, oldValue, newValue) {
if (name === 'placement') {
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
}
}

disconnectedCallback() {
this.button.removeEventListener('pointerdown', this.on_element_resize_start.bind(this))
}

render() {
return `
<button type="button" aria-label="Resize"></button>
`
}
}

customElements.define('visbug-handle', Handle)
10 changes: 9 additions & 1 deletion app/components/selection/handles.element.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
@import "../_variables.css";

:host > svg {
:host {
position: var(--position, 'absolute');
top: var(--top);
left: var(--left);
overflow: visible;
pointer-events: none;
z-index: var(--layer-3);
width: var(--width);
height: var(--height);
display: grid;
isolation: isolate;
}

:host > svg {
position: absolute;
}
30 changes: 16 additions & 14 deletions app/components/selection/handles.element.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import $ from 'blingblingjs'
import { HandleStyles } from '../styles.store'
import { HandlesStyles } from '../styles.store'
import { isFixed } from '../../utilities/';

export class Handles extends HTMLElement {

constructor() {
super()
this.$shadow = this.attachShadow({mode: 'closed'})
this.styles = [HandleStyles]
this.on_resize = this.on_resize.bind(this)
this.styles = [HandlesStyles]
this.on_resize = this.on_window_resize.bind(this)
}

connectedCallback() {
this.$shadow.adoptedStyleSheets = this.styles
window.addEventListener('resize', this.on_resize)
window.addEventListener('resize', this.on_window_resize)
}

disconnectedCallback() {
window.removeEventListener('resize', this.on_resize)
window.removeEventListener('resize', this.on_window_resize)
}

on_resize() {
on_window_resize() {
window.requestAnimationFrame(() => {
const node_label_id = this.$shadow.host.getAttribute('data-label-id')
const [source_el] = $(`[data-label-id="${node_label_id}"]`)
Expand Down Expand Up @@ -62,6 +62,8 @@ export class Handles extends HTMLElement {
this.style.setProperty('--top', `${top + (isFixed ? 0 : window.scrollY)}px`)
this.style.setProperty('--left', `${left}px`)
this.style.setProperty('--position', isFixed ? 'fixed' : 'absolute')
this.style.setProperty('--width', `${width}px`)
this.style.setProperty('--height', `${height}px`)

return `
<svg
Expand All @@ -71,15 +73,15 @@ export class Handles extends HTMLElement {
version="1.1" xmlns="http://www.w3.org/2000/svg"
>
<rect stroke="hotpink" fill="none" width="100%" height="100%"></rect>
<circle stroke="hotpink" fill="white" cx="0" cy="0" r="2"></circle>
<circle stroke="hotpink" fill="white" cx="100%" cy="0" r="2"></circle>
<circle stroke="hotpink" fill="white" cx="100%" cy="100%" r="2"></circle>
<circle stroke="hotpink" fill="white" cx="0" cy="100%" r="2"></circle>
<circle fill="hotpink" cx="${width/2}" cy="0" r="2"></circle>
<circle fill="hotpink" cx="0" cy="${height/2}" r="2"></circle>
<circle fill="hotpink" cx="${width/2}" cy="${height}" r="2"></circle>
<circle fill="hotpink" cx="${width}" cy="${height/2}" r="2"></circle>
</svg>
<visbug-handle placement="top-start"></visbug-handle>
<visbug-handle placement="top-center"></visbug-handle>
<visbug-handle placement="top-end"></visbug-handle>
<visbug-handle placement="middle-start"></visbug-handle>
<visbug-handle placement="middle-end"></visbug-handle>
<visbug-handle placement="bottom-start"></visbug-handle>
<visbug-handle placement="bottom-center"></visbug-handle>
<visbug-handle placement="bottom-end"></visbug-handle>
`
}
}
Expand Down
4 changes: 2 additions & 2 deletions app/components/selection/hover.element.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Handles } from './handles.element'
import { HandleStyles, HoverStyles } from '../styles.store'
import { HandlesStyles, HoverStyles } from '../styles.store'

export class Hover extends Handles {

constructor() {
super()
this.styles = [HandleStyles, HoverStyles]
this.styles = [HandlesStyles, HoverStyles]
}

render({ width, height, top, left }, node_label_id, isFixed) {
Expand Down
Loading

0 comments on commit 317ab7d

Please sign in to comment.