@bodil/dom 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/component.d.ts +218 -6
- package/dist/component.js +154 -13
- package/dist/component.js.map +1 -1
- package/dist/css.d.ts +4 -0
- package/dist/css.js +4 -0
- package/dist/css.js.map +1 -1
- package/dist/decorators/attribute.js +1 -1
- package/dist/decorators/attribute.js.map +1 -1
- package/dist/decorators/connect.test.js +1 -1
- package/dist/decorators/connect.test.js.map +1 -1
- package/dist/decorators/reactive.d.ts +1 -1
- package/dist/decorators/reactive.js +1 -1
- package/dist/decorators/reactive.js.map +1 -1
- package/dist/decorators/reactive.test.js +1 -1
- package/dist/decorators/reactive.test.js.map +1 -1
- package/dist/dom.d.ts +32 -0
- package/dist/dom.js +32 -0
- package/dist/dom.js.map +1 -1
- package/dist/emitter.d.ts +8 -0
- package/dist/emitter.js.map +1 -1
- package/dist/event.d.ts +4 -0
- package/dist/event.js.map +1 -1
- package/dist/geometry.d.ts +7 -0
- package/dist/geometry.js +4 -0
- package/dist/geometry.js.map +1 -1
- package/dist/signal.d.ts +12 -3
- package/dist/signal.js +7 -1
- package/dist/signal.js.map +1 -1
- package/dist/signal.test.js +2 -2
- package/dist/signal.test.js.map +1 -1
- package/package.json +10 -8
- package/src/component.ts +237 -20
- package/src/css.ts +5 -0
- package/src/decorators/attribute.ts +1 -1
- package/src/decorators/connect.test.ts +1 -1
- package/src/decorators/reactive.test.ts +1 -1
- package/src/decorators/reactive.ts +4 -4
- package/src/dom.ts +33 -0
- package/src/emitter.ts +8 -0
- package/src/event.ts +4 -0
- package/src/geometry.ts +8 -0
- package/src/signal.test.ts +2 -2
- package/src/signal.ts +13 -2
package/dist/signal.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signal.test.js","sourceRoot":"","sources":["../src/signal.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEjC,IAAI,CAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE;IAC/B,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"signal.test.js","sourceRoot":"","sources":["../src/signal.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bodil/dom",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "DOM and web component tools",
|
|
5
5
|
"homepage": "https://codeberg.org/bodil/dom",
|
|
6
6
|
"repository": {
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"access": "public"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@bodil/core": "^0.
|
|
66
|
-
"@bodil/opt": "^0.4.
|
|
67
|
-
"@bodil/signal": "^0.
|
|
65
|
+
"@bodil/core": "^0.5.2",
|
|
66
|
+
"@bodil/opt": "^0.4.3",
|
|
67
|
+
"@bodil/signal": "^0.5.1",
|
|
68
68
|
"lit": "^3.3.2",
|
|
69
69
|
"type-fest": "^5.3.1"
|
|
70
70
|
},
|
|
@@ -72,20 +72,22 @@
|
|
|
72
72
|
"@eslint/eslintrc": "^3.3.3",
|
|
73
73
|
"@eslint/js": "^9.39.2",
|
|
74
74
|
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
|
75
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
76
|
-
"@typescript-eslint/parser": "^8.
|
|
75
|
+
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
|
76
|
+
"@typescript-eslint/parser": "^8.52.0",
|
|
77
|
+
"@typhonjs-typedoc/ts-lib-docs": "^2024.12.25",
|
|
77
78
|
"@vitest/browser-playwright": "^4.0.16",
|
|
78
79
|
"@vitest/coverage-v8": "^4.0.16",
|
|
79
80
|
"eslint": "^9.39.2",
|
|
80
81
|
"eslint-config-prettier": "^10.1.8",
|
|
81
|
-
"eslint-plugin-jsdoc": "^
|
|
82
|
-
"globals": "^
|
|
82
|
+
"eslint-plugin-jsdoc": "^61.5.0",
|
|
83
|
+
"globals": "^17.0.0",
|
|
83
84
|
"happy-dom": "^20.0.11",
|
|
84
85
|
"npm-run-all2": "^8.0.4",
|
|
85
86
|
"prettier": "^3.7.4",
|
|
86
87
|
"typedoc": "^0.28.15",
|
|
87
88
|
"typedoc-plugin-extras": "^4.0.1",
|
|
88
89
|
"typedoc-plugin-mdn-links": "^5.0.10",
|
|
90
|
+
"typedoc-theme-fresh": "^0.2.3",
|
|
89
91
|
"typescript": "^5.9.3",
|
|
90
92
|
"vitest": "^4.0.16"
|
|
91
93
|
},
|
package/src/component.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web component base class.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import { isIterable, isNullish } from "@bodil/core/assert";
|
|
2
7
|
import { DisposableContext, toDisposable, type Disposifiable } from "@bodil/core/disposable";
|
|
3
8
|
import { Signal } from "@bodil/signal";
|
|
@@ -9,6 +14,8 @@ import {
|
|
|
9
14
|
unsafeCSS,
|
|
10
15
|
type CSSResult,
|
|
11
16
|
type CSSResultOrNative,
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
18
|
+
type html,
|
|
12
19
|
type ReactiveController,
|
|
13
20
|
type ReactiveControllerHost,
|
|
14
21
|
type RenderOptions,
|
|
@@ -77,6 +84,10 @@ function processCSSStyleSpec(spec: CSSStyleSpec) {
|
|
|
77
84
|
return getCompatibleStyle(typeof spec === "string" ? unsafeCSS(spec) : spec);
|
|
78
85
|
}
|
|
79
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Error thrown when a {@link Component.render} method causes infinite
|
|
89
|
+
* re-renders.
|
|
90
|
+
*/
|
|
80
91
|
export class ComponentUpdateLoopError extends Error {
|
|
81
92
|
constructor(message?: string, options?: ErrorOptions) {
|
|
82
93
|
super(message, options);
|
|
@@ -84,14 +95,49 @@ export class ComponentUpdateLoopError extends Error {
|
|
|
84
95
|
}
|
|
85
96
|
}
|
|
86
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Base class for web components.
|
|
100
|
+
*/
|
|
87
101
|
export abstract class Component
|
|
88
102
|
extends EmitterElement
|
|
89
103
|
implements ReactiveControllerHost, Disposable
|
|
90
104
|
{
|
|
105
|
+
/**
|
|
106
|
+
* Declare the web components this element will be using.
|
|
107
|
+
*
|
|
108
|
+
* This is a convenient place to make sure these web components will be
|
|
109
|
+
* defined when your component is rendered. It has no effect otherwise.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* class MyComponent extends Component {
|
|
113
|
+
* static deps: Deps = [ MySubcomponent, MyOtherSubcomponent ];
|
|
114
|
+
* }
|
|
115
|
+
*/
|
|
91
116
|
static deps: Deps = [];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Declare CSS style sheets which will be installed for this web component.
|
|
120
|
+
*
|
|
121
|
+
* Style sheets can be either {@link CSSStyleSheet} objects,
|
|
122
|
+
* Lit template {@link CSSResult}s, or strings.
|
|
123
|
+
*
|
|
124
|
+
* If a superclass also declares the `styles` property, this list will be
|
|
125
|
+
* appended to the superclass's list.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* class MyComponent extends Component {
|
|
129
|
+
* static styles = css`
|
|
130
|
+
* :host {
|
|
131
|
+
* border: 2px solid red;
|
|
132
|
+
* }
|
|
133
|
+
* `;
|
|
134
|
+
* }
|
|
135
|
+
*/
|
|
92
136
|
static styles?: CSSStyleSpecDeclaration;
|
|
137
|
+
|
|
93
138
|
static shadowRootOptions: ShadowRootInit = { mode: "open" };
|
|
94
|
-
|
|
139
|
+
|
|
140
|
+
private static initialisers = new Set<() => void>();
|
|
95
141
|
|
|
96
142
|
/** @ignore */
|
|
97
143
|
protected static attributeConfig = new Map<string, AttributeConfig>();
|
|
@@ -120,29 +166,59 @@ export abstract class Component
|
|
|
120
166
|
|
|
121
167
|
readonly renderRoot: ShadowRoot | HTMLElement = this.createRenderRoot();
|
|
122
168
|
|
|
169
|
+
/**
|
|
170
|
+
* True if this component had scheduled an update which has not yet started.
|
|
171
|
+
*/
|
|
123
172
|
get isUpdatePending(): boolean {
|
|
124
173
|
return this.#isUpdatePending;
|
|
125
174
|
}
|
|
175
|
+
|
|
126
176
|
get updateComplete(): Promise<boolean> {
|
|
127
177
|
return this.#updateResult.promise;
|
|
128
178
|
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* A {@link Promise} which resolves when this component has completed its
|
|
182
|
+
* first update and considers itself stabilised, according to the
|
|
183
|
+
* {@link Component.stabilise} method.
|
|
184
|
+
*
|
|
185
|
+
* By default, a component considers itself stabilised when all of its
|
|
186
|
+
* children which are also {@link Component}s are reporting as stabilised.
|
|
187
|
+
*/
|
|
129
188
|
get hasStabilised(): Promise<void> {
|
|
130
189
|
return this.#stabilised.promise;
|
|
131
190
|
}
|
|
132
191
|
|
|
133
|
-
|
|
192
|
+
/**
|
|
193
|
+
* An {@link AbortSignal} which will trigger when this component is
|
|
194
|
+
* disconnected from the DOM.
|
|
195
|
+
*
|
|
196
|
+
* This can be passed along to asynchronous tasks such as {@link fetch}
|
|
197
|
+
* initiated by this component, which will then be aborted automatically if
|
|
198
|
+
* the component is removed from the DOM.
|
|
199
|
+
*/
|
|
134
200
|
get abortSignal(): AbortSignal {
|
|
135
201
|
return this.#abortController.signal;
|
|
136
202
|
}
|
|
203
|
+
#abortController = new AbortController();
|
|
137
204
|
|
|
138
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Register a function which will be executed whenever an instance of this
|
|
207
|
+
* class is constructed.
|
|
208
|
+
*/
|
|
209
|
+
static addInitialiser(init: (this: Component) => void) {
|
|
139
210
|
if (!Object.hasOwn(this, "initialisers")) {
|
|
140
211
|
this.initialisers = new Set();
|
|
141
212
|
}
|
|
142
213
|
this.initialisers.add(init);
|
|
143
214
|
}
|
|
144
215
|
|
|
145
|
-
|
|
216
|
+
/**
|
|
217
|
+
* A list of the attributes this element has declared.
|
|
218
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes
|
|
219
|
+
* @internal
|
|
220
|
+
*/
|
|
221
|
+
static get observedAttributes(): ReadonlyArray<string> {
|
|
146
222
|
this.finalise();
|
|
147
223
|
return this.attributeConfig.keys().toArray();
|
|
148
224
|
}
|
|
@@ -181,6 +257,7 @@ export abstract class Component
|
|
|
181
257
|
}
|
|
182
258
|
}
|
|
183
259
|
|
|
260
|
+
/** @ignore */
|
|
184
261
|
constructor() {
|
|
185
262
|
super();
|
|
186
263
|
|
|
@@ -190,7 +267,7 @@ export abstract class Component
|
|
|
190
267
|
const fields = this.constructor[Symbol.metadata]?.[reactiveFields] ?? [];
|
|
191
268
|
for (const field of fields as Iterable<string | symbol>) {
|
|
192
269
|
const sig = signalForObject(this, field, () =>
|
|
193
|
-
Signal(undefined, { equals: Object.is }),
|
|
270
|
+
Signal.from(undefined, { equals: Object.is }),
|
|
194
271
|
) as Signal.State<unknown>;
|
|
195
272
|
Object.defineProperty(this, field, {
|
|
196
273
|
get() {
|
|
@@ -230,6 +307,13 @@ export abstract class Component
|
|
|
230
307
|
}
|
|
231
308
|
}
|
|
232
309
|
|
|
310
|
+
/**
|
|
311
|
+
* This lifecycle callback is called when the component is attached to the
|
|
312
|
+
* DOM.
|
|
313
|
+
*
|
|
314
|
+
* Generally, prefer using methods with @{@link connect} decorators to
|
|
315
|
+
* overriding this method.
|
|
316
|
+
*/
|
|
233
317
|
protected connectedCallback() {
|
|
234
318
|
this.#connectedContext.dispose();
|
|
235
319
|
this.#controllers.forEach((c) => c.hostConnected?.());
|
|
@@ -261,6 +345,13 @@ export abstract class Component
|
|
|
261
345
|
this.requestUpdate();
|
|
262
346
|
}
|
|
263
347
|
|
|
348
|
+
/**
|
|
349
|
+
* This lifecycle callback is called when the component is removed from the
|
|
350
|
+
* DOM.
|
|
351
|
+
*
|
|
352
|
+
* Generally, prefer using methods with @{@link connect} decorators to
|
|
353
|
+
* overriding this method.
|
|
354
|
+
*/
|
|
264
355
|
protected disconnectedCallback() {
|
|
265
356
|
if (this.#updateSignal !== undefined) {
|
|
266
357
|
this.#updateSignalWatcher.unwatch(this.#updateSignal);
|
|
@@ -276,6 +367,9 @@ export abstract class Component
|
|
|
276
367
|
this.#connectedContext.dispose();
|
|
277
368
|
}
|
|
278
369
|
|
|
370
|
+
/**
|
|
371
|
+
* @internal
|
|
372
|
+
*/
|
|
279
373
|
setAttributeQuietly(name: string, value: string | null) {
|
|
280
374
|
this.#ignoreAttributeUpdates++;
|
|
281
375
|
if (value === null) {
|
|
@@ -286,6 +380,9 @@ export abstract class Component
|
|
|
286
380
|
this.#ignoreAttributeUpdates--;
|
|
287
381
|
}
|
|
288
382
|
|
|
383
|
+
/**
|
|
384
|
+
* @internal
|
|
385
|
+
*/
|
|
289
386
|
protected attributeChangedCallback(
|
|
290
387
|
name: string,
|
|
291
388
|
old: string | null,
|
|
@@ -301,11 +398,16 @@ export abstract class Component
|
|
|
301
398
|
}
|
|
302
399
|
}
|
|
303
400
|
|
|
304
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Typedoc does *not* like this type signature. TODO figure out how to
|
|
403
|
+
* document it properly.
|
|
404
|
+
* @ignore
|
|
405
|
+
*/
|
|
406
|
+
$signal<K extends keyof this & string, V extends this[K]>(prop: K): Signal.Computed<V> {
|
|
305
407
|
const _value = this[prop];
|
|
306
408
|
return signalForObject(this, prop, () => {
|
|
307
409
|
throw new TypeError(`Object has no reactive property ${JSON.stringify(prop)}`);
|
|
308
|
-
}) as Signal<V>;
|
|
410
|
+
}) as Signal.Computed<V>;
|
|
309
411
|
}
|
|
310
412
|
|
|
311
413
|
addController(controller: ReactiveController) {
|
|
@@ -319,6 +421,16 @@ export abstract class Component
|
|
|
319
421
|
this.#controllers.delete(controller);
|
|
320
422
|
}
|
|
321
423
|
|
|
424
|
+
/**
|
|
425
|
+
* Create the component's render root.
|
|
426
|
+
*
|
|
427
|
+
* By default, this creates a {@link ShadowRoot} and attaches it to the
|
|
428
|
+
* component.
|
|
429
|
+
*
|
|
430
|
+
* If you don't want to use a shadow DOM, you can override this method to
|
|
431
|
+
* just `return this`, which causes the component's contents to render as
|
|
432
|
+
* direct children of the component itself.
|
|
433
|
+
*/
|
|
322
434
|
protected createRenderRoot(): HTMLElement | ShadowRoot {
|
|
323
435
|
const renderRoot =
|
|
324
436
|
this.shadowRoot ??
|
|
@@ -328,6 +440,9 @@ export abstract class Component
|
|
|
328
440
|
return renderRoot;
|
|
329
441
|
}
|
|
330
442
|
|
|
443
|
+
/**
|
|
444
|
+
* Ask this component to update itself.
|
|
445
|
+
*/
|
|
331
446
|
requestUpdate(opts?: UpdateConfig) {
|
|
332
447
|
this.#dirty = true;
|
|
333
448
|
|
|
@@ -341,7 +456,9 @@ export abstract class Component
|
|
|
341
456
|
}
|
|
342
457
|
}
|
|
343
458
|
|
|
344
|
-
|
|
459
|
+
/**
|
|
460
|
+
* @internal
|
|
461
|
+
*/
|
|
345
462
|
hasRequiredProperties(): Signal.Computed<boolean> {
|
|
346
463
|
if (this.#hasRequiredProperties === undefined) {
|
|
347
464
|
const requiredProperties = (this.constructor as typeof Component).requiredProperties;
|
|
@@ -360,7 +477,9 @@ export abstract class Component
|
|
|
360
477
|
}
|
|
361
478
|
return this.#hasRequiredProperties;
|
|
362
479
|
}
|
|
480
|
+
#hasRequiredProperties?: Signal.Computed<boolean>;
|
|
363
481
|
|
|
482
|
+
/** @internal */
|
|
364
483
|
protected async performUpdate() {
|
|
365
484
|
if (!this.isConnected || !this.isUpdatePending) {
|
|
366
485
|
return;
|
|
@@ -418,6 +537,7 @@ export abstract class Component
|
|
|
418
537
|
}
|
|
419
538
|
}
|
|
420
539
|
|
|
540
|
+
/** @internal */
|
|
421
541
|
protected update() {
|
|
422
542
|
if (this.#updateSignal !== undefined) {
|
|
423
543
|
this.#updateSignalWatcher.unwatch(this.#updateSignal);
|
|
@@ -430,6 +550,10 @@ export abstract class Component
|
|
|
430
550
|
this.#updateSignal.get();
|
|
431
551
|
}
|
|
432
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Return an iterator over this component's children which are also
|
|
555
|
+
* {@link Component}s.
|
|
556
|
+
*/
|
|
433
557
|
protected findChildComponents(
|
|
434
558
|
root: Element | ShadowRoot = this.renderRoot,
|
|
435
559
|
): IteratorObject<Component> {
|
|
@@ -441,6 +565,13 @@ export abstract class Component
|
|
|
441
565
|
return Iterator.from(all).filter((el) => el instanceof Component);
|
|
442
566
|
}
|
|
443
567
|
|
|
568
|
+
/**
|
|
569
|
+
* Return a {@link Promise} which resolves when this component considers
|
|
570
|
+
* itself to have stabilised.
|
|
571
|
+
*
|
|
572
|
+
* The default implementation waits for any children which are also
|
|
573
|
+
* {@link Component}s to report that they have also stabilised.
|
|
574
|
+
*/
|
|
444
575
|
protected async stabilise(): Promise<void> {
|
|
445
576
|
// wait for children to stabilise
|
|
446
577
|
const children = this.findChildComponents();
|
|
@@ -448,29 +579,65 @@ export abstract class Component
|
|
|
448
579
|
await Promise.all(children.map((child) => child.hasStabilised)).catch(console.error);
|
|
449
580
|
}
|
|
450
581
|
|
|
582
|
+
/**
|
|
583
|
+
* This lifecycle callback is called each time an update has completed.
|
|
584
|
+
*/
|
|
451
585
|
protected updated(): void {
|
|
452
|
-
//
|
|
586
|
+
//
|
|
453
587
|
}
|
|
454
588
|
|
|
589
|
+
/**
|
|
590
|
+
* This lifecycle callback is called after the component's first update has
|
|
591
|
+
* completed, and before {@link Component.updated}.
|
|
592
|
+
*/
|
|
455
593
|
protected firstUpdated() {
|
|
456
|
-
//
|
|
457
|
-
// {@link Component.updated}.
|
|
594
|
+
//
|
|
458
595
|
}
|
|
459
596
|
|
|
597
|
+
/**
|
|
598
|
+
* This lifecycle callback is called when the component considers itself
|
|
599
|
+
* stabilised after its first update.
|
|
600
|
+
*
|
|
601
|
+
* @see {@link Component.hasStabilised}
|
|
602
|
+
*/
|
|
460
603
|
protected stabilised() {
|
|
461
|
-
//
|
|
604
|
+
//
|
|
462
605
|
}
|
|
463
606
|
|
|
607
|
+
/**
|
|
608
|
+
* This lifecycle callback is called every time a property decorated with
|
|
609
|
+
* the @{@link require} decorator has changed, but only when every property
|
|
610
|
+
* marked as such is not `undefined`.
|
|
611
|
+
*/
|
|
464
612
|
protected initialised() {
|
|
465
|
-
//
|
|
466
|
-
// such property is non-`undefined`.
|
|
613
|
+
//
|
|
467
614
|
}
|
|
468
615
|
|
|
616
|
+
/**
|
|
617
|
+
* This lifecycle callback is called the first time every property decorated
|
|
618
|
+
* with @{@link require} has been defined, and before
|
|
619
|
+
* {@link Component.initialised}.
|
|
620
|
+
*/
|
|
469
621
|
protected firstInitialised() {
|
|
470
|
-
//
|
|
471
|
-
// defined, and before {@link Component.initialised}.
|
|
622
|
+
//
|
|
472
623
|
}
|
|
473
624
|
|
|
625
|
+
/**
|
|
626
|
+
* Render the component's contents.
|
|
627
|
+
*
|
|
628
|
+
* This function should return a Lit [renderable
|
|
629
|
+
* value](https://lit.dev/docs/templates/expressions/#child-expressions),
|
|
630
|
+
* usually an {@link html} template.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* class MyComponent extends Component {
|
|
634
|
+
* protected override render() {
|
|
635
|
+
* return html`
|
|
636
|
+
* <h1>Hello Joe!</h1>
|
|
637
|
+
* `;
|
|
638
|
+
* }
|
|
639
|
+
* }
|
|
640
|
+
*/
|
|
474
641
|
protected render(): unknown {
|
|
475
642
|
return nothing;
|
|
476
643
|
}
|
|
@@ -541,6 +708,27 @@ export abstract class Component
|
|
|
541
708
|
return this.shadowRoot?.activeElement ?? this.querySelector(":focus");
|
|
542
709
|
}
|
|
543
710
|
|
|
711
|
+
/**
|
|
712
|
+
* Find the first element in the component's {@link Component.renderRoot}
|
|
713
|
+
* matching the provided CSS selector.
|
|
714
|
+
*
|
|
715
|
+
* If you need to query the component's direct child elements instead, use
|
|
716
|
+
* {@link Component.querySlot}.
|
|
717
|
+
*
|
|
718
|
+
* If you were looking for Lit's `@query` decorator, use this as a getter
|
|
719
|
+
* instead, as in the example below.
|
|
720
|
+
*
|
|
721
|
+
* @example
|
|
722
|
+
* class MyComponent extends Component {
|
|
723
|
+
* get button(): HTMLButtonElement {
|
|
724
|
+
* return this.query("button");
|
|
725
|
+
* }
|
|
726
|
+
*
|
|
727
|
+
* protected override render() {
|
|
728
|
+
* return html`<button>I am a button</button>`;
|
|
729
|
+
* }
|
|
730
|
+
* }
|
|
731
|
+
*/
|
|
544
732
|
query<El extends keyof HTMLElementTagNameMap>(selector: El): HTMLElementTagNameMap[El] | null;
|
|
545
733
|
query<El extends keyof SVGElementTagNameMap>(selector: El): SVGElementTagNameMap[El] | null;
|
|
546
734
|
query<El extends keyof MathMLElementTagNameMap>(
|
|
@@ -555,6 +743,30 @@ export abstract class Component
|
|
|
555
743
|
return this.renderRoot.querySelector(selector);
|
|
556
744
|
}
|
|
557
745
|
|
|
746
|
+
/**
|
|
747
|
+
* Find all elements in the component's {@link Component.renderRoot}
|
|
748
|
+
* matching the provided CSS selector.
|
|
749
|
+
*
|
|
750
|
+
* If you need to query the component's direct child elements instead, use
|
|
751
|
+
* {@link Component.querySlot}.
|
|
752
|
+
*
|
|
753
|
+
* If you were looking for Lit's `@queryAll` decorator, use this as a getter
|
|
754
|
+
* instead, as in the example below.
|
|
755
|
+
*
|
|
756
|
+
* @example
|
|
757
|
+
* class MyComponent extends Component {
|
|
758
|
+
* get buttons(): NodeListOf<HTMLButtonElement> {
|
|
759
|
+
* return this.queryAll("button");
|
|
760
|
+
* }
|
|
761
|
+
*
|
|
762
|
+
* protected override render() {
|
|
763
|
+
* return html`
|
|
764
|
+
* <button>I am a button</button>
|
|
765
|
+
* <button>I am also a button</button>
|
|
766
|
+
* `;
|
|
767
|
+
* }
|
|
768
|
+
* }
|
|
769
|
+
*/
|
|
558
770
|
queryAll<El extends keyof HTMLElementTagNameMap>(
|
|
559
771
|
selector: El,
|
|
560
772
|
): NodeListOf<HTMLElementTagNameMap[El]>;
|
|
@@ -573,6 +785,13 @@ export abstract class Component
|
|
|
573
785
|
return this.renderRoot.querySelectorAll(selector);
|
|
574
786
|
}
|
|
575
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Find the elements attached to a given slot on this component, according
|
|
790
|
+
* to the provided {@link QuerySlotOptions}.
|
|
791
|
+
*
|
|
792
|
+
* If you include `reactive: true` in your query, the result will be a
|
|
793
|
+
* signal which updates with the contents of the slot.
|
|
794
|
+
*/
|
|
576
795
|
querySlot(
|
|
577
796
|
options: QuerySlotOptions & { nodes: true; reactive: true },
|
|
578
797
|
): Signal.Computed<Array<Node>>;
|
|
@@ -600,11 +819,9 @@ export abstract class Component
|
|
|
600
819
|
querySlot<El extends keyof MathMLElementTagNameMap>(
|
|
601
820
|
options: QuerySlotOptions & { selector: El },
|
|
602
821
|
): Array<MathMLElementTagNameMap[El]>;
|
|
603
|
-
/** @deprecated */
|
|
604
822
|
querySlot<El extends keyof HTMLElementDeprecatedTagNameMap>(
|
|
605
823
|
options: QuerySlotOptions & { reactive: true; selector: El },
|
|
606
824
|
): Signal.Computed<Array<HTMLElementDeprecatedTagNameMap[El]>>;
|
|
607
|
-
/** @deprecated */
|
|
608
825
|
querySlot<El extends keyof HTMLElementDeprecatedTagNameMap>(
|
|
609
826
|
options: QuerySlotOptions & { selector: El },
|
|
610
827
|
): Array<HTMLElementDeprecatedTagNameMap[El]>;
|
|
@@ -637,9 +854,9 @@ export abstract class Component
|
|
|
637
854
|
return query(slotEl);
|
|
638
855
|
}
|
|
639
856
|
const sig = getOrSetSignal(this, query, () => {
|
|
640
|
-
const sig = Signal(query(slotEl), { equals: listsEqual });
|
|
857
|
+
const sig = Signal.from(query(slotEl), { equals: listsEqual });
|
|
641
858
|
this.addController(new SlotChangeController(this, slot, sig, query));
|
|
642
|
-
return sig.readOnly
|
|
859
|
+
return sig.readOnly;
|
|
643
860
|
});
|
|
644
861
|
return sig;
|
|
645
862
|
}
|
package/src/css.ts
CHANGED
|
@@ -281,7 +281,7 @@ function accessor<C extends Component, T>(
|
|
|
281
281
|
let initValue: Option<T> = None;
|
|
282
282
|
const getSig = (obj: object) =>
|
|
283
283
|
signalForObject(obj, context.name, () =>
|
|
284
|
-
Signal(initValue.unwrapExact(), {
|
|
284
|
+
Signal.from(initValue.unwrapExact(), {
|
|
285
285
|
equals: Object.is,
|
|
286
286
|
}),
|
|
287
287
|
) as Signal.State<T>;
|
|
@@ -61,7 +61,7 @@ test("@connectEffect", async () => {
|
|
|
61
61
|
methodDisposed = 0;
|
|
62
62
|
let fieldRun = 0,
|
|
63
63
|
fieldDisposed = 0;
|
|
64
|
-
const signal = Signal(1);
|
|
64
|
+
const signal = Signal.from(1);
|
|
65
65
|
|
|
66
66
|
@customElement("connect-effect-test-class")
|
|
67
67
|
class ConnectEffectTestClass extends Component {
|
|
@@ -5,7 +5,7 @@ import { Component, reactive } from "../component";
|
|
|
5
5
|
import { customElement } from "lit/decorators.js";
|
|
6
6
|
|
|
7
7
|
test("@reactive", () => {
|
|
8
|
-
const name = Signal("Joe");
|
|
8
|
+
const name = Signal.from("Joe");
|
|
9
9
|
class ReactiveTestClass {
|
|
10
10
|
@reactive get name(): string {
|
|
11
11
|
return name.get();
|
|
@@ -5,13 +5,13 @@ import type { ClassGetterDecoratorResult, ClassGetterDecoratorTarget } from "./t
|
|
|
5
5
|
|
|
6
6
|
export const reactiveFields = Symbol("reactiveFields");
|
|
7
7
|
|
|
8
|
-
const signalCache = new WeakMap<object, Record<PropertyKey, Signal<unknown>>>();
|
|
8
|
+
const signalCache = new WeakMap<object, Record<PropertyKey, Signal.Any<unknown>>>();
|
|
9
9
|
|
|
10
10
|
export function signalForObject(
|
|
11
11
|
obj: object,
|
|
12
12
|
key: string | symbol,
|
|
13
|
-
createSignal: () => Signal<unknown>,
|
|
14
|
-
): Signal<unknown> {
|
|
13
|
+
createSignal: () => Signal.Any<unknown>,
|
|
14
|
+
): Signal.Any<unknown> {
|
|
15
15
|
let sigs = signalCache.get(obj);
|
|
16
16
|
if (sigs === undefined) {
|
|
17
17
|
sigs = {};
|
|
@@ -48,7 +48,7 @@ export function reactive<C extends object, T>(
|
|
|
48
48
|
case "accessor": {
|
|
49
49
|
const getSig = (obj: object) =>
|
|
50
50
|
signalForObject(obj, context.name, () =>
|
|
51
|
-
Signal((value as ClassAccessorDecoratorTarget<unknown, T>).get.call(obj), {
|
|
51
|
+
Signal.from((value as ClassAccessorDecoratorTarget<unknown, T>).get.call(obj), {
|
|
52
52
|
equals: Object.is,
|
|
53
53
|
}),
|
|
54
54
|
) as Signal.State<T>;
|
package/src/dom.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM manipulation tools.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import { assertNever, isNullish, unreachable } from "@bodil/core/assert";
|
|
2
7
|
import { toDisposable } from "@bodil/core/disposable";
|
|
3
8
|
import type { ReactiveController, ReactiveControllerHost } from "lit";
|
|
@@ -89,6 +94,9 @@ export function visibilityObserver(
|
|
|
89
94
|
});
|
|
90
95
|
}
|
|
91
96
|
|
|
97
|
+
/**
|
|
98
|
+
* An iterator for traversing siblings of a DOM node.
|
|
99
|
+
*/
|
|
92
100
|
export class DOMIterator extends Iterator<Node> {
|
|
93
101
|
private currentNode: Node | null;
|
|
94
102
|
private goingForward = true;
|
|
@@ -120,6 +128,9 @@ export class DOMIterator extends Iterator<Node> {
|
|
|
120
128
|
}
|
|
121
129
|
}
|
|
122
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Get an iterator over the child elements of an {@link Element} or {@link DocumentFragment}.
|
|
133
|
+
*/
|
|
123
134
|
export function childElements(parent: Element | DocumentFragment | null): IteratorObject<Element> {
|
|
124
135
|
if (parent === null) {
|
|
125
136
|
return Iterator.from([]);
|
|
@@ -217,6 +228,10 @@ export function isAnimating(el: HTMLElement): boolean {
|
|
|
217
228
|
return path.some((item) => item.getAnimations().length > 0);
|
|
218
229
|
}
|
|
219
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Find the first {@link EventTarget} going up an {@link Event.composedPath}
|
|
233
|
+
* which matches the given `predicate`.
|
|
234
|
+
*/
|
|
220
235
|
export function findEventTarget<T extends EventTarget>(
|
|
221
236
|
e: Event,
|
|
222
237
|
predicate: (target: EventTarget) => target is T,
|
|
@@ -309,6 +324,16 @@ export function findAncestor(
|
|
|
309
324
|
return undefined;
|
|
310
325
|
}
|
|
311
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Test whether an {@link Element} should be considered an editor, ie. something
|
|
329
|
+
* which expects to consume `keydown` events.
|
|
330
|
+
*
|
|
331
|
+
* Use this if you have globally defined keybindings which should not be
|
|
332
|
+
* triggered when typing into an input field or other kind of editor.
|
|
333
|
+
*
|
|
334
|
+
* {@link HTMLInputElement}s, {@link HTMLTextAreaElement}s and elements with the
|
|
335
|
+
* `contenteditable` attribute set are defined as editors.
|
|
336
|
+
*/
|
|
312
337
|
export function isEditor(element: Element | null | undefined): boolean {
|
|
313
338
|
if (isNullish(element)) {
|
|
314
339
|
return false;
|
|
@@ -338,6 +363,11 @@ const scrollToItemOptionsDefault: Required<
|
|
|
338
363
|
padEnd: 0,
|
|
339
364
|
};
|
|
340
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Scroll an element into view.
|
|
368
|
+
*
|
|
369
|
+
* This is {@link Element.scrollIntoView} for advanced users.
|
|
370
|
+
*/
|
|
341
371
|
export function scrollToItem(el: HTMLElement, scrollToItemOptions: ScrollToItemOptions): void {
|
|
342
372
|
const options: Required<ScrollToItemOptions> = {
|
|
343
373
|
...scrollToItemOptionsDefault,
|
|
@@ -393,6 +423,9 @@ export function scrollToItem(el: HTMLElement, scrollToItemOptions: ScrollToItemO
|
|
|
393
423
|
|
|
394
424
|
// HasSlotController nicked largely verbatim from Shoelace
|
|
395
425
|
// https://github.com/shoelace-style/shoelace/blob/next/src/internal/slot.ts
|
|
426
|
+
/**
|
|
427
|
+
* A {@link ReactiveController} which tracks whether a slot is populated.
|
|
428
|
+
*/
|
|
396
429
|
export class HasSlotController implements ReactiveController {
|
|
397
430
|
host: ReactiveControllerHost & Element;
|
|
398
431
|
slotNames: Array<string> = [];
|