@studiometa/js-toolkit 4.0.0-alpha.0 → 4.0.0-alpha.2

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 (126) hide show
  1. package/README.md +10 -0
  2. package/dist/utils/index.d.ts +2 -2
  3. package/package.json +5 -2
  4. package/skills/studiometa-js-toolkit/SKILL.md +87 -0
  5. package/skills/studiometa-js-toolkit/references/api/configuration.md +169 -0
  6. package/skills/studiometa-js-toolkit/references/api/context/createContext.md +46 -0
  7. package/skills/studiometa-js-toolkit/references/api/context/createGroup.md +67 -0
  8. package/skills/studiometa-js-toolkit/references/api/context/index.md +69 -0
  9. package/skills/studiometa-js-toolkit/references/api/context/injectContext.md +58 -0
  10. package/skills/studiometa-js-toolkit/references/api/context/injectContextSync.md +44 -0
  11. package/skills/studiometa-js-toolkit/references/api/context/provideContext.md +61 -0
  12. package/skills/studiometa-js-toolkit/references/api/context/provideRootContext.md +42 -0
  13. package/skills/studiometa-js-toolkit/references/api/context/signal.md +85 -0
  14. package/skills/studiometa-js-toolkit/references/api/context/subscribeContext.md +71 -0
  15. package/skills/studiometa-js-toolkit/references/api/decorators/children.md +92 -0
  16. package/skills/studiometa-js-toolkit/references/api/decorators/component.md +75 -0
  17. package/skills/studiometa-js-toolkit/references/api/decorators/index.md +41 -0
  18. package/skills/studiometa-js-toolkit/references/api/decorators/inject.md +73 -0
  19. package/skills/studiometa-js-toolkit/references/api/decorators/on.md +149 -0
  20. package/skills/studiometa-js-toolkit/references/api/decorators/provide.md +77 -0
  21. package/skills/studiometa-js-toolkit/references/api/decorators/read-write.md +93 -0
  22. package/skills/studiometa-js-toolkit/references/api/diagnostics/DIAGNOSTICS.md +137 -0
  23. package/skills/studiometa-js-toolkit/references/api/diagnostics/EVENTS.md +45 -0
  24. package/skills/studiometa-js-toolkit/references/api/diagnostics/index.md +58 -0
  25. package/skills/studiometa-js-toolkit/references/api/diagnostics/reportDiagnostic.md +67 -0
  26. package/skills/studiometa-js-toolkit/references/api/diagnostics/warn.md +54 -0
  27. package/skills/studiometa-js-toolkit/references/api/dom/breakpoints.md +81 -0
  28. package/skills/studiometa-js-toolkit/references/api/dom/domUpdate.md +70 -0
  29. package/skills/studiometa-js-toolkit/references/api/dom/emitExtendable.md +68 -0
  30. package/skills/studiometa-js-toolkit/references/api/dom/index.md +26 -0
  31. package/skills/studiometa-js-toolkit/references/api/dom/swap.md +105 -0
  32. package/skills/studiometa-js-toolkit/references/api/dom/watchAttributeNamespace.md +96 -0
  33. package/skills/studiometa-js-toolkit/references/api/dom/watchAttributes.md +56 -0
  34. package/skills/studiometa-js-toolkit/references/api/dom/whenDOMSettled.md +41 -0
  35. package/skills/studiometa-js-toolkit/references/api/html/data-component.md +105 -0
  36. package/skills/studiometa-js-toolkit/references/api/html/data-mount.md +69 -0
  37. package/skills/studiometa-js-toolkit/references/api/html/data-option.md +106 -0
  38. package/skills/studiometa-js-toolkit/references/api/html/data-ref.md +63 -0
  39. package/skills/studiometa-js-toolkit/references/api/html/index.md +32 -0
  40. package/skills/studiometa-js-toolkit/references/api/index.md +73 -0
  41. package/skills/studiometa-js-toolkit/references/api/instance-events.md +75 -0
  42. package/skills/studiometa-js-toolkit/references/api/instance-methods.md +267 -0
  43. package/skills/studiometa-js-toolkit/references/api/instance-properties.md +90 -0
  44. package/skills/studiometa-js-toolkit/references/api/methods-hooks-events.md +149 -0
  45. package/skills/studiometa-js-toolkit/references/api/methods-hooks-lifecycle.md +90 -0
  46. package/skills/studiometa-js-toolkit/references/api/methods-hooks-options.md +96 -0
  47. package/skills/studiometa-js-toolkit/references/api/methods-hooks-services.md +164 -0
  48. package/skills/studiometa-js-toolkit/references/api/registry/defineManifest.md +57 -0
  49. package/skills/studiometa-js-toolkit/references/api/registry/fromMetaGlob.md +33 -0
  50. package/skills/studiometa-js-toolkit/references/api/registry/fromWebpackContext.md +37 -0
  51. package/skills/studiometa-js-toolkit/references/api/registry/getInstance.md +52 -0
  52. package/skills/studiometa-js-toolkit/references/api/registry/getInstances.md +48 -0
  53. package/skills/studiometa-js-toolkit/references/api/registry/getMountedInstances.md +39 -0
  54. package/skills/studiometa-js-toolkit/references/api/registry/getUnmountedInstances.md +46 -0
  55. package/skills/studiometa-js-toolkit/references/api/registry/index.md +49 -0
  56. package/skills/studiometa-js-toolkit/references/api/registry/registerComponent.md +93 -0
  57. package/skills/studiometa-js-toolkit/references/api/registry/registerComponents.md +37 -0
  58. package/skills/studiometa-js-toolkit/references/api/registry/registerManifest.md +72 -0
  59. package/skills/studiometa-js-toolkit/references/api/scheduler/defaultScheduler.md +104 -0
  60. package/skills/studiometa-js-toolkit/references/api/scheduler/index.md +27 -0
  61. package/skills/studiometa-js-toolkit/references/api/scheduler/nextFrame.md +31 -0
  62. package/skills/studiometa-js-toolkit/references/api/scheduler/viewTransition.md +53 -0
  63. package/skills/studiometa-js-toolkit/references/api/services/createService.md +95 -0
  64. package/skills/studiometa-js-toolkit/references/api/services/createServiceMixin.md +81 -0
  65. package/skills/studiometa-js-toolkit/references/api/services/index.md +94 -0
  66. package/skills/studiometa-js-toolkit/references/api/services/mixins.md +148 -0
  67. package/skills/studiometa-js-toolkit/references/api/services/perTarget.md +65 -0
  68. package/skills/studiometa-js-toolkit/references/api/services/toggle.md +80 -0
  69. package/skills/studiometa-js-toolkit/references/api/services/until.md +44 -0
  70. package/skills/studiometa-js-toolkit/references/api/services/useBreakpoint.md +42 -0
  71. package/skills/studiometa-js-toolkit/references/api/services/useDrag.md +119 -0
  72. package/skills/studiometa-js-toolkit/references/api/services/useInView.md +64 -0
  73. package/skills/studiometa-js-toolkit/references/api/services/useKey.md +105 -0
  74. package/skills/studiometa-js-toolkit/references/api/services/useMediaQuery.md +45 -0
  75. package/skills/studiometa-js-toolkit/references/api/services/useMutation.md +69 -0
  76. package/skills/studiometa-js-toolkit/references/api/services/usePointer.md +91 -0
  77. package/skills/studiometa-js-toolkit/references/api/services/usePrefersReducedMotion.md +42 -0
  78. package/skills/studiometa-js-toolkit/references/api/services/useRaf.md +91 -0
  79. package/skills/studiometa-js-toolkit/references/api/services/useResize.md +60 -0
  80. package/skills/studiometa-js-toolkit/references/api/services/useScroll.md +88 -0
  81. package/skills/studiometa-js-toolkit/references/api/services/useScrollProgress.md +88 -0
  82. package/skills/studiometa-js-toolkit/references/api/services/useWindowScroll.md +25 -0
  83. package/skills/studiometa-js-toolkit/references/api/services/useWindowSize.md +25 -0
  84. package/skills/studiometa-js-toolkit/references/api/storage/createStorage.md +126 -0
  85. package/skills/studiometa-js-toolkit/references/api/storage/index.md +33 -0
  86. package/skills/studiometa-js-toolkit/references/api/storage/presets.md +58 -0
  87. package/skills/studiometa-js-toolkit/references/api/storage/providers.md +109 -0
  88. package/skills/studiometa-js-toolkit/references/api/test/index.md +214 -0
  89. package/skills/studiometa-js-toolkit/references/guide/concepts/attribute-grammar.md +93 -0
  90. package/skills/studiometa-js-toolkit/references/guide/concepts/mutation-engine.md +71 -0
  91. package/skills/studiometa-js-toolkit/references/guide/concepts/philosophy.md +78 -0
  92. package/skills/studiometa-js-toolkit/references/guide/concepts/the-registry.md +110 -0
  93. package/skills/studiometa-js-toolkit/references/guide/going-further/autoloading.md +104 -0
  94. package/skills/studiometa-js-toolkit/references/guide/going-further/handling-diagnostics.md +104 -0
  95. package/skills/studiometa-js-toolkit/references/guide/going-further/mount-strategies.md +95 -0
  96. package/skills/studiometa-js-toolkit/references/guide/going-further/scheduling-work.md +124 -0
  97. package/skills/studiometa-js-toolkit/references/guide/going-further/sharing-state.md +218 -0
  98. package/skills/studiometa-js-toolkit/references/guide/going-further/swapping-content.md +92 -0
  99. package/skills/studiometa-js-toolkit/references/guide/going-further/testing-components.md +137 -0
  100. package/skills/studiometa-js-toolkit/references/guide/going-further/typing-components.md +190 -0
  101. package/skills/studiometa-js-toolkit/references/guide/going-further/using-decorators.md +168 -0
  102. package/skills/studiometa-js-toolkit/references/guide/going-further/using-storage.md +125 -0
  103. package/skills/studiometa-js-toolkit/references/guide/index.md +163 -0
  104. package/skills/studiometa-js-toolkit/references/guide/introduction/installation.md +144 -0
  105. package/skills/studiometa-js-toolkit/references/guide/introduction/lifecycle-hooks.md +174 -0
  106. package/skills/studiometa-js-toolkit/references/guide/introduction/managing-components.md +229 -0
  107. package/skills/studiometa-js-toolkit/references/guide/introduction/managing-options.md +235 -0
  108. package/skills/studiometa-js-toolkit/references/guide/introduction/managing-refs.md +174 -0
  109. package/skills/studiometa-js-toolkit/references/guide/introduction/using-services.md +218 -0
  110. package/skills/studiometa-js-toolkit/references/guide/introduction/working-with-events.md +251 -0
  111. package/skills/studiometa-js-toolkit/references/guide/migration/v3-to-v4.md +373 -0
  112. package/skills/studiometa-js-toolkit/references/utils/css.md +98 -0
  113. package/skills/studiometa-js-toolkit/references/utils/dom.md +54 -0
  114. package/skills/studiometa-js-toolkit/references/utils/easings.md +217 -0
  115. package/skills/studiometa-js-toolkit/references/utils/focus.md +84 -0
  116. package/skills/studiometa-js-toolkit/references/utils/history.md +69 -0
  117. package/skills/studiometa-js-toolkit/references/utils/index.md +53 -0
  118. package/skills/studiometa-js-toolkit/references/utils/is.md +84 -0
  119. package/skills/studiometa-js-toolkit/references/utils/load.md +59 -0
  120. package/skills/studiometa-js-toolkit/references/utils/math.md +129 -0
  121. package/skills/studiometa-js-toolkit/references/utils/motion.md +218 -0
  122. package/skills/studiometa-js-toolkit/references/utils/objects.md +68 -0
  123. package/skills/studiometa-js-toolkit/references/utils/scroll.md +128 -0
  124. package/skills/studiometa-js-toolkit/references/utils/strings.md +176 -0
  125. package/skills/studiometa-js-toolkit/references/utils/timing.md +111 -0
  126. package/skills/studiometa-js-toolkit/references/utils/transitions.md +122 -0
