From 5b61fe8d42134f2c76b3e43266586a4e75dcd3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cchrisshank=E2=80=9D?= Date: Mon, 9 Dec 2024 23:08:34 -0800 Subject: [PATCH] refactor base connection to use lit --- src/folk-arrow.ts | 7 +- src/folk-base-connection.ts | 125 ++++++++++++------------------------ src/folk-rope.ts | 15 ++++- src/folk-shape.ts | 11 ++-- src/folk-xanadu.ts | 9 ++- tsconfig.json | 3 +- 6 files changed, 76 insertions(+), 94 deletions(-) diff --git a/src/folk-arrow.ts b/src/folk-arrow.ts index f800b96..9300a73 100644 --- a/src/folk-arrow.ts +++ b/src/folk-arrow.ts @@ -2,6 +2,7 @@ import { getBoxToBoxArrow } from 'perfect-arrows'; import { FolkBaseConnection } from './folk-base-connection.ts'; import { getSvgPathFromStroke, pointsOnBezierCurves } from './common/utils.ts'; import { getStroke, StrokeOptions } from 'perfect-freehand'; +import { PropertyValues } from '@lit/reactive-element'; export type Arrow = [ /** The x position of the (padded) starting point. */ @@ -53,10 +54,12 @@ export class FolkArrow extends FolkBaseConnection { }, }; - override render() { + override update(changedProperties: PropertyValues) { + super.update(changedProperties); + const { sourceRect, targetRect } = this; - if (sourceRect === undefined || targetRect === undefined) { + if (sourceRect === null || targetRect === null) { this.style.clipPath = ''; return; } diff --git a/src/folk-base-connection.ts b/src/folk-base-connection.ts index cb8aa3e..a13776f 100644 --- a/src/folk-base-connection.ts +++ b/src/folk-base-connection.ts @@ -1,97 +1,62 @@ -import { FolkShape } from './folk-shape.ts'; import { parseVertex } from './common/utils.ts'; import { ClientRectObserverEntry } from './common/client-rect-observer.ts'; import { FolkObserver } from './common/folk-observer.ts'; +import { FolkElement } from './common/folk-element.ts'; +import { property, state } from '@lit/reactive-element/decorators.js'; +import { PropertyValues } from '@lit/reactive-element'; const folkObserver = new FolkObserver(); -export class FolkBaseConnection extends HTMLElement { - static tagName = ''; - - static define() { - if (customElements.get(this.tagName)) return; - FolkShape.define(); - customElements.define(this.tagName, this); - } - - #source = ''; - /** A CSS selector for the source of the arrow. */ - get source() { - return this.#source; - } - - set source(source) { - this.#source = source; - this.observeSource(); - } - - #sourceRect: DOMRectReadOnly | undefined; - get sourceRect() { - return this.#sourceRect; - } +export class FolkBaseConnection extends FolkElement { + @property({ type: String, reflect: true }) source = ''; #sourceElement: Element | null = null; - get sourceElement() { - return this.#sourceElement; - } + @state() sourceRect: DOMRectReadOnly | null = null; - #sourceCallback = (entry: ClientRectObserverEntry) => { - this.#sourceRect = entry.contentRect; - this.#update(); - }; + @property({ type: String, reflect: true }) target = ''; - #target = ''; - /** A CSS selector for the target of the arrow. */ - get target() { - return this.#target; - } - set target(target) { - this.#target = target; - this.observeTarget(); - } - - #targetRect: DOMRectReadOnly | undefined; - get targetRect() { - return this.#targetRect; - } + @state() targetRect: DOMRectReadOnly | null = null; #targetElement: Element | null = null; - get targetElement() { - return this.#targetElement; - } - #targetCallback = (entry: ClientRectObserverEntry) => { - this.#targetRect = entry.contentRect; - this.#update(); - }; - - connectedCallback() { - this.source = this.getAttribute('source') || this.#source; - this.target = this.getAttribute('target') || this.#target; - } - - disconnectedCallback() { + override disconnectedCallback() { + super.disconnectedCallback(); this.unobserveSource(); this.unobserveTarget(); } + override update(changedProperties: PropertyValues) { + super.update(changedProperties); + + if (changedProperties.has('source')) { + this.observeSource(); + } + + if (changedProperties.has('target')) { + this.observeTarget(); + } + } + + #sourceCallback = (entry: ClientRectObserverEntry) => { + this.sourceRect = entry.contentRect; + }; + observeSource() { this.unobserveSource(); - const vertex = parseVertex(this.#source); + const vertex = parseVertex(this.source); if (vertex) { - this.#sourceRect = DOMRectReadOnly.fromRect(vertex); - this.#update(); + this.sourceRect = DOMRectReadOnly.fromRect(vertex); } else { this.#sourceElement = document.querySelector(this.source); if (this.#sourceElement === null) { - throw new Error('source is not a valid element'); + this.sourceRect = null; + } else { + folkObserver.observe(this.#sourceElement, this.#sourceCallback); } - - folkObserver.observe(this.#sourceElement, this.#sourceCallback); } } @@ -101,22 +66,25 @@ export class FolkBaseConnection extends HTMLElement { folkObserver.unobserve(this.#sourceElement, this.#sourceCallback); } + #targetCallback = (entry: ClientRectObserverEntry) => { + this.targetRect = entry.contentRect; + }; + observeTarget() { this.unobserveTarget(); - const vertex = parseVertex(this.#target); + const vertex = parseVertex(this.target); if (vertex) { - this.#targetRect = DOMRectReadOnly.fromRect(vertex); - this.#update(); + this.targetRect = DOMRectReadOnly.fromRect(vertex); } else { - this.#targetElement = document.querySelector(this.#target); + this.#targetElement = document.querySelector(this.target); - if (!this.#targetElement) { - throw new Error('target is not a valid element'); + if (this.#targetElement === null) { + this.targetRect = null; + } else { + folkObserver.observe(this.#targetElement, this.#targetCallback); } - - folkObserver.observe(this.#targetElement, this.#targetCallback); } } @@ -124,13 +92,4 @@ export class FolkBaseConnection extends HTMLElement { if (this.#targetElement === null) return; folkObserver.unobserve(this.#targetElement, this.#targetCallback); } - - #update() { - if (this.#sourceRect === undefined || this.#targetRect === undefined) return; - - this.render(this.#sourceRect, this.#targetRect); - } - - // @ts-ignore - render(sourceRect: DOMRectReadOnly, targetRect: DOMRectReadOnly) {} } diff --git a/src/folk-rope.ts b/src/folk-rope.ts index 5bcfda6..5907dc5 100644 --- a/src/folk-rope.ts +++ b/src/folk-rope.ts @@ -4,6 +4,7 @@ import { Vector } from './common/Vector.ts'; import type { Point } from './common/types.ts'; import { DOMRectTransform } from './common/DOMRectTransform.ts'; import { FolkBaseConnection } from './folk-base-connection.ts'; +import { PropertyValues } from '@lit/reactive-element'; const lerp = (first: number, second: number, percentage: number) => first + (second - first) * percentage; @@ -120,7 +121,19 @@ export class FolkRope extends FolkBaseConnection { this.draw(); }; - override render(sourceRect: DOMRectTransform | DOMRectReadOnly, targetRect: DOMRectTransform | DOMRectReadOnly) { + override update(changedProperties: PropertyValues) { + super.update(changedProperties); + + const { sourceRect, targetRect } = this; + + if (sourceRect === null || targetRect === null) { + cancelAnimationFrame(this.#rAFId); + this.#points = []; + this.#path.removeAttribute('d'); + this.#path2.removeAttribute('d'); + return; + } + let source: Point; let target: Point; diff --git a/src/folk-shape.ts b/src/folk-shape.ts index 0ae8927..7530173 100644 --- a/src/folk-shape.ts +++ b/src/folk-shape.ts @@ -180,6 +180,7 @@ export class FolkShape extends HTMLElement { #rect = new DOMRectTransform(); #previousRect = new DOMRectTransform(); + #readonlyRect = new DOMRectTransformReadonly(); #handles: Record; @@ -294,17 +295,15 @@ export class FolkShape extends HTMLElement { } #isConnected = false; + connectedCallback() { this.setAttribute('tabindex', '0'); this.#isConnected = true; this.#update(); } - getTransformDOMRectReadonly() { - return new DOMRectTransformReadonly(this.#rect); - } getTransformDOMRect() { - return this.#rect; + return this.#readonlyRect; } // Similar to `Element.getClientBoundingRect()`, but returns an SVG path that precisely outlines the shape. @@ -459,7 +458,9 @@ export class FolkShape extends HTMLElement { } #dispatchTransformEvent() { - const event = new TransformEvent(this.#rect, this.#previousRect); + this.#readonlyRect = new DOMRectTransformReadonly(this.#rect); + + const event = new TransformEvent(this.#readonlyRect, this.#previousRect); this.dispatchEvent(event); if (event.xPrevented) { diff --git a/src/folk-xanadu.ts b/src/folk-xanadu.ts index 845e24b..4ba4d59 100644 --- a/src/folk-xanadu.ts +++ b/src/folk-xanadu.ts @@ -1,11 +1,16 @@ import { FolkBaseConnection } from './folk-base-connection.js'; import { verticesToPolygon } from './common/utils.js'; import type { Point } from './common/types.js'; +import { PropertyValues } from '@lit/reactive-element'; export class FolkXanadu extends FolkBaseConnection { static tagName = 'folk-xanadu'; - render(sourceRect: DOMRectReadOnly, targetRect: DOMRectReadOnly): void { - if (this.sourceElement === null || this.targetElement === null) { + override update(changedProperties: PropertyValues) { + super.update(changedProperties); + + let { sourceRect, targetRect } = this; + + if (sourceRect === null || targetRect === null) { this.style.clipPath = ''; return; } diff --git a/tsconfig.json b/tsconfig.json index d581ae5..0e0be3a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,8 @@ "strict": true, "noFallthroughCasesInSwitch": true, "allowImportingTsExtensions": true, - "useDefineForClassFields": true, + "useDefineForClassFields": false, + "experimentalDecorators": true, "skipLibCheck": true, "noUnusedLocals": false, "lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"],