@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.
Files changed (43) hide show
  1. package/dist/component.d.ts +218 -6
  2. package/dist/component.js +154 -13
  3. package/dist/component.js.map +1 -1
  4. package/dist/css.d.ts +4 -0
  5. package/dist/css.js +4 -0
  6. package/dist/css.js.map +1 -1
  7. package/dist/decorators/attribute.js +1 -1
  8. package/dist/decorators/attribute.js.map +1 -1
  9. package/dist/decorators/connect.test.js +1 -1
  10. package/dist/decorators/connect.test.js.map +1 -1
  11. package/dist/decorators/reactive.d.ts +1 -1
  12. package/dist/decorators/reactive.js +1 -1
  13. package/dist/decorators/reactive.js.map +1 -1
  14. package/dist/decorators/reactive.test.js +1 -1
  15. package/dist/decorators/reactive.test.js.map +1 -1
  16. package/dist/dom.d.ts +32 -0
  17. package/dist/dom.js +32 -0
  18. package/dist/dom.js.map +1 -1
  19. package/dist/emitter.d.ts +8 -0
  20. package/dist/emitter.js.map +1 -1
  21. package/dist/event.d.ts +4 -0
  22. package/dist/event.js.map +1 -1
  23. package/dist/geometry.d.ts +7 -0
  24. package/dist/geometry.js +4 -0
  25. package/dist/geometry.js.map +1 -1
  26. package/dist/signal.d.ts +12 -3
  27. package/dist/signal.js +7 -1
  28. package/dist/signal.js.map +1 -1
  29. package/dist/signal.test.js +2 -2
  30. package/dist/signal.test.js.map +1 -1
  31. package/package.json +10 -8
  32. package/src/component.ts +237 -20
  33. package/src/css.ts +5 -0
  34. package/src/decorators/attribute.ts +1 -1
  35. package/src/decorators/connect.test.ts +1 -1
  36. package/src/decorators/reactive.test.ts +1 -1
  37. package/src/decorators/reactive.ts +4 -4
  38. package/src/dom.ts +33 -0
  39. package/src/emitter.ts +8 -0
  40. package/src/event.ts +4 -0
  41. package/src/geometry.ts +8 -0
  42. package/src/signal.test.ts +2 -2
  43. package/src/signal.ts +13 -2
@@ -1 +1 @@
1
- {"version":3,"file":"signal.test.js","sourceRoot":"","sources":["../src/signal.test.ts"],"names":[],"mappings":"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"}
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.9",
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.4.9",
66
- "@bodil/opt": "^0.4.2",
67
- "@bodil/signal": "^0.3.3",
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.51.0",
76
- "@typescript-eslint/parser": "^8.51.0",
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": "^54.7.0",
82
- "globals": "^16.5.0",
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
- static initialisers = new Set<() => void>();
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
- #abortController = new AbortController();
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
- static addInitialiser(init: () => void) {
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
- static get observedAttributes(): Array<string> {
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
- $signal<K extends keyof this & string, V extends this[K]>(prop: K): Signal<V> {
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
- #hasRequiredProperties?: Signal.Computed<boolean>;
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
- // called when an update has completed
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
- // called when the first update has completed, and before
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
- // called when the component has stabilised after its first update
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
- // called when a property marked `@require` has changed and every
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
- // called the first time all properties marked `@require` become
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
@@ -1,3 +1,8 @@
1
+ /**
2
+ * CSS manipulation tools.
3
+ * @module
4
+ */
5
+
1
6
  import { assert } from "@bodil/core/assert";
2
7
  import { None, Option, Some } from "@bodil/opt";
3
8
 
@@ -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> = [];