refactor folkshape to use lit

This commit is contained in:
“chrisshank” 2024-12-19 20:58:19 -08:00
parent 39881e3930
commit 0a4debaaad
3 changed files with 33 additions and 69 deletions

View File

@ -38,8 +38,6 @@ export class FolkBaseConnection extends FolkElement {
} }
override willUpdate(changedProperties: PropertyValues<this>) { override willUpdate(changedProperties: PropertyValues<this>) {
super.willUpdate(changedProperties);
if (changedProperties.has('source')) { if (changedProperties.has('source')) {
this.#unobserveSource(); this.#unobserveSource();

View File

@ -45,8 +45,6 @@ export class FolkBaseSet extends FolkElement {
} }
override willUpdate(changedProperties: PropertyValues<this>) { override willUpdate(changedProperties: PropertyValues<this>) {
super.willUpdate(changedProperties);
if (changedProperties.has('sources')) { if (changedProperties.has('sources')) {
this.#observeSources(); this.#observeSources();
} }

View File

@ -1,7 +1,8 @@
import { getResizeCursorUrl, getRotateCursorUrl } from '@labs/utils/cursors'; import { getResizeCursorUrl, getRotateCursorUrl } from '@labs/utils/cursors';
import { DOMRectTransform, DOMRectTransformReadonly, Point, TransformEvent, Vector } from '@lib'; import { DOMRectTransform, DOMRectTransformReadonly, FolkElement, Point, TransformEvent, Vector } from '@lib';
import { ResizeManager } from '@lib/resize-manger'; import { ResizeManager } from '@lib/resize-manger';
import { css, html } from '@lib/tags'; import { html } from '@lib/tags';
import { css, PropertyValues } from '@lit/reactive-element';
const resizeManager = new ResizeManager(); const resizeManager = new ResizeManager();
@ -163,15 +164,11 @@ declare global {
} }
} }
export class FolkShape extends HTMLElement { export class FolkShape extends FolkElement {
static tagName = 'folk-shape'; static tagName = 'folk-shape';
static define() { static styles = styles;
if (customElements.get(this.tagName)) return;
customElements.define(this.tagName, this);
}
#shadow = this.attachShadow({ mode: 'open' });
#internals = this.attachInternals(); #internals = this.attachInternals();
#attrWidth: Dimension = 0; #attrWidth: Dimension = 0;
@ -181,7 +178,7 @@ export class FolkShape extends HTMLElement {
#previousRect = new DOMRectTransform(); #previousRect = new DOMRectTransform();
#readonlyRect = new DOMRectTransformReadonly(); #readonlyRect = new DOMRectTransformReadonly();
#handles: Record<ResizeHandle | RotateHandle, HTMLElement>; #handles!: Record<ResizeHandle | RotateHandle, HTMLElement>;
#startAngle = 0; #startAngle = 0;
@ -192,7 +189,7 @@ export class FolkShape extends HTMLElement {
set x(x) { set x(x) {
this.#previousRect.x = this.#rect.x; this.#previousRect.x = this.#rect.x;
this.#rect.x = x; this.#rect.x = x;
this.#requestUpdate(); this.requestUpdate('x');
} }
get y() { get y() {
@ -202,7 +199,7 @@ export class FolkShape extends HTMLElement {
set y(y) { set y(y) {
this.#previousRect.y = this.#rect.y; this.#previousRect.y = this.#rect.y;
this.#rect.y = y; this.#rect.y = y;
this.#requestUpdate(); this.requestUpdate('x');
} }
get width(): number { get width(): number {
@ -220,7 +217,7 @@ export class FolkShape extends HTMLElement {
this.#rect.width = width; this.#rect.width = width;
} }
this.#attrWidth = width; this.#attrWidth = width;
this.#requestUpdate(); this.requestUpdate('width');
} }
get height(): number { get height(): number {
@ -239,7 +236,7 @@ export class FolkShape extends HTMLElement {
} }
this.#attrHeight = height; this.#attrHeight = height;
this.#requestUpdate(); this.requestUpdate('height');
} }
get rotation(): number { get rotation(): number {
@ -249,7 +246,7 @@ export class FolkShape extends HTMLElement {
set rotation(rotation: number) { set rotation(rotation: number) {
this.#previousRect.rotation = this.#rect.rotation; this.#previousRect.rotation = this.#rect.rotation;
this.#rect.rotation = rotation; this.#rect.rotation = rotation;
this.#requestUpdate(); this.requestUpdate('rotation');
} }
#highlighted = false; #highlighted = false;
@ -264,17 +261,16 @@ export class FolkShape extends HTMLElement {
highlighted ? this.#internals.states.add('highlighted') : this.#internals.states.delete('highlighted'); highlighted ? this.#internals.states.add('highlighted') : this.#internals.states.delete('highlighted');
} }
constructor() { override createRenderRoot() {
super(); const root = super.createRenderRoot();
this.addEventListener('pointerdown', this); this.addEventListener('pointerdown', this);
this.addEventListener('keydown', this); this.addEventListener('keydown', this);
this.#shadow.adoptedStyleSheets.push(styles);
// Ideally we would creating these lazily on first focus, but the resize handlers need to be around for delegate focus to work. // Ideally we would creating these lazily on first focus, but the resize handlers need to be around for delegate focus to work.
// Maybe can add the first resize handler here, and lazily instantiate the rest when needed? // Maybe can add the first resize handler here, and lazily instantiate the rest when needed?
// I can see it becoming important at scale // I can see it becoming important at scale
this.#shadow.setHTMLUnsafe( (root as ShadowRoot).setHTMLUnsafe(
html` <button part="rotation-top-left" tabindex="-1"></button> html` <button part="rotation-top-left" tabindex="-1"></button>
<button part="rotation-top-right" tabindex="-1"></button> <button part="rotation-top-right" tabindex="-1"></button>
<button part="rotation-bottom-right" tabindex="-1"></button> <button part="rotation-bottom-right" tabindex="-1"></button>
@ -287,7 +283,7 @@ export class FolkShape extends HTMLElement {
); );
this.#handles = Object.fromEntries( this.#handles = Object.fromEntries(
Array.from(this.#shadow.querySelectorAll('[part]')).map((el) => [ Array.from(root.querySelectorAll('[part]')).map((el) => [
el.getAttribute('part') as ResizeHandle | RotateHandle, el.getAttribute('part') as ResizeHandle | RotateHandle,
el as HTMLElement, el as HTMLElement,
]), ]),
@ -305,14 +301,10 @@ export class FolkShape extends HTMLElement {
this.#rect.rotateOrigin = { x: 0.5, y: 0.5 }; this.#rect.rotateOrigin = { x: 0.5, y: 0.5 };
this.#previousRect = new DOMRectTransform(this.#rect); this.#previousRect = new DOMRectTransform(this.#rect);
}
#isConnected = false;
connectedCallback() {
this.setAttribute('tabindex', '0'); this.setAttribute('tabindex', '0');
this.#isConnected = true;
this.#update(); return root;
} }
// todo: rename to `getDOMRectTransform` // todo: rename to `getDOMRectTransform`
@ -320,18 +312,8 @@ export class FolkShape extends HTMLElement {
return this.#readonlyRect; return this.#readonlyRect;
} }
// Similar to `Element.getClientBoundingRect()`, but returns an SVG path that precisely outlines the shape.
getBoundingPath(): string {
return '';
}
// We might also want some kind of utility function that maps a path into an approximate set of vertices.
getBoundingVertices() {
return [];
}
handleEvent(event: PointerEvent | KeyboardEvent) { handleEvent(event: PointerEvent | KeyboardEvent) {
const focusedElement = this.#shadow.activeElement as HTMLElement | null; const focusedElement = (this.renderRoot as ShadowRoot).activeElement as HTMLElement | null;
const target = event.composedPath()[0] as HTMLElement; const target = event.composedPath()[0] as HTMLElement;
let handle: Handle | null = null; let handle: Handle | null = null;
if (target) { if (target) {
@ -457,52 +439,38 @@ export class FolkShape extends HTMLElement {
} }
} }
#isUpdating = false; protected override update(changedProperties: PropertyValues): void {
super.update(changedProperties);
async #requestUpdate() {
if (!this.#isConnected) return;
if (this.#isUpdating) return;
this.#isUpdating = true;
await true;
this.#isUpdating = false;
this.#update();
}
// Any updates that should be batched should happen here like updating the DOM or emitting events should be executed here.
#update() {
this.#dispatchTransformEvent(); this.#dispatchTransformEvent();
} }
#dispatchTransformEvent() { #dispatchTransformEvent() {
const emmittedRect = new DOMRectTransform(this.#rect); const emittedRect = new DOMRectTransform(this.#rect);
console.log('emit X', this.#rect.x); const event = new TransformEvent(emittedRect, this.#previousRect);
const event = new TransformEvent(emmittedRect, this.#previousRect);
this.dispatchEvent(event); this.dispatchEvent(event);
if (event.xPrevented) { if (event.xPrevented) {
emmittedRect.x = this.#previousRect.x; emittedRect.x = this.#previousRect.x;
} }
if (event.yPrevented) { if (event.yPrevented) {
emmittedRect.y = this.#previousRect.y; emittedRect.y = this.#previousRect.y;
} }
if (event.widthPrevented) { if (event.widthPrevented) {
emmittedRect.width = this.#previousRect.width; emittedRect.width = this.#previousRect.width;
} }
if (event.heightPrevented) { if (event.heightPrevented) {
emmittedRect.height = this.#previousRect.height; emittedRect.height = this.#previousRect.height;
} }
if (event.rotatePrevented) { if (event.rotatePrevented) {
emmittedRect.rotation = this.#previousRect.rotation; emittedRect.rotation = this.#previousRect.rotation;
} }
console.log('applied X', emmittedRect.x); this.style.transform = emittedRect.toCssString();
this.style.transform = emmittedRect.toCssString(); this.style.width = this.#attrWidth === 'auto' ? '' : `${emittedRect.width}px`;
this.style.width = this.#attrWidth === 'auto' ? '' : `${emmittedRect.width}px`; this.style.height = this.#attrHeight === 'auto' ? '' : `${emittedRect.height}px`;
this.style.height = this.#attrHeight === 'auto' ? '' : `${emmittedRect.height}px`;
this.#readonlyRect = new DOMRectTransformReadonly(emmittedRect); this.#readonlyRect = new DOMRectTransformReadonly(emittedRect);
} }
#onAutoResize = (entry: ResizeObserverEntry) => { #onAutoResize = (entry: ResizeObserverEntry) => {
@ -561,7 +529,7 @@ export class FolkShape extends HTMLElement {
nextHandle = flipYHandleMap[handle]; nextHandle = flipYHandleMap[handle];
} }
const newTarget = this.#shadow.querySelector(`[part="${nextHandle}"]`) as HTMLElement; const newTarget = this.renderRoot.querySelector(`[part="${nextHandle}"]`) as HTMLElement;
if (newTarget) { if (newTarget) {
// Update focus for keyboard events // Update focus for keyboard events
@ -587,6 +555,6 @@ export class FolkShape extends HTMLElement {
} }
} }
this.#requestUpdate(); this.requestUpdate();
} }
} }