refactor sets to use lit

This commit is contained in:
“chrisshank” 2024-12-09 23:37:40 -08:00
parent d7b0cfe85b
commit 45cbde27e2
4 changed files with 55 additions and 72 deletions

View File

@ -44,11 +44,11 @@
<folk-shape x="100" y="300" width="50" height="50"></folk-shape> <folk-shape x="100" y="300" width="50" height="50"></folk-shape>
<folk-hull sources="h1, body > folk-shape"></folk-hull> <folk-hull sources="h1, body > folk-shape"></folk-hull>
<!-- <folk-hull sources="h1"> <folk-hull sources="h1">
<folk-shape x="350" y="100" width="50" height="50"></folk-shape> <folk-shape x="350" y="100" width="50" height="50"></folk-shape>
<folk-shape x="500" y="200" width="50" height="50"></folk-shape> <folk-shape x="500" y="200" width="50" height="50"></folk-shape>
<folk-shape x="400" y="300" width="50" height="50"></folk-shape> <folk-shape x="400" y="300" width="50" height="50"></folk-shape>
</folk-hull> --> </folk-hull>
<script type="module"> <script type="module">
import '../src/standalone/folk-shape.ts'; import '../src/standalone/folk-shape.ts';

View File

@ -1,28 +1,13 @@
import { property, state } from '@lit/reactive-element/decorators.js';
import { ClientRectObserverEntry } from './common/client-rect-observer.ts'; import { ClientRectObserverEntry } from './common/client-rect-observer.ts';
import { FolkElement } from './common/folk-element.ts';
import { FolkObserver } from './common/folk-observer.ts'; import { FolkObserver } from './common/folk-observer.ts';
import { PropertyValues } from '@lit/reactive-element';
const folkObserver = new FolkObserver(); const folkObserver = new FolkObserver();
const defaultRect = DOMRectReadOnly.fromRect(); export class FolkBaseSet extends FolkElement {
@property({ type: String, reflect: true }) sources = '';
export class FolkBaseSet extends HTMLElement {
static tagName = '';
static define() {
if (customElements.get(this.tagName)) return;
customElements.define(this.tagName, this);
}
#sources = this.getAttribute('sources') || '';
/** A CSS selector for the sources of the arrow. */
get sources() {
return this.#sources;
}
set sources(sources) {
this.#sources = sources;
this.observeSources();
}
#sourcesMap = new Map<Element, DOMRectReadOnly>(); #sourcesMap = new Map<Element, DOMRectReadOnly>();
get sourcesMap(): ReadonlyMap<Element, DOMRectReadOnly> { get sourcesMap(): ReadonlyMap<Element, DOMRectReadOnly> {
@ -33,34 +18,32 @@ export class FolkBaseSet extends HTMLElement {
return Array.from(this.#sourcesMap.values()); return Array.from(this.#sourcesMap.values());
} }
#sourceElements = new Set<Element>(); @state() sourceElements = new Set<Element>();
get sourceElements(): ReadonlySet<Element> {
return this.#sourceElements;
}
#sourcesCallback = (entry: ClientRectObserverEntry) => { #sourcesCallback = (entry: ClientRectObserverEntry) => {
this.#sourcesMap.set(entry.target, entry.contentRect); this.#sourcesMap.set(entry.target, entry.contentRect);
if (this.#sourceElements.size === this.#sourcesMap.size) { this.requestUpdate();
this.update();
}
}; };
connectedCallback() {
this.observeSources();
}
disconnectedCallback() { disconnectedCallback() {
super.disconnectedCallback();
this.unobserveSources(); this.unobserveSources();
} }
override update(changedProperties: PropertyValues<this>) {
super.update(changedProperties);
if (changedProperties.has('sources')) {
this.observeSources();
}
}
observeSources() { observeSources() {
const elements = this.sources ? document.querySelectorAll(this.sources) : [];
const childElements = new Set(this.children); const childElements = new Set(this.children);
const elements = this.sources ? document.querySelectorAll(this.sources) : [];
const sourceElements = new Set(elements).union(childElements); const sourceElements = new Set(elements).union(childElements);
const elementsToObserve = sourceElements.difference(this.sourceElements);
const elementsToObserve = sourceElements.difference(this.#sourceElements); const elementsToUnobserve = this.sourceElements.difference(sourceElements);
const elementsToUnobserve = this.#sourceElements.difference(sourceElements);
this.unobserveSources(elementsToUnobserve); this.unobserveSources(elementsToUnobserve);
@ -68,16 +51,14 @@ export class FolkBaseSet extends HTMLElement {
folkObserver.observe(el, this.#sourcesCallback); folkObserver.observe(el, this.#sourcesCallback);
} }
this.#sourceElements = sourceElements; this.sourceElements = sourceElements;
} }
unobserveSources(elements: Set<Element> = this.#sourceElements) { unobserveSources(elements: Set<Element> = this.sourceElements) {
for (const el of elements) { for (const el of elements) {
folkObserver.unobserve(el, this.#sourcesCallback); folkObserver.unobserve(el, this.#sourcesCallback);
this.#sourcesMap.delete(el); this.#sourcesMap.delete(el);
this.#sourceElements.delete(el); this.sourceElements.delete(el);
} }
} }
update() {}
} }

View File

@ -4,6 +4,7 @@ import { glsl } from './common/tags.ts';
import { WebGLUtils } from './common/webgl.ts'; import { WebGLUtils } from './common/webgl.ts';
import { FolkBaseSet } from './folk-base-set.ts'; import { FolkBaseSet } from './folk-base-set.ts';
import { FolkShape } from './folk-shape.ts'; import { FolkShape } from './folk-shape.ts';
import { PropertyValues } from '@lit/reactive-element';
/** /**
* The DistanceField class calculates a distance field using the Jump Flooding Algorithm (JFA) in WebGL. * The DistanceField class calculates a distance field using the Jump Flooding Algorithm (JFA) in WebGL.
@ -76,7 +77,11 @@ export class FolkDistanceField extends FolkBaseSet {
/** /**
* Handles updates to geometry elements by re-initializing seed points and rerunning the JFA. * Handles updates to geometry elements by re-initializing seed points and rerunning the JFA.
*/ */
override update() { override update(changedProperties: PropertyValues<this>) {
super.update(changedProperties);
if (this.sourcesMap.size !== this.sourceElements.size) return;
this.populateSeedPoints(); this.populateSeedPoints();
this.runJumpFloodingAlgorithm(); this.runJumpFloodingAlgorithm();
} }

View File

@ -1,7 +1,7 @@
import { FolkBaseSet } from './folk-base-set'; import { FolkBaseSet } from './folk-base-set';
import { verticesToPolygon } from './common/utils'; import { verticesToPolygon } from './common/utils';
import type { Point } from './common/types'; import type { Point } from './common/types';
import { css, html } from './common/tags'; import { PropertyValues, css } from '@lit/reactive-element';
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
@ -9,44 +9,40 @@ declare global {
} }
} }
const styles = css`
:host {
pointer-events: none;
}
#hull {
background-color: var(--folk-hull-bg, #b4d8f644);
height: 100%;
width: 100%;
}
::slotted(*) {
pointer-events: auto;
}
`;
export class FolkHull extends FolkBaseSet { export class FolkHull extends FolkBaseSet {
static tagName = 'folk-hull'; static tagName = 'folk-hull';
static styles = css`
:host {
pointer-events: none;
}
#hull {
background-color: var(--folk-hull-bg, #b4d8f644);
height: 100%;
width: 100%;
}
::slotted(*) {
pointer-events: auto;
}
`;
#hull: Point[] = []; #hull: Point[] = [];
get hull(): ReadonlyArray<Point> { get hull(): ReadonlyArray<Point> {
return this.#hull; return this.#hull;
} }
#shadow = this.attachShadow({ mode: 'open' });
#slot = document.createElement('slot'); #slot = document.createElement('slot');
#hullEl = document.createElement('div'); #hullEl = document.createElement('div');
constructor() { override firstUpdated(changedProperties: PropertyValues<this>): void {
super(); super.firstUpdated(changedProperties);
this.#hullEl.id = 'hull'; this.#hullEl.id = 'hull';
this.#shadow.adoptedStyleSheets.push(styles); this.renderRoot.append(this.#hullEl, this.#slot);
this.#shadow.append(this.#hullEl, this.#slot);
this.#slot.addEventListener('slotchange', this.#onSlotchange); this.#slot.addEventListener('slotchange', this.#onSlotchange);
} }
@ -54,14 +50,15 @@ export class FolkHull extends FolkBaseSet {
// we might not need to react to the first slot change // we might not need to react to the first slot change
#onSlotchange = () => this.observeSources(); #onSlotchange = () => this.observeSources();
update() { override update(changedProperties: PropertyValues<this>) {
if (this.sourcesMap.size === 0) { super.update(changedProperties);
if (this.sourcesMap.size !== this.sourceElements.size) {
this.style.clipPath = ''; this.style.clipPath = '';
return; return;
} }
const rects = Array.from(this.sourcesMap.values()); this.#hull = makeHull(this.sourceRects);
this.#hull = makeHull(rects);
this.#hullEl.style.clipPath = verticesToPolygon(this.#hull); this.#hullEl.style.clipPath = verticesToPolygon(this.#hull);
} }
} }