@studiometa/js-toolkit 4.0.0-alpha.1 → 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.
- package/README.md +10 -0
- package/package.json +5 -2
- package/skills/studiometa-js-toolkit/SKILL.md +87 -0
- package/skills/studiometa-js-toolkit/references/api/configuration.md +169 -0
- package/skills/studiometa-js-toolkit/references/api/context/createContext.md +46 -0
- package/skills/studiometa-js-toolkit/references/api/context/createGroup.md +67 -0
- package/skills/studiometa-js-toolkit/references/api/context/index.md +69 -0
- package/skills/studiometa-js-toolkit/references/api/context/injectContext.md +58 -0
- package/skills/studiometa-js-toolkit/references/api/context/injectContextSync.md +44 -0
- package/skills/studiometa-js-toolkit/references/api/context/provideContext.md +61 -0
- package/skills/studiometa-js-toolkit/references/api/context/provideRootContext.md +42 -0
- package/skills/studiometa-js-toolkit/references/api/context/signal.md +85 -0
- package/skills/studiometa-js-toolkit/references/api/context/subscribeContext.md +71 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/children.md +92 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/component.md +75 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/index.md +41 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/inject.md +73 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/on.md +149 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/provide.md +77 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/read-write.md +93 -0
- package/skills/studiometa-js-toolkit/references/api/diagnostics/DIAGNOSTICS.md +137 -0
- package/skills/studiometa-js-toolkit/references/api/diagnostics/EVENTS.md +45 -0
- package/skills/studiometa-js-toolkit/references/api/diagnostics/index.md +58 -0
- package/skills/studiometa-js-toolkit/references/api/diagnostics/reportDiagnostic.md +67 -0
- package/skills/studiometa-js-toolkit/references/api/diagnostics/warn.md +54 -0
- package/skills/studiometa-js-toolkit/references/api/dom/breakpoints.md +81 -0
- package/skills/studiometa-js-toolkit/references/api/dom/domUpdate.md +70 -0
- package/skills/studiometa-js-toolkit/references/api/dom/emitExtendable.md +68 -0
- package/skills/studiometa-js-toolkit/references/api/dom/index.md +26 -0
- package/skills/studiometa-js-toolkit/references/api/dom/swap.md +105 -0
- package/skills/studiometa-js-toolkit/references/api/dom/watchAttributeNamespace.md +96 -0
- package/skills/studiometa-js-toolkit/references/api/dom/watchAttributes.md +56 -0
- package/skills/studiometa-js-toolkit/references/api/dom/whenDOMSettled.md +41 -0
- package/skills/studiometa-js-toolkit/references/api/html/data-component.md +105 -0
- package/skills/studiometa-js-toolkit/references/api/html/data-mount.md +69 -0
- package/skills/studiometa-js-toolkit/references/api/html/data-option.md +106 -0
- package/skills/studiometa-js-toolkit/references/api/html/data-ref.md +63 -0
- package/skills/studiometa-js-toolkit/references/api/html/index.md +32 -0
- package/skills/studiometa-js-toolkit/references/api/index.md +73 -0
- package/skills/studiometa-js-toolkit/references/api/instance-events.md +75 -0
- package/skills/studiometa-js-toolkit/references/api/instance-methods.md +267 -0
- package/skills/studiometa-js-toolkit/references/api/instance-properties.md +90 -0
- package/skills/studiometa-js-toolkit/references/api/methods-hooks-events.md +149 -0
- package/skills/studiometa-js-toolkit/references/api/methods-hooks-lifecycle.md +90 -0
- package/skills/studiometa-js-toolkit/references/api/methods-hooks-options.md +96 -0
- package/skills/studiometa-js-toolkit/references/api/methods-hooks-services.md +164 -0
- package/skills/studiometa-js-toolkit/references/api/registry/defineManifest.md +57 -0
- package/skills/studiometa-js-toolkit/references/api/registry/fromMetaGlob.md +33 -0
- package/skills/studiometa-js-toolkit/references/api/registry/fromWebpackContext.md +37 -0
- package/skills/studiometa-js-toolkit/references/api/registry/getInstance.md +52 -0
- package/skills/studiometa-js-toolkit/references/api/registry/getInstances.md +48 -0
- package/skills/studiometa-js-toolkit/references/api/registry/getMountedInstances.md +39 -0
- package/skills/studiometa-js-toolkit/references/api/registry/getUnmountedInstances.md +46 -0
- package/skills/studiometa-js-toolkit/references/api/registry/index.md +49 -0
- package/skills/studiometa-js-toolkit/references/api/registry/registerComponent.md +93 -0
- package/skills/studiometa-js-toolkit/references/api/registry/registerComponents.md +37 -0
- package/skills/studiometa-js-toolkit/references/api/registry/registerManifest.md +72 -0
- package/skills/studiometa-js-toolkit/references/api/scheduler/defaultScheduler.md +104 -0
- package/skills/studiometa-js-toolkit/references/api/scheduler/index.md +27 -0
- package/skills/studiometa-js-toolkit/references/api/scheduler/nextFrame.md +31 -0
- package/skills/studiometa-js-toolkit/references/api/scheduler/viewTransition.md +53 -0
- package/skills/studiometa-js-toolkit/references/api/services/createService.md +95 -0
- package/skills/studiometa-js-toolkit/references/api/services/createServiceMixin.md +81 -0
- package/skills/studiometa-js-toolkit/references/api/services/index.md +94 -0
- package/skills/studiometa-js-toolkit/references/api/services/mixins.md +148 -0
- package/skills/studiometa-js-toolkit/references/api/services/perTarget.md +65 -0
- package/skills/studiometa-js-toolkit/references/api/services/toggle.md +80 -0
- package/skills/studiometa-js-toolkit/references/api/services/until.md +44 -0
- package/skills/studiometa-js-toolkit/references/api/services/useBreakpoint.md +42 -0
- package/skills/studiometa-js-toolkit/references/api/services/useDrag.md +119 -0
- package/skills/studiometa-js-toolkit/references/api/services/useInView.md +64 -0
- package/skills/studiometa-js-toolkit/references/api/services/useKey.md +105 -0
- package/skills/studiometa-js-toolkit/references/api/services/useMediaQuery.md +45 -0
- package/skills/studiometa-js-toolkit/references/api/services/useMutation.md +69 -0
- package/skills/studiometa-js-toolkit/references/api/services/usePointer.md +91 -0
- package/skills/studiometa-js-toolkit/references/api/services/usePrefersReducedMotion.md +42 -0
- package/skills/studiometa-js-toolkit/references/api/services/useRaf.md +91 -0
- package/skills/studiometa-js-toolkit/references/api/services/useResize.md +60 -0
- package/skills/studiometa-js-toolkit/references/api/services/useScroll.md +88 -0
- package/skills/studiometa-js-toolkit/references/api/services/useScrollProgress.md +88 -0
- package/skills/studiometa-js-toolkit/references/api/services/useWindowScroll.md +25 -0
- package/skills/studiometa-js-toolkit/references/api/services/useWindowSize.md +25 -0
- package/skills/studiometa-js-toolkit/references/api/storage/createStorage.md +126 -0
- package/skills/studiometa-js-toolkit/references/api/storage/index.md +33 -0
- package/skills/studiometa-js-toolkit/references/api/storage/presets.md +58 -0
- package/skills/studiometa-js-toolkit/references/api/storage/providers.md +109 -0
- package/skills/studiometa-js-toolkit/references/api/test/index.md +214 -0
- package/skills/studiometa-js-toolkit/references/guide/concepts/attribute-grammar.md +93 -0
- package/skills/studiometa-js-toolkit/references/guide/concepts/mutation-engine.md +71 -0
- package/skills/studiometa-js-toolkit/references/guide/concepts/philosophy.md +78 -0
- package/skills/studiometa-js-toolkit/references/guide/concepts/the-registry.md +110 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/autoloading.md +104 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/handling-diagnostics.md +104 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/mount-strategies.md +95 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/scheduling-work.md +124 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/sharing-state.md +218 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/swapping-content.md +92 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/testing-components.md +137 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/typing-components.md +190 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/using-decorators.md +168 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/using-storage.md +125 -0
- package/skills/studiometa-js-toolkit/references/guide/index.md +163 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/installation.md +144 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/lifecycle-hooks.md +174 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/managing-components.md +229 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/managing-options.md +235 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/managing-refs.md +174 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/using-services.md +218 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/working-with-events.md +251 -0
- package/skills/studiometa-js-toolkit/references/guide/migration/v3-to-v4.md +373 -0
- package/skills/studiometa-js-toolkit/references/utils/css.md +98 -0
- package/skills/studiometa-js-toolkit/references/utils/dom.md +54 -0
- package/skills/studiometa-js-toolkit/references/utils/easings.md +217 -0
- package/skills/studiometa-js-toolkit/references/utils/focus.md +84 -0
- package/skills/studiometa-js-toolkit/references/utils/history.md +69 -0
- package/skills/studiometa-js-toolkit/references/utils/index.md +53 -0
- package/skills/studiometa-js-toolkit/references/utils/is.md +84 -0
- package/skills/studiometa-js-toolkit/references/utils/load.md +59 -0
- package/skills/studiometa-js-toolkit/references/utils/math.md +129 -0
- package/skills/studiometa-js-toolkit/references/utils/motion.md +218 -0
- package/skills/studiometa-js-toolkit/references/utils/objects.md +68 -0
- package/skills/studiometa-js-toolkit/references/utils/scroll.md +128 -0
- package/skills/studiometa-js-toolkit/references/utils/strings.md +176 -0
- package/skills/studiometa-js-toolkit/references/utils/timing.md +111 -0
- package/skills/studiometa-js-toolkit/references/utils/transitions.md +122 -0
package/README.md
CHANGED
|
@@ -54,6 +54,16 @@ There is no build step to adopt: the same class runs from a `<script type="modul
|
|
|
54
54
|
|
|
55
55
|
The design record lives in the repository: [DESIGN.md](./DESIGN.md) states what v4 does, and [RATIONALE.md](./RATIONALE.md) states why, which options were refused, and what the measurements are.
|
|
56
56
|
|
|
57
|
+
## Agent skills
|
|
58
|
+
|
|
59
|
+
The package ships the `studiometa-js-toolkit` [agent skill](https://agentskills.io). The skill teaches coding agents how to use the package, and it matches the installed version. Link the skills of your dependencies into your project with the [`skills` CLI](https://github.com/vercel-labs/skills):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx skills experimental_sync
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The command links each skill from `node_modules` into the agent folders (`.agents/skills/`, `.claude/skills/`…). When you update the package, the linked skill updates too.
|
|
66
|
+
|
|
57
67
|
## Contributing
|
|
58
68
|
|
|
59
69
|
See the [repository README](https://github.com/studiometa/js-toolkit#contributing). Fixes for the 3.x line go to the [`3.x` branch](https://github.com/studiometa/js-toolkit/tree/3.x).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@studiometa/js-toolkit",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"./dist/responsive-options.js"
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"homepage": "https://js-toolkit-v4.studiometa.dev",
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
24
|
+
"skills",
|
|
24
25
|
"README.md",
|
|
25
26
|
"LICENSE"
|
|
26
27
|
],
|
|
@@ -812,7 +813,8 @@
|
|
|
812
813
|
}
|
|
813
814
|
},
|
|
814
815
|
"scripts": {
|
|
815
|
-
"build": "node scripts/build.js",
|
|
816
|
+
"build": "node scripts/build.js && npm run build:skills",
|
|
817
|
+
"build:skills": "node ../../scripts/build-skills.js",
|
|
816
818
|
"check:constant-subpaths": "node scripts/check-constant-subpaths.js",
|
|
817
819
|
"check:diagnostics": "node scripts/check-diagnostics.js",
|
|
818
820
|
"check:doc-links": "node scripts/check-doc-links.js",
|
|
@@ -822,6 +824,7 @@
|
|
|
822
824
|
"demo:preview": "vite preview demo --host",
|
|
823
825
|
"docs:dev": "vitepress dev docs",
|
|
824
826
|
"docs:build": "vitepress build docs",
|
|
827
|
+
"postdocs:build": "node ../../scripts/build-skills.js --well-known docs/.vitepress/dist",
|
|
825
828
|
"docs:preview": "vitepress preview docs",
|
|
826
829
|
"test": "npm run check:diagnostics && npm run check:doc-links && npm run test:fixtures && vitest run",
|
|
827
830
|
"test:fixtures": "node scripts/build-runtime-fixtures.js",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: studiometa-js-toolkit
|
|
3
|
+
description: Write components with @studiometa/js-toolkit v4 — classes that extend Base, registered with registerComponent and bound to the DOM through data-component, data-ref, data-option-* and data-mount attributes. Use when code imports @studiometa/js-toolkit, extends Base, uses $refs, $options, $emit, $query, $closest, on<Ref><Event> hooks, services (useScroll, useResize, useRaf, withScroll…), mount strategies, provide/inject, the scheduler or @studiometa/js-toolkit/utils, or when you migrate code from js-toolkit v3.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @studiometa/js-toolkit
|
|
7
|
+
|
|
8
|
+
`@studiometa/js-toolkit` binds JavaScript classes to server-rendered HTML. A component is a class that extends `Base`, a name in the registry, and a `data-component` attribute in the markup.
|
|
9
|
+
|
|
10
|
+
This skill matches the installed package version. v4 is a full breaking major: most examples on the web and in training data are v3 code that does not work in v4. Read the references in this folder before the online docs.
|
|
11
|
+
|
|
12
|
+
## References
|
|
13
|
+
|
|
14
|
+
- `references/guide/introduction/`: components, refs, options, events, lifecycle, services. Start here.
|
|
15
|
+
- `references/guide/going-further/`: autoloading, mount strategies, scheduling, shared state, swapping content, testing, TypeScript, decorators, storage, diagnostics.
|
|
16
|
+
- `references/guide/migration/v3-to-v4.md`: every v3 API that changed, with its replacement. Read it before you change existing code.
|
|
17
|
+
- `references/api/`: the exact API of `Base`, the registry, services, context, DOM helpers, scheduler, storage and diagnostics.
|
|
18
|
+
- `references/utils/`: the utilities of `@studiometa/js-toolkit/utils`, by category.
|
|
19
|
+
|
|
20
|
+
## A component
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
import { Base, registerComponent } from '@studiometa/js-toolkit';
|
|
24
|
+
|
|
25
|
+
class Counter extends Base {
|
|
26
|
+
static config = {
|
|
27
|
+
name: 'Counter',
|
|
28
|
+
refs: ['output'],
|
|
29
|
+
options: { step: { type: Number, default: 1 } },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
count = 0;
|
|
33
|
+
|
|
34
|
+
onClick() {
|
|
35
|
+
this.count += this.$options.step;
|
|
36
|
+
this.$refs.output.textContent = String(this.count);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
registerComponent(Counter);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<button data-component="Counter" data-option-step="5">
|
|
45
|
+
Clicked
|
|
46
|
+
<span data-ref="output">0</span>
|
|
47
|
+
times
|
|
48
|
+
</button>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## v4 rules
|
|
52
|
+
|
|
53
|
+
These are the v3 habits that break in v4:
|
|
54
|
+
|
|
55
|
+
| Do not write (v3) | Write (v4) |
|
|
56
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
57
|
+
| `createApp(App)` | `registerComponents(A, B, C)`: no root component |
|
|
58
|
+
| `this.$children.Item`, `this.$parent`, `this.$root` | `this.$query('Item')`, `this.$watchChildren('Item')`, `this.$closest('Name')` (can be `null`) |
|
|
59
|
+
| `this.$update()` | nothing: `$refs` reads the DOM on each access |
|
|
60
|
+
| `data-ref="items"` for a list | `data-ref="items[]"`, same as `refs: ['items[]']`; read it as `this.$refs.items` |
|
|
61
|
+
| `this.$options.open = true` | `$options` is read-only: change the `data-option-*` attribute |
|
|
62
|
+
| `data-option-open="false"` | remove the attribute, or `data-option-no-open` when the default is `true` |
|
|
63
|
+
| `default: {}` or `default: []` | a factory: `default: () => ({})` |
|
|
64
|
+
| `this.$emit('go', 1, 'left')` | one payload object: `this.$emit('go', { index: 1, direction: 'left' })` |
|
|
65
|
+
| `onItemClick(event, index)` | one object: `onItemsClick({ event, target, index })`, `onChildEvent({ event, target, payload })` |
|
|
66
|
+
| `updated()` | `option<Name>Changed()` or `watchAttributes()` |
|
|
67
|
+
| `unmounted()` only to unsubscribe | return the cleanup from `mounted()` |
|
|
68
|
+
| `data-load`, `withMountWhenInView`, `importWhen*` | `data-mount="visible"` or a `registerManifest()` entry with `mountStrategy` |
|
|
69
|
+
| `withExtraConfig()` | a subclass with its own `static config` |
|
|
70
|
+
| `withResponsiveOptions`, `data-option-x:s:m` | every option is responsive: `data-option-x:s` cascades up |
|
|
71
|
+
| `domScheduler`, `afterWrite` | `this.$read()` / `this.$write()`, or `defaultScheduler` |
|
|
72
|
+
| `tween`, `animate` | not in core: use `transition()`, `spring()`, `smoothTo()` or `@studiometa/ui-motion` |
|
|
73
|
+
|
|
74
|
+
## Practices
|
|
75
|
+
|
|
76
|
+
- Let the registry construct instances. Never call `new MyComponent(el)`.
|
|
77
|
+
- Declare `refs` and use `on<Ref><Event>` hooks instead of `querySelector` and `addEventListener`. Use `onWindow<Event>` and `onDocument<Event>` for global events.
|
|
78
|
+
- Use a service before a custom listener or observer: the mixins `withScroll` (`scrolled`), `withResize` (`resized`), `withRaf` (`ticked`), `withPointer` (`moved`), `withDrag` (`dragged`), `withKey` (`keyed`), `withInView` (`intersected`), `withMutation` (`mutated`), `withScrollProgress` (`scrolledInView`), or the `use*()` functions.
|
|
79
|
+
- Use mount strategies (`eager`, `visible`, `in-view`, `idle`, `interaction`, `media:<query>`) to defer work, not custom observers.
|
|
80
|
+
- Share state with `provideRootContext()`, `$provide()` and `$inject()`, not with global variables.
|
|
81
|
+
- Import utilities from `@studiometa/js-toolkit/utils` (for example `import { clamp, damp } from '@studiometa/js-toolkit/utils'`). Read `references/utils/` before you write a helper.
|
|
82
|
+
- Diagnostics (`EVENTS.diagnostic`) explain wrong refs, options and payloads. Read the warning code in `references/api/diagnostics/DIAGNOSTICS.md`.
|
|
83
|
+
|
|
84
|
+
## Related skills
|
|
85
|
+
|
|
86
|
+
- `studiometa-ui`: ready-made components (Action, Dialog, Disclosure, Tabs, Carousel, Fetch, Transition, Data*…). Look for a component there before you write a custom one.
|
|
87
|
+
- `studiometa-eslint-plugin-js-toolkit`: the lint rules for js-toolkit code.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
`static config` declares everything the framework needs to know about a component before an instance exists.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
interface BaseConfig {
|
|
7
|
+
name: string;
|
|
8
|
+
components?: Record<string, BaseConstructor | ComponentImporter>;
|
|
9
|
+
refs?: string[];
|
|
10
|
+
options?: Record<string, OptionDefinition>;
|
|
11
|
+
mountStrategy?: MountStrategy;
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## `config.name`
|
|
16
|
+
|
|
17
|
+
**Required.** The name the registry registers under, the token `data-component` writes, and the prefix of every instance's [`$id`](instance-properties.md#id).
|
|
18
|
+
|
|
19
|
+
The name comes from the **merged** config, so a subclass that extends a component and forgets to rename registers under the name it inherited — and collides — rather than under `undefined`.
|
|
20
|
+
|
|
21
|
+
## `config.refs`
|
|
22
|
+
|
|
23
|
+
The refs the component declares. A list ref keeps its `[]`:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { Base } from '@studiometa/js-toolkit';
|
|
27
|
+
|
|
28
|
+
class Tabs extends Base {
|
|
29
|
+
static config = {
|
|
30
|
+
name: 'Tabs',
|
|
31
|
+
refs: ['list', 'tabs[]', 'panels[]'],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
See [Refs](../guide/introduction/managing-refs.md) and [`data-ref`](html/data-ref.md).
|
|
37
|
+
|
|
38
|
+
## `config.options`
|
|
39
|
+
|
|
40
|
+
Each entry is a type, a tuple of types, or an object with `type` and `default`:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import { Base } from '@studiometa/js-toolkit';
|
|
44
|
+
|
|
45
|
+
class Slider extends Base {
|
|
46
|
+
static config = {
|
|
47
|
+
name: 'Slider',
|
|
48
|
+
options: {
|
|
49
|
+
label: String, // short form
|
|
50
|
+
speed: { type: Number, default: 1 }, // primitive default
|
|
51
|
+
loop: { type: Boolean, default: true },
|
|
52
|
+
tween: { type: Object, default: () => ({}) }, // factory required
|
|
53
|
+
offset: [Number, Array], // a union, in order
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The five option types are `String`, `Number`, `Boolean`, `Array` and `Object`. `Function` is not one, which is what makes a function `default` unambiguously a factory.
|
|
60
|
+
|
|
61
|
+
See [Options](../guide/introduction/managing-options.md) and [`data-option-<name>`](html/data-option.md).
|
|
62
|
+
|
|
63
|
+
## `config.components`
|
|
64
|
+
|
|
65
|
+
The declared **family**. It does two jobs, and neither is ownership:
|
|
66
|
+
|
|
67
|
+
1. it registers those names when this component registers, so one `registerComponent()` call covers a whole tree;
|
|
68
|
+
2. it gives the name set that `on<Child><Event>` resolution needs.
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
static config = {
|
|
72
|
+
name: 'Accordion',
|
|
73
|
+
components: {
|
|
74
|
+
AccordionItem, // a class
|
|
75
|
+
Icon: () => import('./Icon.js'), // a thunk — its own chunk
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- **The key supplies the name**, so a lazy child is a name the registry knows with nothing downloaded.
|
|
81
|
+
- A thunk is deferred rather than resolved, and becomes a lazy entry of the same registry.
|
|
82
|
+
- **First wins, quietly.** Several parents declaring the same lazy child is the normal case.
|
|
83
|
+
- A value written with `class` that does not extend `Base` is reported as `component.invalid-family-declaration`, where it is declared.
|
|
84
|
+
|
|
85
|
+
See [Autoloading](../guide/going-further/autoloading.md).
|
|
86
|
+
|
|
87
|
+
## `config.mountStrategy`
|
|
88
|
+
|
|
89
|
+
The component's default answer to _when_. Any element overrides it with `data-mount`:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
import { Base } from '@studiometa/js-toolkit';
|
|
93
|
+
|
|
94
|
+
class Map extends Base {
|
|
95
|
+
static config = {
|
|
96
|
+
name: 'Map',
|
|
97
|
+
mountStrategy: 'visible',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
See [Mount strategies](../guide/going-further/mount-strategies.md) and [`data-mount`](html/data-mount.md).
|
|
103
|
+
|
|
104
|
+
## Merging along the prototype chain
|
|
105
|
+
|
|
106
|
+
`$config` walks the prototype chain and merges every config it finds:
|
|
107
|
+
|
|
108
|
+
| Key | Merge rule |
|
|
109
|
+
| --------------- | --------------------------- |
|
|
110
|
+
| `refs` | union |
|
|
111
|
+
| `options` | entry by entry |
|
|
112
|
+
| `components` | entry by entry |
|
|
113
|
+
| `name` | the most derived class wins |
|
|
114
|
+
| `mountStrategy` | the most derived class wins |
|
|
115
|
+
|
|
116
|
+
A subclass that states a `components` key again wins for **that key only**.
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
import { Base } from '@studiometa/js-toolkit';
|
|
120
|
+
|
|
121
|
+
class AbstractControl extends Base {
|
|
122
|
+
static config = {
|
|
123
|
+
name: 'AbstractControl',
|
|
124
|
+
refs: ['button'],
|
|
125
|
+
options: { label: String },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
class NavigationControl extends AbstractControl {
|
|
130
|
+
static config = {
|
|
131
|
+
name: 'NavigationControl',
|
|
132
|
+
refs: ['compass'], // merged: ['button', 'compass']
|
|
133
|
+
options: { showCompass: Boolean }, // merged with `label`
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
An intermediate class should declare `static config: BaseConfig`, or let TypeScript infer a literal type every subclass matches.
|
|
139
|
+
|
|
140
|
+
**The registry reads the merged config too**, before any instance exists, through `resolveConfig()`. That is how it knows the mount strategy of a pair, the family to register, and the name a class registers under.
|
|
141
|
+
|
|
142
|
+
## There is no `withExtraConfig()`
|
|
143
|
+
|
|
144
|
+
To extend a component with a different config, declare a class. To extend one you cannot edit, do it in expression position:
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
registerComponent(
|
|
148
|
+
class extends Vendor {
|
|
149
|
+
static config = { name: 'CompactVendor', options: { compact: Boolean } };
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## `@component()`
|
|
155
|
+
|
|
156
|
+
The decorator writes `static config` and calls `registerComponent()` in one step, and the two forms merge:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { Base, component } from '@studiometa/js-toolkit';
|
|
160
|
+
|
|
161
|
+
@component({ name: 'Slider', refs: ['next'] })
|
|
162
|
+
class Slider extends Base {
|
|
163
|
+
static config = { name: 'Slider', options: { speed: Number } };
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
They merge in a class initializer, which runs after the fields and inside the class definition, so `registerComponent()` reads the finished config. A key both sides declare differently is reported as `component.config-conflict`.
|
|
168
|
+
|
|
169
|
+
See [`@component`](decorators/component.md).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# createContext
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
createContext<T = unknown>(description?: string): ContextKey<T>
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Creates a typed injection key.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createContext, type Signal } from '@studiometa/js-toolkit';
|
|
13
|
+
|
|
14
|
+
interface SliderApi {
|
|
15
|
+
state: Signal<{ index: number }>;
|
|
16
|
+
goNext(): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const SliderContext = createContext<SliderApi>('slider');
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Parameters**
|
|
23
|
+
|
|
24
|
+
- `description` (`string`, optional) — for debugging only.
|
|
25
|
+
|
|
26
|
+
**Return value**
|
|
27
|
+
|
|
28
|
+
- `ContextKey<T>` — an opaque value carrying the phantom type `T`.
|
|
29
|
+
|
|
30
|
+
## Why a value and not a string
|
|
31
|
+
|
|
32
|
+
**The identity of the key is what resolves.** Two keys with the same description are two different keys, so nothing collides — and the type parameter is what makes `$inject(key)` give a typed value with no cast at the call site.
|
|
33
|
+
|
|
34
|
+
Declare the key beside the interface it carries, and export both:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
// slider-context.ts
|
|
38
|
+
export interface SliderApi { … }
|
|
39
|
+
export const SliderContext = createContext<SliderApi>('slider');
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Provider and consumer then import one module and agree by construction.
|
|
43
|
+
|
|
44
|
+
## The default is `unknown`
|
|
45
|
+
|
|
46
|
+
`createContext('name')` with no type parameter gives `ContextKey<unknown>`, so `$inject()` resolves with `unknown` and the consumer has to narrow. Give it the type.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# createGroup
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
createGroup<T extends GroupMember = GroupMember>(): Group<T>
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
A membership set whose members list is a reactive value.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
interface Group<T extends GroupMember = GroupMember> {
|
|
11
|
+
readonly members: Signal<readonly T[]>;
|
|
12
|
+
join(member: T): () => void;
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
A `GroupMember` is anything with a `readonly $el: Element` — which every `Base` instance is.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
The coordinator owns the membership and gives out the ways in:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { Base, createContext, createGroup, type Signal } from '@studiometa/js-toolkit';
|
|
24
|
+
|
|
25
|
+
interface GroupApi {
|
|
26
|
+
members: Signal<readonly Base[]>;
|
|
27
|
+
join(peer: Base): () => void;
|
|
28
|
+
open(peer: Base): void;
|
|
29
|
+
}
|
|
30
|
+
const DisclosureGroupContext = createContext<GroupApi>('disclosure-group');
|
|
31
|
+
class DisclosureGroup extends Base {
|
|
32
|
+
static config = { name: 'DisclosureGroup' };
|
|
33
|
+
|
|
34
|
+
#peers = createGroup<Base>();
|
|
35
|
+
|
|
36
|
+
api = this.$provide(DisclosureGroupContext, {
|
|
37
|
+
members: this.#peers.members, // the members to read, in document order
|
|
38
|
+
join: (peer: Base) => this.#peers.join(peer), // returns the leave function
|
|
39
|
+
open: (peer: Base) => this.open(peer), // the invariant stays here
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
mounted() {
|
|
43
|
+
// The membership is a value: re-check the invariant on each change.
|
|
44
|
+
return this.#peers.members.subscribe((members) => this.enforce(members));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
open(peer: Base) {}
|
|
48
|
+
|
|
49
|
+
enforce(members: readonly Base[]) {}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A member joins the nearest group whenever that group appears — see [`subscribeContext()`](subscribeContext.md).
|
|
54
|
+
|
|
55
|
+
## What it does, and what it does not
|
|
56
|
+
|
|
57
|
+
- **`join()` returns its own `leave`**, so a member that moves to a nearer group leaves the old one first.
|
|
58
|
+
- **The membership is a value.** A coordinator subscribes to it and re-checks its invariant on each change, rather than being called back on a mutation.
|
|
59
|
+
- **Document order is the tie-breaker**, so the markup decides which peer keeps its state.
|
|
60
|
+
- **Nothing sweeps disconnected members.** The teardown of the member removes it.
|
|
61
|
+
- **It names no group and resolves no scope.** Scope comes from nearest-provider-wins, so a nested group takes its own members only.
|
|
62
|
+
|
|
63
|
+
The helper holds a `Set` and a [`Signal`](signal.md), and nothing else. That is the whole implementation.
|
|
64
|
+
|
|
65
|
+
## It replaces `withGroup`
|
|
66
|
+
|
|
67
|
+
`withGroup` is not ported. A group is a provided value now, so its scope is the provider's subtree rather than a name in a global table — and a nested group is a nested provider, with nothing to configure.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Shared state
|
|
2
|
+
|
|
3
|
+
provide/inject in core, with the shape of Vue and the mechanics of the WICG context protocol.
|
|
4
|
+
|
|
5
|
+
## The functions
|
|
6
|
+
|
|
7
|
+
| Function | Does |
|
|
8
|
+
| -------------------------------------------------------------- | --------------------------------------------- |
|
|
9
|
+
| [`createContext(description?)`](createContext.md) | a typed key |
|
|
10
|
+
| [`signal(initialValue)`](signal.md) | a reactive value |
|
|
11
|
+
| [`provideContext(el, key, value)`](provideContext.md) | provide on an element |
|
|
12
|
+
| [`provideRootContext(key, create)`](provideRootContext.md) | provide page-wide, created at most once |
|
|
13
|
+
| [`injectContext(el, key)`](injectContext.md) | a promise for the nearest value |
|
|
14
|
+
| [`injectContextSync(el, key)`](injectContextSync.md) | the nearest value, or `undefined` |
|
|
15
|
+
| [`subscribeContext(el, key, cb)`](subscribeContext.md) | every answer, as providers come and go |
|
|
16
|
+
| [`createGroup()`](createGroup.md) | a membership set with a reactive members list |
|
|
17
|
+
|
|
18
|
+
From inside a component, `$provide()`, `$inject()` and `$injectSync()` are the same three, with the element filled in. The [`@provide`](../decorators/provide.md) and [`@inject`](../decorators/inject.md) decorators are field sugar over them.
|
|
19
|
+
|
|
20
|
+
## The model
|
|
21
|
+
|
|
22
|
+
- **The injection key is typed**, so strings cannot collide.
|
|
23
|
+
- **The scope is the subtree**, and the nearest provider wins.
|
|
24
|
+
- **The value is provided as it is.** Nothing is wrapped, so the type of the key is the contract from end to end. A reactive value is a provided `Signal`. A command surface is a provided object.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { Base, createContext, signal, type Signal } from '@studiometa/js-toolkit';
|
|
28
|
+
|
|
29
|
+
interface SliderApi {
|
|
30
|
+
state: Signal<{ index: number; total: number }>;
|
|
31
|
+
goNext(): void;
|
|
32
|
+
}
|
|
33
|
+
const SliderContext = createContext<SliderApi>('slider');
|
|
34
|
+
class Slider extends Base {
|
|
35
|
+
static config = { name: 'Slider' };
|
|
36
|
+
|
|
37
|
+
api = this.$provide(SliderContext, {
|
|
38
|
+
state: signal({ index: 0, total: 0 }), // what changes
|
|
39
|
+
goNext: () => {}, // what a control can command
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Two ways to ask
|
|
45
|
+
|
|
46
|
+
| Form | Resolves | When nothing provides |
|
|
47
|
+
| ------------------ | --------------------------------- | ----------------------------------------------------- |
|
|
48
|
+
| `$inject(key)` | a promise, awaited in `mounted()` | it never settles: a missing provider means "not yet". |
|
|
49
|
+
| `$injectSync(key)` | the value, synchronously | `undefined`: the caller falls back or does nothing. |
|
|
50
|
+
|
|
51
|
+
The pending request of the async form is **unmount-scoped**. A new mount runs `mounted()` again and asks again. The `@inject` field decorator asks once, at construction; a consumer that can wait through several cycles calls `$inject()` from `mounted()`.
|
|
52
|
+
|
|
53
|
+
## The mechanics
|
|
54
|
+
|
|
55
|
+
The consumer dispatches a bubbling, **module-private** `js-toolkit:context:request` event carrying a key, a callback and a subscription marker. It is deliberately not part of public `EVENTS`.
|
|
56
|
+
|
|
57
|
+
The nearest **mounted** provider answers and stops propagation. `provideContext()` replays the requests that have no first answer yet, which is what makes mount order irrelevant. `injectContext()` and `$inject()` are one-shot.
|
|
58
|
+
|
|
59
|
+
Duplicate bundles share the basic provider and pending-request state through the `context` shared-runtime slot at schema revision 2. The optional owner map, weak index and listener flag use the `context-subscription` slot at revision 1 — and `context.ts` and the `Base` graph import none of that optional state.
|
|
60
|
+
|
|
61
|
+
## Scopes
|
|
62
|
+
|
|
63
|
+
`$provide()` in a field initializer is **instance-scoped**: it is never released and dies with the element. A component whose declaration is withdrawn keeps providing until its element goes.
|
|
64
|
+
|
|
65
|
+
A **root** provider cannot be disposed and it outlives the instance that asked first, because it is page state. `withGroup` is not ported.
|
|
66
|
+
|
|
67
|
+
## Read next
|
|
68
|
+
|
|
69
|
+
[Shared state](../../guide/going-further/sharing-state.md) walks the whole pattern, from a coordinator's provided API to a member joining the nearest group.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# injectContext
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
injectContext<T>(el: Element, key: ContextKey<T>): { promise: Promise<T>; cancel: () => void }
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Asks the nearest provider for a value, once.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createContext, injectContext } from '@studiometa/js-toolkit';
|
|
13
|
+
|
|
14
|
+
const Key = createContext<number>('answer');
|
|
15
|
+
const el = document.body;
|
|
16
|
+
const { promise, cancel } = injectContext(el, Key);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Return value**
|
|
20
|
+
|
|
21
|
+
- `promise` — resolves with the nearest provided value.
|
|
22
|
+
- `cancel` — withdraws the request.
|
|
23
|
+
|
|
24
|
+
## It never settles when nothing provides
|
|
25
|
+
|
|
26
|
+
**A missing provider means "not yet", not "no".** The request stays pending, and it resolves when a provider appears and replays it — which is what makes mount order irrelevant.
|
|
27
|
+
|
|
28
|
+
If you need an answer now, or none, use [`injectContextSync()`](injectContextSync.md).
|
|
29
|
+
|
|
30
|
+
## From a component
|
|
31
|
+
|
|
32
|
+
`$inject()` is the same call with the element filled in, and it returns the promise directly:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { Base, createContext, type Signal } from '@studiometa/js-toolkit';
|
|
36
|
+
|
|
37
|
+
const CountContext = createContext<Signal<number>>('count');
|
|
38
|
+
class Output extends Base {
|
|
39
|
+
static config = { name: 'Output' };
|
|
40
|
+
|
|
41
|
+
async mounted() {
|
|
42
|
+
const count = await this.$inject(CountContext);
|
|
43
|
+
return count.subscribe((value) => {
|
|
44
|
+
this.$el.textContent = String(value);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**The pending request is unmount-scoped**
|
|
51
|
+
|
|
52
|
+
`$unmount()` cancels it, and a new mount runs `mounted()` again and asks again. That is why `mounted()` is the right place: a component that outlives several cycles asks once per cycle, and never holds a request for a scope it has left.
|
|
53
|
+
|
|
54
|
+
The [`@inject`](../decorators/inject.md) field decorator asks once, at construction, instead.
|
|
55
|
+
|
|
56
|
+
## It is one-shot
|
|
57
|
+
|
|
58
|
+
`injectContext()` and `$inject()` resolve with the **first** answer and stop. To follow providers as they come and go, use [`subscribeContext()`](subscribeContext.md).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# injectContextSync
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
injectContextSync<T>(el: Element, key: ContextKey<T>): T | undefined
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
The nearest provided value, synchronously, or `undefined`.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createContext, injectContextSync } from '@studiometa/js-toolkit';
|
|
13
|
+
|
|
14
|
+
const Key = createContext<number>('answer');
|
|
15
|
+
const el = document.body;
|
|
16
|
+
const value = injectContextSync(el, Key);
|
|
17
|
+
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
// no provider — fall back, or do nothing
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## When to reach for it
|
|
24
|
+
|
|
25
|
+
When the answer is **optional and the caller has a fallback**. `undefined` means "no provider right now", which is a different statement from `$inject()`'s "not yet".
|
|
26
|
+
|
|
27
|
+
The canonical pair is a look-up-or-create:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createContext, injectContextSync, provideRootContext } from '@studiometa/js-toolkit';
|
|
31
|
+
|
|
32
|
+
const DataChannels = createContext<Map<string, unknown>>('channels');
|
|
33
|
+
const el = document.body;
|
|
34
|
+
const channels =
|
|
35
|
+
injectContextSync(el, DataChannels) ?? provideRootContext(DataChannels, () => new Map());
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## From a component
|
|
39
|
+
|
|
40
|
+
`$injectSync(key)` is the same call with the element filled in.
|
|
41
|
+
|
|
42
|
+
**It answers for right now**
|
|
43
|
+
|
|
44
|
+
A provider that mounts later is not seen, because there is no request left pending. If mount order is not something you control, use [`injectContext()`](injectContext.md) or [`subscribeContext()`](subscribeContext.md).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# provideContext
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
provideContext<T>(el: Element, key: ContextKey<T>, value: T): { value: T; dispose: () => void }
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Provides a value to the subtree of `el`.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createContext, provideContext } from '@studiometa/js-toolkit';
|
|
13
|
+
|
|
14
|
+
const Key = createContext<number>('answer');
|
|
15
|
+
const el = document.body;
|
|
16
|
+
const { value, dispose } = provideContext(el, Key, 42);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Parameters**
|
|
20
|
+
|
|
21
|
+
- `el` — the element that owns the scope.
|
|
22
|
+
- `key` — a [`ContextKey`](createContext.md).
|
|
23
|
+
- `value` — provided **as it is**. Nothing is wrapped.
|
|
24
|
+
|
|
25
|
+
**Return value**
|
|
26
|
+
|
|
27
|
+
- `value` — the value, so the call can be an expression.
|
|
28
|
+
- `dispose` — stops answering for this key on this element.
|
|
29
|
+
|
|
30
|
+
## From a component
|
|
31
|
+
|
|
32
|
+
`$provide()` is the same call with the element filled in, and it returns the value directly so it reads as a field initializer:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { Base, createContext, signal, type Signal } from '@studiometa/js-toolkit';
|
|
36
|
+
|
|
37
|
+
const CountContext = createContext<Signal<number>>('count');
|
|
38
|
+
class Counter extends Base {
|
|
39
|
+
static config = { name: 'Counter' };
|
|
40
|
+
|
|
41
|
+
count = this.$provide(CountContext, signal(0));
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**`$provide()` is instance-scoped**
|
|
46
|
+
|
|
47
|
+
It is never released, and it dies with the instance or its element. A component whose declaration is withdrawn keeps providing until its element goes — which is why the call belongs in the constructor or a field initializer, not in `mounted()`.
|
|
48
|
+
|
|
49
|
+
## Nearest wins
|
|
50
|
+
|
|
51
|
+
The scope is the subtree, and a nearer provider shadows a further one by answering first and stopping propagation.
|
|
52
|
+
|
|
53
|
+
## It replays pending requests
|
|
54
|
+
|
|
55
|
+
A consumer that asked before any provider existed has a pending request with no first answer. `provideContext()` **replays those requests**, which is what makes mount order irrelevant: a child that mounts before its coordinator still gets its answer when the coordinator arrives.
|
|
56
|
+
|
|
57
|
+
Only a **mounted** provider answers.
|
|
58
|
+
|
|
59
|
+
## Page-wide
|
|
60
|
+
|
|
61
|
+
For state that belongs to the page rather than to a subtree, use [`provideRootContext()`](provideRootContext.md).
|