@bodil/dom 0.1.9 → 0.2.0
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 +227 -9
- package/dist/component.js +185 -38
- 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.d.ts +15 -15
- package/dist/decorators/attribute.js +28 -9
- package/dist/decorators/attribute.js.map +1 -1
- package/dist/decorators/attribute.test.js +58 -6
- package/dist/decorators/attribute.test.js.map +1 -1
- package/dist/decorators/connect.d.ts +2 -0
- package/dist/decorators/connect.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/decorators/require.test.js +1 -1
- package/dist/decorators/require.test.js.map +1 -1
- package/dist/dom.d.ts +43 -1
- package/dist/dom.js +63 -15
- package/dist/dom.js.map +1 -1
- package/dist/dom.test.d.ts +1 -0
- package/dist/dom.test.js +20 -0
- package/dist/dom.test.js.map +1 -0
- 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/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.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/dist/test.d.ts +17 -0
- package/dist/test.js +34 -0
- package/dist/test.js.map +1 -0
- package/package.json +27 -19
- package/src/component.ts +289 -53
- package/src/css.ts +5 -0
- package/src/decorators/attribute.test.ts +41 -14
- package/src/decorators/attribute.ts +57 -29
- package/src/decorators/reactive.test.ts +1 -1
- package/src/decorators/reactive.ts +4 -4
- package/src/decorators/require.test.ts +1 -1
- package/src/dom.test.ts +23 -0
- package/src/dom.ts +87 -15
- package/src/emitter.ts +8 -0
- package/src/event.ts +4 -0
- package/src/geometry.ts +8 -0
- package/src/index.ts +2 -1
- package/src/signal.test.ts +2 -2
- package/src/signal.ts +13 -2
- package/src/test.ts +38 -0
- package/src/decorators/connect.test.ts +0 -119
- package/src/decorators/connect.ts +0 -85
package/src/component.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Web component base class.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isNullish, present } from "@bodil/core/assert";
|
|
2
7
|
import { DisposableContext, toDisposable, type Disposifiable } from "@bodil/core/disposable";
|
|
3
8
|
import { Signal } from "@bodil/signal";
|
|
4
9
|
import {
|
|
@@ -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,
|
|
@@ -22,10 +29,9 @@ import {
|
|
|
22
29
|
toAttribute,
|
|
23
30
|
type AttributeConfig,
|
|
24
31
|
} from "./decorators/attribute";
|
|
25
|
-
import { connectedJobs } from "./decorators/connect";
|
|
26
32
|
import { reactiveFields, signalForObject } from "./decorators/reactive";
|
|
27
33
|
import { requiredProperties } from "./decorators/require";
|
|
28
|
-
import {
|
|
34
|
+
import { findDescendants } from "./dom";
|
|
29
35
|
import { EmitterElement } from "./emitter";
|
|
30
36
|
import { eventListener } from "./event";
|
|
31
37
|
import { scheduler } from "./scheduler";
|
|
@@ -47,12 +53,6 @@ export {
|
|
|
47
53
|
type AttributeGetterSetterOptions,
|
|
48
54
|
type AttributeType,
|
|
49
55
|
} from "./decorators/attribute";
|
|
50
|
-
export {
|
|
51
|
-
connect,
|
|
52
|
-
connectEffect,
|
|
53
|
-
type ConnectFunction,
|
|
54
|
-
type ConnectFunctionReturnValue,
|
|
55
|
-
} from "./decorators/connect";
|
|
56
56
|
export { reactive } from "./decorators/reactive";
|
|
57
57
|
export { require } from "./decorators/require";
|
|
58
58
|
export { EmitterElement } from "./emitter";
|
|
@@ -67,6 +67,8 @@ export type UpdateConfig = {
|
|
|
67
67
|
|
|
68
68
|
const finalised = Symbol("finalised");
|
|
69
69
|
|
|
70
|
+
export type Disposables = Disposifiable | Iterable<Disposifiable | undefined> | undefined | void;
|
|
71
|
+
|
|
70
72
|
export type Deps = Array<typeof HTMLElement>;
|
|
71
73
|
|
|
72
74
|
export type CSSStyleSpec = CSSResult | CSSStyleSheet | string;
|
|
@@ -77,6 +79,10 @@ function processCSSStyleSpec(spec: CSSStyleSpec) {
|
|
|
77
79
|
return getCompatibleStyle(typeof spec === "string" ? unsafeCSS(spec) : spec);
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Error thrown when a {@link Component.render} method causes infinite
|
|
84
|
+
* re-renders.
|
|
85
|
+
*/
|
|
80
86
|
export class ComponentUpdateLoopError extends Error {
|
|
81
87
|
constructor(message?: string, options?: ErrorOptions) {
|
|
82
88
|
super(message, options);
|
|
@@ -84,14 +90,49 @@ export class ComponentUpdateLoopError extends Error {
|
|
|
84
90
|
}
|
|
85
91
|
}
|
|
86
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Base class for web components.
|
|
95
|
+
*/
|
|
87
96
|
export abstract class Component
|
|
88
97
|
extends EmitterElement
|
|
89
98
|
implements ReactiveControllerHost, Disposable
|
|
90
99
|
{
|
|
100
|
+
/**
|
|
101
|
+
* Declare the web components this element will be using.
|
|
102
|
+
*
|
|
103
|
+
* This is a convenient place to make sure these web components will be
|
|
104
|
+
* defined when your component is rendered. It has no effect otherwise.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* class MyComponent extends Component {
|
|
108
|
+
* static deps: Deps = [ MySubcomponent, MyOtherSubcomponent ];
|
|
109
|
+
* }
|
|
110
|
+
*/
|
|
91
111
|
static deps: Deps = [];
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Declare CSS style sheets which will be installed for this web component.
|
|
115
|
+
*
|
|
116
|
+
* Style sheets can be either {@link CSSStyleSheet} objects,
|
|
117
|
+
* Lit template {@link CSSResult}s, or strings.
|
|
118
|
+
*
|
|
119
|
+
* If a superclass also declares the `styles` property, this list will be
|
|
120
|
+
* appended to the superclass's list.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* class MyComponent extends Component {
|
|
124
|
+
* static styles = css`
|
|
125
|
+
* :host {
|
|
126
|
+
* border: 2px solid red;
|
|
127
|
+
* }
|
|
128
|
+
* `;
|
|
129
|
+
* }
|
|
130
|
+
*/
|
|
92
131
|
static styles?: CSSStyleSpecDeclaration;
|
|
132
|
+
|
|
93
133
|
static shadowRootOptions: ShadowRootInit = { mode: "open" };
|
|
94
|
-
|
|
134
|
+
|
|
135
|
+
private static initialisers = new Set<() => void>();
|
|
95
136
|
|
|
96
137
|
/** @ignore */
|
|
97
138
|
protected static attributeConfig = new Map<string, AttributeConfig>();
|
|
@@ -106,6 +147,7 @@ export abstract class Component
|
|
|
106
147
|
|
|
107
148
|
readonly #controllers = new Set<ReactiveController>();
|
|
108
149
|
readonly #connectedContext = new DisposableContext();
|
|
150
|
+
readonly #finalCleanupContext = new DisposableContext();
|
|
109
151
|
#dirty = false;
|
|
110
152
|
#isUpdatePending = false;
|
|
111
153
|
#updateSignal?: Signal.Computed<void>;
|
|
@@ -120,29 +162,59 @@ export abstract class Component
|
|
|
120
162
|
|
|
121
163
|
readonly renderRoot: ShadowRoot | HTMLElement = this.createRenderRoot();
|
|
122
164
|
|
|
165
|
+
/**
|
|
166
|
+
* True if this component had scheduled an update which has not yet started.
|
|
167
|
+
*/
|
|
123
168
|
get isUpdatePending(): boolean {
|
|
124
169
|
return this.#isUpdatePending;
|
|
125
170
|
}
|
|
171
|
+
|
|
126
172
|
get updateComplete(): Promise<boolean> {
|
|
127
173
|
return this.#updateResult.promise;
|
|
128
174
|
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* A {@link Promise} which resolves when this component has completed its
|
|
178
|
+
* first update and considers itself stabilised, according to the
|
|
179
|
+
* {@link Component.stabilise} method.
|
|
180
|
+
*
|
|
181
|
+
* By default, a component considers itself stabilised when all of its
|
|
182
|
+
* children which are also {@link Component}s are reporting as stabilised.
|
|
183
|
+
*/
|
|
129
184
|
get hasStabilised(): Promise<void> {
|
|
130
185
|
return this.#stabilised.promise;
|
|
131
186
|
}
|
|
132
187
|
|
|
133
|
-
|
|
188
|
+
/**
|
|
189
|
+
* An {@link AbortSignal} which will trigger when this component is
|
|
190
|
+
* disconnected from the DOM.
|
|
191
|
+
*
|
|
192
|
+
* This can be passed along to asynchronous tasks such as {@link fetch}
|
|
193
|
+
* initiated by this component, which will then be aborted automatically if
|
|
194
|
+
* the component is removed from the DOM.
|
|
195
|
+
*/
|
|
134
196
|
get abortSignal(): AbortSignal {
|
|
135
197
|
return this.#abortController.signal;
|
|
136
198
|
}
|
|
199
|
+
#abortController = new AbortController();
|
|
137
200
|
|
|
138
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Register a function which will be executed whenever an instance of this
|
|
203
|
+
* class is constructed.
|
|
204
|
+
*/
|
|
205
|
+
static addInitialiser(init: (this: Component) => void) {
|
|
139
206
|
if (!Object.hasOwn(this, "initialisers")) {
|
|
140
207
|
this.initialisers = new Set();
|
|
141
208
|
}
|
|
142
209
|
this.initialisers.add(init);
|
|
143
210
|
}
|
|
144
211
|
|
|
145
|
-
|
|
212
|
+
/**
|
|
213
|
+
* A list of the attributes this element has declared.
|
|
214
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes
|
|
215
|
+
* @internal
|
|
216
|
+
*/
|
|
217
|
+
static get observedAttributes(): ReadonlyArray<string> {
|
|
146
218
|
this.finalise();
|
|
147
219
|
return this.attributeConfig.keys().toArray();
|
|
148
220
|
}
|
|
@@ -181,6 +253,7 @@ export abstract class Component
|
|
|
181
253
|
}
|
|
182
254
|
}
|
|
183
255
|
|
|
256
|
+
/** @ignore */
|
|
184
257
|
constructor() {
|
|
185
258
|
super();
|
|
186
259
|
|
|
@@ -190,7 +263,7 @@ export abstract class Component
|
|
|
190
263
|
const fields = this.constructor[Symbol.metadata]?.[reactiveFields] ?? [];
|
|
191
264
|
for (const field of fields as Iterable<string | symbol>) {
|
|
192
265
|
const sig = signalForObject(this, field, () =>
|
|
193
|
-
Signal(undefined, { equals: Object.is }),
|
|
266
|
+
Signal.from(undefined, { equals: Object.is }),
|
|
194
267
|
) as Signal.State<unknown>;
|
|
195
268
|
Object.defineProperty(this, field, {
|
|
196
269
|
get() {
|
|
@@ -230,20 +303,12 @@ export abstract class Component
|
|
|
230
303
|
}
|
|
231
304
|
}
|
|
232
305
|
|
|
233
|
-
|
|
306
|
+
private connectedCallback() {
|
|
234
307
|
this.#connectedContext.dispose();
|
|
235
308
|
this.#controllers.forEach((c) => c.hostConnected?.());
|
|
236
309
|
this.#rootPart?.setConnected(true);
|
|
237
310
|
|
|
238
|
-
|
|
239
|
-
const result = job.call(this);
|
|
240
|
-
const items = isNullish(result)
|
|
241
|
-
? Iterator.from<Disposifiable>([])
|
|
242
|
-
: isIterable(result)
|
|
243
|
-
? Iterator.from(result)
|
|
244
|
-
: Iterator.from([result]);
|
|
245
|
-
items.filter((i) => i !== undefined).forEach(this.useWhileConnected.bind(this));
|
|
246
|
-
}
|
|
311
|
+
this.connected();
|
|
247
312
|
|
|
248
313
|
this.useWhileConnected(
|
|
249
314
|
Signal.effect(() => {
|
|
@@ -261,12 +326,13 @@ export abstract class Component
|
|
|
261
326
|
this.requestUpdate();
|
|
262
327
|
}
|
|
263
328
|
|
|
264
|
-
|
|
329
|
+
private disconnectedCallback() {
|
|
265
330
|
if (this.#updateSignal !== undefined) {
|
|
266
331
|
this.#updateSignalWatcher.unwatch(this.#updateSignal);
|
|
267
332
|
this.#updateSignal = undefined;
|
|
268
333
|
}
|
|
269
334
|
this.#isUpdatePending = false;
|
|
335
|
+
this.disconnected();
|
|
270
336
|
this.#abortController.abort(
|
|
271
337
|
new DOMException(`${this.tagName} element disconnected`, "AbortError"),
|
|
272
338
|
);
|
|
@@ -276,6 +342,9 @@ export abstract class Component
|
|
|
276
342
|
this.#connectedContext.dispose();
|
|
277
343
|
}
|
|
278
344
|
|
|
345
|
+
/**
|
|
346
|
+
* @internal
|
|
347
|
+
*/
|
|
279
348
|
setAttributeQuietly(name: string, value: string | null) {
|
|
280
349
|
this.#ignoreAttributeUpdates++;
|
|
281
350
|
if (value === null) {
|
|
@@ -286,6 +355,9 @@ export abstract class Component
|
|
|
286
355
|
this.#ignoreAttributeUpdates--;
|
|
287
356
|
}
|
|
288
357
|
|
|
358
|
+
/**
|
|
359
|
+
* @internal
|
|
360
|
+
*/
|
|
289
361
|
protected attributeChangedCallback(
|
|
290
362
|
name: string,
|
|
291
363
|
old: string | null,
|
|
@@ -297,15 +369,23 @@ export abstract class Component
|
|
|
297
369
|
const attrConfig = (this.constructor as typeof Component).attributeConfig.get(name);
|
|
298
370
|
if (attrConfig !== undefined) {
|
|
299
371
|
const value = fromAttribute(valueString, attrConfig.type);
|
|
300
|
-
|
|
372
|
+
try {
|
|
373
|
+
this[attrConfig.property as keyof this] = value as any;
|
|
374
|
+
// Silently swallow any write errors.
|
|
375
|
+
} catch (_e) {}
|
|
301
376
|
}
|
|
302
377
|
}
|
|
303
378
|
|
|
304
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Typedoc does *not* like this type signature. TODO figure out how to
|
|
381
|
+
* document it properly.
|
|
382
|
+
* @ignore
|
|
383
|
+
*/
|
|
384
|
+
$signal<K extends keyof this & string, V extends this[K]>(prop: K): Signal.Computed<V> {
|
|
305
385
|
const _value = this[prop];
|
|
306
386
|
return signalForObject(this, prop, () => {
|
|
307
387
|
throw new TypeError(`Object has no reactive property ${JSON.stringify(prop)}`);
|
|
308
|
-
}) as Signal<V>;
|
|
388
|
+
}) as Signal.Computed<V>;
|
|
309
389
|
}
|
|
310
390
|
|
|
311
391
|
addController(controller: ReactiveController) {
|
|
@@ -319,6 +399,16 @@ export abstract class Component
|
|
|
319
399
|
this.#controllers.delete(controller);
|
|
320
400
|
}
|
|
321
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Create the component's render root.
|
|
404
|
+
*
|
|
405
|
+
* By default, this creates a {@link ShadowRoot} and attaches it to the
|
|
406
|
+
* component.
|
|
407
|
+
*
|
|
408
|
+
* If you don't want to use a shadow DOM, you can override this method to
|
|
409
|
+
* just `return this`, which causes the component's contents to render as
|
|
410
|
+
* direct children of the component itself.
|
|
411
|
+
*/
|
|
322
412
|
protected createRenderRoot(): HTMLElement | ShadowRoot {
|
|
323
413
|
const renderRoot =
|
|
324
414
|
this.shadowRoot ??
|
|
@@ -328,6 +418,9 @@ export abstract class Component
|
|
|
328
418
|
return renderRoot;
|
|
329
419
|
}
|
|
330
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Ask this component to update itself.
|
|
423
|
+
*/
|
|
331
424
|
requestUpdate(opts?: UpdateConfig) {
|
|
332
425
|
this.#dirty = true;
|
|
333
426
|
|
|
@@ -341,7 +434,9 @@ export abstract class Component
|
|
|
341
434
|
}
|
|
342
435
|
}
|
|
343
436
|
|
|
344
|
-
|
|
437
|
+
/**
|
|
438
|
+
* @internal
|
|
439
|
+
*/
|
|
345
440
|
hasRequiredProperties(): Signal.Computed<boolean> {
|
|
346
441
|
if (this.#hasRequiredProperties === undefined) {
|
|
347
442
|
const requiredProperties = (this.constructor as typeof Component).requiredProperties;
|
|
@@ -349,9 +444,7 @@ export abstract class Component
|
|
|
349
444
|
() => {
|
|
350
445
|
return requiredProperties.size === 0
|
|
351
446
|
? true
|
|
352
|
-
: requiredProperties
|
|
353
|
-
.keys()
|
|
354
|
-
.every((key) => (this as any)[key] !== undefined);
|
|
447
|
+
: requiredProperties.keys().every((key) => !isNullish((this as any)[key]));
|
|
355
448
|
},
|
|
356
449
|
// every signal update is relevant, even if the signal value doesn't
|
|
357
450
|
// change, because the required values probably did.
|
|
@@ -360,7 +453,9 @@ export abstract class Component
|
|
|
360
453
|
}
|
|
361
454
|
return this.#hasRequiredProperties;
|
|
362
455
|
}
|
|
456
|
+
#hasRequiredProperties?: Signal.Computed<boolean>;
|
|
363
457
|
|
|
458
|
+
/** @internal */
|
|
364
459
|
protected async performUpdate() {
|
|
365
460
|
if (!this.isConnected || !this.isUpdatePending) {
|
|
366
461
|
return;
|
|
@@ -418,6 +513,7 @@ export abstract class Component
|
|
|
418
513
|
}
|
|
419
514
|
}
|
|
420
515
|
|
|
516
|
+
/** @internal */
|
|
421
517
|
protected update() {
|
|
422
518
|
if (this.#updateSignal !== undefined) {
|
|
423
519
|
this.#updateSignalWatcher.unwatch(this.#updateSignal);
|
|
@@ -430,17 +526,23 @@ export abstract class Component
|
|
|
430
526
|
this.#updateSignal.get();
|
|
431
527
|
}
|
|
432
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Return an iterator over this component's children which are also
|
|
531
|
+
* {@link Component}s.
|
|
532
|
+
*/
|
|
433
533
|
protected findChildComponents(
|
|
434
534
|
root: Element | ShadowRoot = this.renderRoot,
|
|
435
535
|
): IteratorObject<Component> {
|
|
436
|
-
|
|
437
|
-
const top = [...all];
|
|
438
|
-
for (const el of top) {
|
|
439
|
-
all = [...all, ...this.findChildComponents(el)];
|
|
440
|
-
}
|
|
441
|
-
return Iterator.from(all).filter((el) => el instanceof Component);
|
|
536
|
+
return findDescendants(root, (el) => el instanceof Component);
|
|
442
537
|
}
|
|
443
538
|
|
|
539
|
+
/**
|
|
540
|
+
* Return a {@link Promise} which resolves when this component considers
|
|
541
|
+
* itself to have stabilised.
|
|
542
|
+
*
|
|
543
|
+
* The default implementation waits for any children which are also
|
|
544
|
+
* {@link Component}s to report that they have also stabilised.
|
|
545
|
+
*/
|
|
444
546
|
protected async stabilise(): Promise<void> {
|
|
445
547
|
// wait for children to stabilise
|
|
446
548
|
const children = this.findChildComponents();
|
|
@@ -448,41 +550,112 @@ export abstract class Component
|
|
|
448
550
|
await Promise.all(children.map((child) => child.hasStabilised)).catch(console.error);
|
|
449
551
|
}
|
|
450
552
|
|
|
553
|
+
/**
|
|
554
|
+
* This lifecycle callback is called each time this component is connected
|
|
555
|
+
* to the DOM.
|
|
556
|
+
*/
|
|
557
|
+
protected connected(): void {
|
|
558
|
+
//
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* This lifecycle callback is called each time this component is
|
|
563
|
+
* disconnected from the DOM.
|
|
564
|
+
*/
|
|
565
|
+
protected disconnected(): void {
|
|
566
|
+
//
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* This lifecycle callback is called each time an update has completed.
|
|
571
|
+
*/
|
|
451
572
|
protected updated(): void {
|
|
452
|
-
//
|
|
573
|
+
//
|
|
453
574
|
}
|
|
454
575
|
|
|
576
|
+
/**
|
|
577
|
+
* This lifecycle callback is called after the component's first update has
|
|
578
|
+
* completed, and before {@link Component.updated}.
|
|
579
|
+
*/
|
|
455
580
|
protected firstUpdated() {
|
|
456
|
-
//
|
|
457
|
-
// {@link Component.updated}.
|
|
581
|
+
//
|
|
458
582
|
}
|
|
459
583
|
|
|
584
|
+
/**
|
|
585
|
+
* This lifecycle callback is called when the component considers itself
|
|
586
|
+
* stabilised after its first update.
|
|
587
|
+
*
|
|
588
|
+
* @see {@link Component.hasStabilised}
|
|
589
|
+
*/
|
|
460
590
|
protected stabilised() {
|
|
461
|
-
//
|
|
591
|
+
//
|
|
462
592
|
}
|
|
463
593
|
|
|
594
|
+
/**
|
|
595
|
+
* This lifecycle callback is called every time a property decorated with
|
|
596
|
+
* the @{@link require} decorator has changed, but only when every property
|
|
597
|
+
* marked as such is not `undefined`.
|
|
598
|
+
*/
|
|
464
599
|
protected initialised() {
|
|
465
|
-
//
|
|
466
|
-
// such property is non-`undefined`.
|
|
600
|
+
//
|
|
467
601
|
}
|
|
468
602
|
|
|
603
|
+
/**
|
|
604
|
+
* This lifecycle callback is called the first time every property decorated
|
|
605
|
+
* with @{@link require} has been defined, and before
|
|
606
|
+
* {@link Component.initialised}.
|
|
607
|
+
*/
|
|
469
608
|
protected firstInitialised() {
|
|
470
|
-
//
|
|
471
|
-
// defined, and before {@link Component.initialised}.
|
|
609
|
+
//
|
|
472
610
|
}
|
|
473
611
|
|
|
612
|
+
/**
|
|
613
|
+
* Render the component's contents.
|
|
614
|
+
*
|
|
615
|
+
* This function should return a Lit [renderable
|
|
616
|
+
* value](https://lit.dev/docs/templates/expressions/#child-expressions),
|
|
617
|
+
* usually an {@link html} template.
|
|
618
|
+
*
|
|
619
|
+
* @example
|
|
620
|
+
* class MyComponent extends Component {
|
|
621
|
+
* protected override render() {
|
|
622
|
+
* return html`
|
|
623
|
+
* <h1>Hello Joe!</h1>
|
|
624
|
+
* `;
|
|
625
|
+
* }
|
|
626
|
+
* }
|
|
627
|
+
*/
|
|
474
628
|
protected render(): unknown {
|
|
475
629
|
return nothing;
|
|
476
630
|
}
|
|
477
631
|
|
|
478
|
-
[Symbol.dispose]() {
|
|
632
|
+
[Symbol.dispose](): void {
|
|
479
633
|
for (const child of this.findChildComponents()) {
|
|
480
634
|
child[Symbol.dispose]();
|
|
481
635
|
}
|
|
636
|
+
this.remove();
|
|
482
637
|
this.disconnectedCallback();
|
|
483
638
|
(this.#rootPart as any)?._$clear?.();
|
|
484
639
|
this.#rootPart = undefined;
|
|
485
|
-
this.
|
|
640
|
+
this.#finalCleanupContext.dispose();
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Register a {@link Disposable} for automatic disposal when this component
|
|
645
|
+
* is disposed.
|
|
646
|
+
*/
|
|
647
|
+
use(disposifiable: undefined): undefined;
|
|
648
|
+
use(disposifiable: null): null;
|
|
649
|
+
use(disposifiable: Disposifiable): Disposable;
|
|
650
|
+
use(disposifiable: Disposifiable | undefined): Disposable | undefined;
|
|
651
|
+
use(disposifiable: Disposifiable | null | undefined): Disposable | null | undefined;
|
|
652
|
+
use(disposifiable: Disposifiable | null | undefined): Disposable | null | undefined {
|
|
653
|
+
if (isNullish(disposifiable)) {
|
|
654
|
+
return disposifiable;
|
|
655
|
+
}
|
|
656
|
+
const disposable = toDisposable(disposifiable);
|
|
657
|
+
this.#finalCleanupContext.use(disposable);
|
|
658
|
+
return disposable;
|
|
486
659
|
}
|
|
487
660
|
|
|
488
661
|
/**
|
|
@@ -501,6 +674,11 @@ export abstract class Component
|
|
|
501
674
|
* this.useWhileConnected(this.on("click", this.handleClick.bind(this)));
|
|
502
675
|
* }
|
|
503
676
|
*/
|
|
677
|
+
useWhileConnected(
|
|
678
|
+
disposifiable: Disposifiable,
|
|
679
|
+
secondDisposable: Disposifiable,
|
|
680
|
+
...disposifiables: Array<Disposifiable>
|
|
681
|
+
): Array<Disposable>;
|
|
504
682
|
useWhileConnected(disposifiable: undefined): undefined;
|
|
505
683
|
useWhileConnected(disposifiable: null): null;
|
|
506
684
|
useWhileConnected(disposifiable: Disposifiable): Disposable;
|
|
@@ -510,7 +688,15 @@ export abstract class Component
|
|
|
510
688
|
): Disposable | null | undefined;
|
|
511
689
|
useWhileConnected(
|
|
512
690
|
disposifiable: Disposifiable | null | undefined,
|
|
513
|
-
|
|
691
|
+
...disposifiables: Array<Disposifiable>
|
|
692
|
+
): Array<Disposable> | Disposable | null | undefined {
|
|
693
|
+
if (disposifiables.length > 0) {
|
|
694
|
+
return [disposifiable, ...disposifiables].map((d) => {
|
|
695
|
+
const disposable = toDisposable(present(d));
|
|
696
|
+
this.#connectedContext.use(disposable);
|
|
697
|
+
return disposable;
|
|
698
|
+
});
|
|
699
|
+
}
|
|
514
700
|
if (isNullish(disposifiable)) {
|
|
515
701
|
return disposifiable;
|
|
516
702
|
}
|
|
@@ -541,6 +727,27 @@ export abstract class Component
|
|
|
541
727
|
return this.shadowRoot?.activeElement ?? this.querySelector(":focus");
|
|
542
728
|
}
|
|
543
729
|
|
|
730
|
+
/**
|
|
731
|
+
* Find the first element in the component's {@link Component.renderRoot}
|
|
732
|
+
* matching the provided CSS selector.
|
|
733
|
+
*
|
|
734
|
+
* If you need to query the component's direct child elements instead, use
|
|
735
|
+
* {@link Component.querySlot}.
|
|
736
|
+
*
|
|
737
|
+
* If you were looking for Lit's `@query` decorator, use this as a getter
|
|
738
|
+
* instead, as in the example below.
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* class MyComponent extends Component {
|
|
742
|
+
* get button(): HTMLButtonElement {
|
|
743
|
+
* return this.query("button");
|
|
744
|
+
* }
|
|
745
|
+
*
|
|
746
|
+
* protected override render() {
|
|
747
|
+
* return html`<button>I am a button</button>`;
|
|
748
|
+
* }
|
|
749
|
+
* }
|
|
750
|
+
*/
|
|
544
751
|
query<El extends keyof HTMLElementTagNameMap>(selector: El): HTMLElementTagNameMap[El] | null;
|
|
545
752
|
query<El extends keyof SVGElementTagNameMap>(selector: El): SVGElementTagNameMap[El] | null;
|
|
546
753
|
query<El extends keyof MathMLElementTagNameMap>(
|
|
@@ -555,6 +762,30 @@ export abstract class Component
|
|
|
555
762
|
return this.renderRoot.querySelector(selector);
|
|
556
763
|
}
|
|
557
764
|
|
|
765
|
+
/**
|
|
766
|
+
* Find all elements in the component's {@link Component.renderRoot}
|
|
767
|
+
* matching the provided CSS selector.
|
|
768
|
+
*
|
|
769
|
+
* If you need to query the component's direct child elements instead, use
|
|
770
|
+
* {@link Component.querySlot}.
|
|
771
|
+
*
|
|
772
|
+
* If you were looking for Lit's `@queryAll` decorator, use this as a getter
|
|
773
|
+
* instead, as in the example below.
|
|
774
|
+
*
|
|
775
|
+
* @example
|
|
776
|
+
* class MyComponent extends Component {
|
|
777
|
+
* get buttons(): NodeListOf<HTMLButtonElement> {
|
|
778
|
+
* return this.queryAll("button");
|
|
779
|
+
* }
|
|
780
|
+
*
|
|
781
|
+
* protected override render() {
|
|
782
|
+
* return html`
|
|
783
|
+
* <button>I am a button</button>
|
|
784
|
+
* <button>I am also a button</button>
|
|
785
|
+
* `;
|
|
786
|
+
* }
|
|
787
|
+
* }
|
|
788
|
+
*/
|
|
558
789
|
queryAll<El extends keyof HTMLElementTagNameMap>(
|
|
559
790
|
selector: El,
|
|
560
791
|
): NodeListOf<HTMLElementTagNameMap[El]>;
|
|
@@ -573,6 +804,13 @@ export abstract class Component
|
|
|
573
804
|
return this.renderRoot.querySelectorAll(selector);
|
|
574
805
|
}
|
|
575
806
|
|
|
807
|
+
/**
|
|
808
|
+
* Find the elements attached to a given slot on this component, according
|
|
809
|
+
* to the provided {@link QuerySlotOptions}.
|
|
810
|
+
*
|
|
811
|
+
* If you include `reactive: true` in your query, the result will be a
|
|
812
|
+
* signal which updates with the contents of the slot.
|
|
813
|
+
*/
|
|
576
814
|
querySlot(
|
|
577
815
|
options: QuerySlotOptions & { nodes: true; reactive: true },
|
|
578
816
|
): Signal.Computed<Array<Node>>;
|
|
@@ -600,11 +838,9 @@ export abstract class Component
|
|
|
600
838
|
querySlot<El extends keyof MathMLElementTagNameMap>(
|
|
601
839
|
options: QuerySlotOptions & { selector: El },
|
|
602
840
|
): Array<MathMLElementTagNameMap[El]>;
|
|
603
|
-
/** @deprecated */
|
|
604
841
|
querySlot<El extends keyof HTMLElementDeprecatedTagNameMap>(
|
|
605
842
|
options: QuerySlotOptions & { reactive: true; selector: El },
|
|
606
843
|
): Signal.Computed<Array<HTMLElementDeprecatedTagNameMap[El]>>;
|
|
607
|
-
/** @deprecated */
|
|
608
844
|
querySlot<El extends keyof HTMLElementDeprecatedTagNameMap>(
|
|
609
845
|
options: QuerySlotOptions & { selector: El },
|
|
610
846
|
): Array<HTMLElementDeprecatedTagNameMap[El]>;
|
|
@@ -637,9 +873,9 @@ export abstract class Component
|
|
|
637
873
|
return query(slotEl);
|
|
638
874
|
}
|
|
639
875
|
const sig = getOrSetSignal(this, query, () => {
|
|
640
|
-
const sig = Signal(query(slotEl), { equals: listsEqual });
|
|
876
|
+
const sig = Signal.from(query(slotEl), { equals: listsEqual });
|
|
641
877
|
this.addController(new SlotChangeController(this, slot, sig, query));
|
|
642
|
-
return sig.readOnly
|
|
878
|
+
return sig.readOnly;
|
|
643
879
|
});
|
|
644
880
|
return sig;
|
|
645
881
|
}
|