@@ -0,0 +1,106 @@
1
+ # `data-option-<name>`
2
+
3
+ Sets the value of one declared option.
4
+
5
+ ```html
6
+ <div data-component="Slider" data-option-speed="2"></div>
7
+ ```
8
+
9
+ The name is the option name in **kebab case**: `dragThreshold` is `data-option-drag-threshold`.
10
+
11
+ ## An option's name is inside its namespace
12
+
13
+ Every declared option owns the attribute built for it, so `data-option-` is a namespace _family_ rather than a namespace. It is `data-option-columns:s`, never `data-option:columns:s`.
14
+
15
+ That is what keeps the page-wide `attributeFilter` precise: the set of names is enumerable _because_ it comes from a declaration, so no option costs a second observer.
16
+
17
+ ## The five types
18
+
19
+ | Declared | `data-option-x="…"` | Reads as |
20
+ | --------- | ------------------- | ---------- |
21
+ | `String` | `"hello"` | `'hello'` |
22
+ | `Number` | `"2"` | `2` |
23
+ | `Boolean` | presence only | `true` |
24
+ | `Array` | `"[1, 2]"` | `[1, 2]` |
25
+ | `Object` | `'{"a": 1}'` | `{ a: 1 }` |
26
+
27
+ A union is declared in order — `[Number, Array]` reads `"10"` as a number and `"[10, 20]"` as an array. Each parser must give a value of its declared type before the next runs.
28
+
29
+ An absent attribute gives the declared default, or the empty value of the first declared type.
30
+
31
+ ## A boolean reads presence
32
+
33
+ The string the attribute carries is **never read**, the way `disabled` and `checked` work on the platform:
34
+
35
+ ```html
36
+ <div data-component="Panel" data-option-open></div>
37
+ <!-- true -->
38
+ <div data-component="Panel" data-option-open="false"></div>
39
+ <!-- true — the value says nothing -->
40
+ <div data-component="Panel"></div>
41
+ <!-- the declared default -->
42
+ ```
43
+
44
+ A template must write the attribute conditionally rather than interpolate a boolean:
45
+
46
+ ```html
47
+ {# Correct #}
48
+ <div data-component="Panel" {% if isOpen %}data-option-open{% endif %}></div>
49
+ ```
50
+
51
+ ### `data-option-no-<name>` turns one off
52
+
53
+ ```html
54
+ <div data-component="Dialog" data-option-no-trap-focus></div>
55
+ ```
56
+
57
+ - It is how an option declared `default: true` is turned off, since removing an attribute that is not there says nothing.
58
+ - **Only an option that can hold `false` has one** — a declared `Boolean`, or a union containing it. A `String` option has nothing to turn off, so `data-option-no-label` is not an attribute and the observer never watches for it.
59
+ - **Presence is the whole statement.** `data-option-no-x="false"` is not a double negative; it is the same flag.
60
+ - **An option whose own name starts with `no` negates independently.** `noSort` owns `data-option-no-sort`, and its off spelling doubles the prefix, `data-option-no-no-sort`, so no attribute an author writes means two things.
61
+
62
+ The cost is paid by boolean options only: each adds its negated name, plus one scoped spelling per breakpoint, to the observer's filter.
63
+
64
+ ## Every option is responsive
65
+
66
+ There is nothing to declare for it. **The suffix names one breakpoint and it cascades upwards:**
67
+
68
+ ```html
69
+ <div
70
+ data-component="Grid"
71
+ data-option-columns="1"
72
+ data-option-columns:s="2"
73
+ data-option-columns:l="4"></div>
74
+ ```
75
+
76
+ `$options.columns` walks from the active breakpoint down to the base value and gives the first attribute present. v3 spelled a set (`:xs:s`); v4 does not.
77
+
78
+ **The separator is a colon**, because an option name in kebab case can contain a dash.
79
+
80
+ ### The negated spelling is responsive too
81
+
82
+ One cascade, from the active breakpoint down. At each level the value-carrying spelling is read first, then the negation, and the first hit wins — so a narrower breakpoint turns an option back **on**, and at one breakpoint an explicit value outranks a flag:
83
+
84
+ ```html
85
+ <div data-component="Panel" data-option-no-x data-option-x:l="true"></div>
86
+ ```
87
+
88
+ **A suffix must name a configured breakpoint**
89
+
90
+ An unknown suffix gives one `responsive.unknown-breakpoint` warning per mount.
91
+
92
+ ## Writing one from code
93
+
94
+ `$options` is read-only, so the way to change a value is to write the attribute — the same statement the markup makes:
95
+
96
+ ```js
97
+ this.$el.setAttribute('data-option-speed', '4');
98
+ this.$el.removeAttribute('data-option-disabled');
99
+ flag ? el.setAttribute('data-option-open', '') : el.removeAttribute('data-option-open');
100
+ ```
101
+
102
+ The next read gives the new value, and [`option<Name>Changed()`](../methods-hooks-options.md) announces it.
103
+
104
+ Several writes in one batch give **one** change, from the value before the first write to the value at the end. A write that ends where it started is not a change.
105
+
106
+ See [Options](../../guide/introduction/managing-options.md).
@@ -0,0 +1,63 @@
1
+ # `data-ref`
2
+
3
+ Marks an element as a named part of a component.
4
+
5
+ ```html
6
+ <div data-component="Dialog">
7
+ <button data-ref="close">Close</button>
8
+ </div>
9
+ ```
10
+
11
+ The name must also be declared in [`config.refs`](../configuration.md#config-refs).
12
+
13
+ ## A single ref
14
+
15
+ ```html
16
+ <button data-ref="close"></button>
17
+ ```
18
+
19
+ `config.refs: ['close']` gives `$refs.close`, the **first** match.
20
+
21
+ ## A list
22
+
23
+ **The `[]` is part of the attribute:**
24
+
25
+ ```html
26
+ <button data-ref="tabs[]"></button>
27
+ <button data-ref="tabs[]"></button>
28
+ ```
29
+
30
+ `config.refs: ['tabs[]']` gives `$refs.tabs` as an array.
31
+
32
+ A list declaration matches the suffixed attribute **and nothing else**. The opposite mistake — the suffix missing from the attribute — gives one `ref.mismatch` warning per instance and per ref, naming the component and both spellings.
33
+
34
+ ## Naming the owner
35
+
36
+ By default a ref belongs to the **nearest enclosing component**. The prefixed form crosses every boundary except another component of that name:
37
+
38
+ ```html
39
+ <div data-component="Slider">
40
+ <div data-component="SliderItem">
41
+ <button data-ref="Slider.next">Slider's, from inside a child</button>
42
+ </div>
43
+ </div>
44
+ ```
45
+
46
+ The nearest `Slider` wins, and a nested `Slider` shadows its parent.
47
+
48
+ **The namespace is written in the markup only, never in `config.refs`**, and the name elsewhere never carries it. The namespace goes **before** the suffix:
49
+
50
+ | Markup | `config.refs` | Property | Handler | Decorator |
51
+ | -------------------------- | ------------- | ------------ | ------------- | ------------------ |
52
+ | `data-ref="next"` | `'next'` | `$refs.next` | `onNextClick` | `@on('next', …)` |
53
+ | `data-ref="Slider.next"` | `'next'` | `$refs.next` | `onNextClick` | `@on('next', …)` |
54
+ | `data-ref="dots[]"` | `'dots[]'` | `$refs.dots` | `onDotsClick` | `@on('dots[]', …)` |
55
+ | `data-ref="Slider.dots[]"` | `'dots[]'` | `$refs.dots` | `onDotsClick` | `@on('dots[]', …)` |
56
+
57
+ ## It is live
58
+
59
+ `$refs` reads the DOM on access, so a `data-ref` element added later is found with no refresh, and a detached one never stays in a list. There is no `$update()`.
60
+
61
+ `on<Ref><Event>` handlers are delegated from the component's root element, so a ref that appears later needs no new binding. Events that do not bubble — `focus`, `blur`, `scroll`, `mouseenter`, `mouseleave` — are delegated from the capture phase.
62
+
63
+ See [Refs](../../guide/introduction/managing-refs.md).
@@ -0,0 +1,32 @@
1
+ # HTML
2
+
3
+ Four attributes are the whole markup surface of the framework.
4
+
5
+ | Attribute | Declares |
6
+ | ------------------------------------------ | ------------------------------------------------ |
7
+ | [`data-component`](data-component.md) | which components this element runs |
8
+ | [`data-mount`](data-mount.md) | when they mount |
9
+ | [`data-ref`](data-ref.md) | that this element is a named part of a component |
10
+ | [`data-option-<name>`](data-option.md) | the value of one declared option |
11
+
12
+ Every one of them follows the same grammar:
13
+
14
+ ```
15
+ data-<namespace>[:<qualifier>[.<part>…]]
16
+ ```
17
+
18
+ **The colon selects one member of the vocabulary the namespace declares**, which is why an attribute the framework reads holds at most one colon. See [The attribute grammar](../../guide/concepts/attribute-grammar.md).
19
+
20
+ ## What is not an attribute
21
+
22
+ - `data-load` — the mount and the import are one decision, so `data-mount` says it.
23
+ - `<tk-name>` tag sugar and lowercase arbitrary-selector registrations — removed.
24
+ - A breakpoint **list** suffix such as `:xxs:xs:s` — a suffix names one breakpoint and cascades.
25
+ - A range form or a breakpoint-list form — neither exists.
26
+
27
+ ## Watching your own
28
+
29
+ `attributeFilter` takes exact names and the DOM has no wildcard, so the engine cannot see an attribute the framework cannot name. Two opt-ins cover the rest:
30
+
31
+ - [`watchAttributes()`](../dom/watchAttributes.md) — every attribute of one element.
32
+ - [`watchAttributeNamespace()`](../dom/watchAttributeNamespace.md) — a namespace whose names cannot be enumerated, with keyed bindings.
@@ -0,0 +1,73 @@
1
+ # Base class
2
+
3
+ Every component extends `Base`. It carries the element, the live views over its markup, the lifecycle, the event conventions and the scheduling handles — and nothing else. `Base` knows nothing about services, view transitions or storage.
4
+
5
+ ```js
6
+ import { Base } from '@studiometa/js-toolkit';
7
+
8
+ class Component extends Base {
9
+ static config = {
10
+ name: 'Component',
11
+ };
12
+ }
13
+ ```
14
+
15
+ ## Sections
16
+
17
+ - [Configuration](configuration.md) — `static config` and how it merges
18
+ - [Lifecycle hooks](methods-hooks-lifecycle.md) — `mounted()` and `unmounted()`
19
+ - [Options hooks](methods-hooks-options.md) — `option<Name>Changed()`
20
+ - [Events hooks](methods-hooks-events.md) — `on<Event>`, `on<Ref><Event>`, `on<Child><Event>`, `onWindow<Event>`, `onDocument<Event>`
21
+ - [Services hooks](methods-hooks-services.md) — `ticked()`, `scrolled()`, `resized()` and the rest
22
+ - [Instance properties](instance-properties.md) — `$el`, `$id`, `$refs`, `$options`, `$config`, `$isMounted`
23
+ - [Instance methods](instance-methods.md) — `$mount()`, `$emit()`, `$query()`, `$provide()`, `$read()` and the rest
24
+ - [Instance events](instance-events.md) — what an instance dispatches
25
+
26
+ ## The constructor
27
+
28
+ ```ts
29
+ new Base(el: HTMLElement)
30
+ ```
31
+
32
+ **Do not call it.** The registry is the only code that constructs an instance, and an instance built by hand is not in the element's instance map, so nothing finds it and nothing unmounts it.
33
+
34
+ A component's own constructor is a legitimate place for field initializers, and two registrations belong there because they are **instance-scoped** rather than unmount-scoped:
35
+
36
+ ```js
37
+ import { Base, createContext } from '@studiometa/js-toolkit';
38
+
39
+ const Ctx = createContext('ctx');
40
+ class Slider extends Base {
41
+ static config = { name: 'Slider', components: {} };
42
+
43
+ // Never released. Both die with the element.
44
+ api = this.$provide(Ctx, { goNext: () => {} });
45
+ items = this.$watchChildren('SliderItem');
46
+ }
47
+ ```
48
+
49
+ ## Fixed properties
50
+
51
+ `$el`, `$id`, `$options` and `$refs` are **fixed properties of the instance**, not fields it happens to hold. They are defined non-writable in the constructor, so an assignment throws in a module rather than replacing what every other part of the framework reads.
52
+
53
+ `readonly` states it for a reader with a build step; the property descriptor states it for everyone else. They stay enumerable, so an instance still reads as one.
54
+
55
+ ## The typed surface
56
+
57
+ `Base` takes an optional props type with four optional keys — `$el`, `$refs`, `$options` and `$emits`. See [TypeScript](../guide/going-further/typing-components.md).
58
+
59
+ ```ts
60
+ import { Base } from '@studiometa/js-toolkit';
61
+
62
+ class Slider extends Base<{
63
+ $refs: { next: HTMLButtonElement };
64
+ $options: { speed: number };
65
+ $emits: { goto: { index: number }; stop: void };
66
+ }> {
67
+ static config = {
68
+ name: 'Slider',
69
+ refs: ['next'],
70
+ options: { speed: { type: Number, default: 1 } },
71
+ };
72
+ }
73
+ ```
@@ -0,0 +1,75 @@
1
+ # Instance events
2
+
3
+ Everything an instance dispatches is a real DOM event, so a plain `addEventListener` hears all of it.
4
+
5
+ ## Component events
6
+
7
+ `$emit(name, payload?)` dispatches on `$el`:
8
+
9
+ | Property | Value |
10
+ | ------------ | ----------------------------- |
11
+ | `bubbles` | `true` |
12
+ | `cancelable` | `true` |
13
+ | `detail` | the payload object, or `null` |
14
+
15
+ Names are typed lower-kebab string literals declared through the props type's `$emits` key. There is no namespace: a component event is `open`, not `js-toolkit:open`.
16
+
17
+ ```js
18
+ el.addEventListener('goto', (event) => {
19
+ console.log(event.detail.index);
20
+ });
21
+ ```
22
+
23
+ ## Lifecycle announcements
24
+
25
+ Every instance announces its own mount and unmount, carrying itself in the payload:
26
+
27
+ ```ts
28
+ interface LifecycleEventDetail {
29
+ instance: Base;
30
+ }
31
+ ```
32
+
33
+ | Event | Constant | Dispatched from |
34
+ | -------------------------------- | ---------------------------- | ------------------------- |
35
+ | `js-toolkit:component:mounted` | `EVENTS.component.mounted` | the element — **bubbles** |
36
+ | `js-toolkit:component:unmounted` | `EVENTS.component.unmounted` | `document` |
37
+
38
+ ```ts
39
+ import { EVENTS, type LifecycleEventDetail } from '@studiometa/js-toolkit';
40
+
41
+ document.addEventListener(EVENTS.component.mounted, (rawEvent) => {
42
+ const event = rawEvent as CustomEvent<LifecycleEventDetail>;
43
+ console.log(`${event.detail.instance.$id} mounted`);
44
+ });
45
+ ```
46
+
47
+ The unmount event dispatches from `document` because the element can already be detached. That is also what lets one lazy, realm-shared listener serve every [`$watchChildren()`](instance-methods.md#watchchildren) watcher while the document holds nothing but weak references to them — a listener per watcher would make every watching component immortal.
48
+
49
+ **An instance that is scheduled but not mounted announces nothing.**
50
+
51
+ ## Diagnostics
52
+
53
+ `js-toolkit:diagnostic` — `EVENTS.diagnostic` — starts on the relevant connected element, or on `document` when there is none, with `{ bubbles: true, composed: true, cancelable: true }`.
54
+
55
+ `preventDefault()` suppresses the default console output only. See [Diagnostics](diagnostics/index.md).
56
+
57
+ ## Negotiated events
58
+
59
+ `js-toolkit:dom:update` — `EVENTS.dom.update` — is dispatched by [`domUpdate()`](dom/domUpdate.md), and [`emitExtendable()`](dom/emitExtendable.md) dispatches the name its caller gives.
60
+
61
+ Both are bubbling and **non-cancelable**: the step is announced, not proposed. Their detail carries a `wrap()` or a `waitUntil()` valid only while the event dispatches.
62
+
63
+ They have **no `Base` path** and are absent from `$emits`, so the negotiation code and the optional view-transition import stay out of the `Base` graph. Plain DOM code uses the same functions with no instance.
64
+
65
+ ## Private transports
66
+
67
+ The context request is a bubbling, **module-private** event and is deliberately not part of public `EVENTS`. Framework internals that need a transport use module-local namespaced constants; only what a consumer can usefully listen to is exported.
68
+
69
+ ## The convention, in full
70
+
71
+ - Public framework events use the deeply frozen `EVENTS` object and the `js-toolkit:` namespace.
72
+ - Private framework transports use module-local constants and do not join `EVENTS`.
73
+ - Component events use typed lower-kebab string literals from `$emits`.
74
+ - A payload is **one object** in `CustomEvent.detail`, or the platform value `null`.
75
+ - Diagnostic events are cancelable, so monitoring can suppress the default output only.
@@ -0,0 +1,267 @@
1
+ # Instance methods
2
+
3
+ ## `$mount()`
4
+
5
+ ```ts
6
+ $mount(): this
7
+ ```
8
+
9
+ Starts a mount cycle. On a page the registry calls it; calling it yourself is legitimate but nearly always a sign that a [mount strategy](../guide/going-further/mount-strategies.md) is the answer instead.
10
+
11
+ ## `$unmount()`
12
+
13
+ ```ts
14
+ $unmount(): this
15
+ ```
16
+
17
+ The reversible opposite of mount. It unbinds the cycle's listeners, runs the `mounted()` cleanups, cancels the instance's scheduled tasks, calls `unmounted()` and announces the change.
18
+
19
+ **The instance stays on its element**, so `$mount()` can start a new cycle with the same identity.
20
+
21
+ ## `$emit()` {#emit}
22
+
23
+ ```ts
24
+ $emit<K extends EmitName<T>>(event: K, ...payload: EmitArgs<T, K>): CustomEvent<EmitDetail<T, K>>
25
+ ```
26
+
27
+ Dispatches a bubbling, cancelable `CustomEvent` on `$el`, with the payload as `detail`.
28
+
29
+ ```js
30
+ import { Base } from '@studiometa/js-toolkit';
31
+
32
+ class Dialog extends Base {
33
+ static config = { name: 'Dialog' };
34
+
35
+ close() {
36
+ const event = this.$emit('close');
37
+ if (event.defaultPrevented) return;
38
+ this.$el.removeAttribute('data-option-open');
39
+ }
40
+
41
+ goto(index) {
42
+ this.$emit('goto', { index });
43
+ }
44
+ }
45
+ ```
46
+
47
+ - The payload is **one object**, or nothing. An omitted payload leaves `detail` at the platform value `null`.
48
+ - A value that is not an object is refused by the type and reported as `event.invalid-emit-payload`. The event still dispatches.
49
+ - Nothing in the framework is gated on cancellation.
50
+
51
+ Declare the names and payloads through the props type's `$emits` key. See [Events](../guide/introduction/working-with-events.md).
52
+
53
+ ## `$on()` {#on}
54
+
55
+ ```ts
56
+ $on(type: string, listener: EventListener, options?: AddEventListenerOptions): () => void
57
+ ```
58
+
59
+ Adds a listener on `$el` and **returns its own remover**, which makes it a `mounted()` cleanup as it is:
60
+
61
+ ```js
62
+ mounted() {
63
+ return this.$on('transitionend', this.handleEnd, { once: true });
64
+ }
65
+ ```
66
+
67
+ Reach for it when the event name is data rather than a method name, or when you need listener options.
68
+
69
+ ## `$off()`
70
+
71
+ ```ts
72
+ $off(type: string, listener: EventListener, options?: AddEventListenerOptions): void
73
+ ```
74
+
75
+ ## `$query()` {#query}
76
+
77
+ ```ts
78
+ $query<T extends Base = Base>(name: string): T[]
79
+ ```
80
+
81
+ Every **mounted** instance of `name` among the descendants of `$el`, in document order. A flat array at any depth, not a keyed object.
82
+
83
+ ```js
84
+ import { Base } from '@studiometa/js-toolkit';
85
+
86
+ class Slider extends Base {
87
+ static config = { name: 'Slider' };
88
+
89
+ reset() {
90
+ for (const item of this.$query('SliderItem')) {
91
+ item.$el.removeAttribute('data-option-active');
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ For a collection kept over time rather than queried each time, prefer [`$watchChildren()`](#watchchildren).
98
+
99
+ ## `$closest()` {#closest}
100
+
101
+ ```ts
102
+ $closest<T extends Base = Base>(name: string): T | null
103
+ ```
104
+
105
+ The nearest ancestor instance of `name`, or `null`.
106
+
107
+ **Always guard it**
108
+
109
+ It is resolved on every access and returns `null` when no matching ancestor instance has been constructed. Whether an ancestor is resolvable from a child's own `mounted()` depends on mount order, so never dereference it unguarded there.
110
+
111
+ ```js
112
+ this.$closest('Slider')?.goNext();
113
+ ```
114
+
115
+ A child that reaches for its parent is usually a child that should `$emit()` instead.
116
+
117
+ ## `$watchChildren()` {#watchchildren}
118
+
119
+ ```ts
120
+ $watchChildren<T extends Base>(name: string, callbacks?: WatchChildrenCallbacks<T>): ChildrenCollection<T>
121
+ $watchChildren<T extends BaseConstructor>(ComponentClass: T, callbacks?: WatchChildrenCallbacks<InstanceType<T>>): ChildrenCollection<InstanceType<T>>
122
+ ```
123
+
124
+ A **live collection** of descendant instances, in document order whatever the mount order is.
125
+
126
+ ```js
127
+ import { Base } from '@studiometa/js-toolkit';
128
+
129
+ class SliderItem extends Base {
130
+ static config = { name: 'SliderItem' };
131
+ }
132
+
133
+ class Slider extends Base {
134
+ static config = { name: 'Slider', components: { SliderItem } };
135
+
136
+ // Exact `config.name`.
137
+ items = this.$watchChildren('SliderItem', {
138
+ added(item) {},
139
+ removed(item) {},
140
+ });
141
+
142
+ // Also every named subclass, through `instanceof`.
143
+ allItems = this.$watchChildren(SliderItem);
144
+ }
145
+ ```
146
+
147
+ ```ts
148
+ interface ChildrenCollection<T extends Base = Base> extends Iterable<T> {
149
+ readonly size: number;
150
+ readonly items: T[];
151
+ }
152
+ ```
153
+
154
+ - **The string overload** looks up the exact `config.name`.
155
+ - **The constructor overload** walks the descendant elements in document order, reads their instance maps, keeps the instances where `instance instanceof ComponentClass`, excludes the watching instance, and removes duplicates.
156
+ - Callbacks passed to the decorator form are bound to the instance.
157
+
158
+ **It is instance-scoped, not unmount-scoped**
159
+
160
+ The subscription stays active through unmount and mount cycles, for the whole life of the watching instance, which is why the call belongs in a field initializer. It is never released and dies with the element.
161
+
162
+ The initial sweep is deferred to a microtask, because the call is usually a field initializer; the announcement listeners attach at once, so nothing is missed. No global instance registry is added — unmounted instances announce from `document`, so one lazy, realm-shared listener serves every watcher and the document holds nothing but weak references.
163
+
164
+ ## `$provide()`
165
+
166
+ ```ts
167
+ $provide<V>(key: ContextKey<V>, value: V): V
168
+ ```
169
+
170
+ Provides a value to the subtree and returns it, so the call is a field initializer. **The value is provided as it is** — nothing is wrapped.
171
+
172
+ ```js
173
+ import { Base, createContext, signal } from '@studiometa/js-toolkit';
174
+
175
+ const SliderContext = createContext('slider');
176
+ class Slider extends Base {
177
+ static config = { name: 'Slider' };
178
+
179
+ api = this.$provide(SliderContext, {
180
+ state: signal({ index: 0 }),
181
+ goNext: () => {},
182
+ });
183
+ }
184
+ ```
185
+
186
+ Instance-scoped: never released, and it dies with the element. A component whose declaration is withdrawn keeps providing until its element goes.
187
+
188
+ ## `$inject()`
189
+
190
+ ```ts
191
+ $inject<V>(key: ContextKey<V>): Promise<V>
192
+ ```
193
+
194
+ Resolves with the nearest provided value. **When nothing provides, it never settles** — a missing provider means "not yet", not "no".
195
+
196
+ ```js
197
+ async mounted() {
198
+ const api = await this.$inject(SliderContext);
199
+ return api.state.subscribe((state) => this.render(state));
200
+ }
201
+ ```
202
+
203
+ The pending request is **unmount-scoped**: `$unmount()` cancels it, and a new mount runs `mounted()` again and asks again.
204
+
205
+ ## `$injectSync()`
206
+
207
+ ```ts
208
+ $injectSync<V>(key: ContextKey<V>): V | undefined
209
+ ```
210
+
211
+ The value, synchronously, or `undefined`. For the case where the answer is optional and the caller has a fallback.
212
+
213
+ ## `$read()` and `$write()`
214
+
215
+ ```ts
216
+ $read<T>(fn: () => T): ScheduledTask<T>
217
+ $write<T>(fn: () => T): ScheduledTask<T>
218
+ ```
219
+
220
+ Schedules a layout read or a DOM write in the frame's matching phase, tied to this instance. `$unmount()` cancels the pending ones.
221
+
222
+ ```js
223
+ import { Base } from '@studiometa/js-toolkit';
224
+
225
+ class Reveal extends Base {
226
+ static config = { name: 'Reveal' };
227
+
228
+ async mounted() {
229
+ const box = await this.$read(() => this.$el.getBoundingClientRect()).promise;
230
+ this.$write(() => {
231
+ this.$el.style.setProperty('--height', `${box?.height}px`);
232
+ });
233
+ }
234
+ }
235
+ ```
236
+
237
+ ```ts
238
+ interface ScheduledTask<T = unknown> {
239
+ promise: Promise<T | undefined>;
240
+ cancel(): void;
241
+ }
242
+ ```
243
+
244
+ Every read runs before every write, once, before paint. A `read` scheduled from a `write` runs in the next frame; a `write` scheduled from a `read` runs in the same frame. See [The scheduler](../guide/going-further/scheduling-work.md).
245
+
246
+ ## `$warn()` and `$error()`
247
+
248
+ ```ts
249
+ $warn(code: ToolkitDiagnosticCode, message: string): void
250
+ $error(code: ToolkitDiagnosticCode, message: string, error: unknown): void
251
+ ```
252
+
253
+ Report on the [diagnostic channel](../guide/going-further/handling-diagnostics.md) with this component's name filled in.
254
+
255
+ ```js
256
+ this.$warn('carousel.no-slides', 'A Carousel with no slide does nothing.');
257
+ ```
258
+
259
+ ## What does not exist
260
+
261
+ | Not in v4 | Use instead |
262
+ | -------------- | ---------------------------------------- |
263
+ | `$parent` | `$closest(name)` |
264
+ | `$children` | `$watchChildren(name)` or `$query(name)` |
265
+ | `$root` | `$closest(name)` with the real name |
266
+ | `$update()` | nothing — refs are live |
267
+ | `$terminate()` | nothing — there is no permanent state |