refactor base connection to use lit

This commit is contained in:
“chrisshank” 2024-12-09 23:08:34 -08:00
parent 7c9e614d76
commit 5b61fe8d42
6 changed files with 76 additions and 94 deletions

View File

@ -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<this>) {
super.update(changedProperties);
const { sourceRect, targetRect } = this;
if (sourceRect === undefined || targetRect === undefined) {
if (sourceRect === null || targetRect === null) {
this.style.clipPath = '';
return;
}

View File

@ -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<this>) {
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) {}
}

View File

@ -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<this>) {
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;

View File

@ -180,6 +180,7 @@ export class FolkShape extends HTMLElement {
#rect = new DOMRectTransform();
#previousRect = new DOMRectTransform();
#readonlyRect = new DOMRectTransformReadonly();
#handles: Record<ResizeHandle | RotateHandle, HTMLElement>;
@ -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) {

View File

@ -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<this>) {
super.update(changedProperties);
let { sourceRect, targetRect } = this;
if (sourceRect === null || targetRect === null) {
this.style.clipPath = '';
return;
}

View File

@ -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"],