@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.
- package/README.md +10 -0
- package/dist/utils/index.d.ts +2 -2
- 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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @provide
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
provide<T>(key: ContextKey<T>): ValueDecorator<T>
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Field sugar over [`$provide()`](../instance-methods.md#provide).
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import {
|
|
13
|
+
Base,
|
|
14
|
+
component,
|
|
15
|
+
createContext,
|
|
16
|
+
provide,
|
|
17
|
+
signal,
|
|
18
|
+
type Signal,
|
|
19
|
+
} from '@studiometa/js-toolkit';
|
|
20
|
+
|
|
21
|
+
const CountContext = createContext<Signal<number>>('count');
|
|
22
|
+
@component({ name: 'Counter' })
|
|
23
|
+
class Counter extends Base {
|
|
24
|
+
@provide(CountContext)
|
|
25
|
+
count = signal(0);
|
|
26
|
+
|
|
27
|
+
increment() {
|
|
28
|
+
this.count.value += 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The field's value is provided to the subtree, **as it is**. Nothing is wrapped, so the type of the key is the contract from end to end.
|
|
34
|
+
|
|
35
|
+
## It is instance-scoped
|
|
36
|
+
|
|
37
|
+
Like `$provide()`, it is never released and dies with the element. A component whose declaration is withdrawn keeps providing until its element goes.
|
|
38
|
+
|
|
39
|
+
## A plain field or an `accessor`
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import {
|
|
43
|
+
Base,
|
|
44
|
+
component,
|
|
45
|
+
createContext,
|
|
46
|
+
provide,
|
|
47
|
+
signal,
|
|
48
|
+
type Signal,
|
|
49
|
+
} from '@studiometa/js-toolkit';
|
|
50
|
+
|
|
51
|
+
const CountContext = createContext<Signal<number>>('count');
|
|
52
|
+
@component({ name: 'Counter' })
|
|
53
|
+
class Counter extends Base {
|
|
54
|
+
@provide(CountContext) a = signal(0);
|
|
55
|
+
@provide(CountContext) accessor b = signal(0);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## The function form
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { Base, createContext, signal, type Signal } from '@studiometa/js-toolkit';
|
|
63
|
+
|
|
64
|
+
const CountContext = createContext<Signal<number>>('count');
|
|
65
|
+
class Counter extends Base {
|
|
66
|
+
static config = { name: 'Counter' };
|
|
67
|
+
|
|
68
|
+
count = this.$provide(CountContext, signal(0));
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Identical behaviour, no build step — and `$provide()` returns the value, which is why the field initializer reads the same way.
|
|
73
|
+
|
|
74
|
+
## See also
|
|
75
|
+
|
|
76
|
+
- [`@inject`](inject.md) — the other half
|
|
77
|
+
- [Shared state](../../guide/going-further/sharing-state.md)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @read and @write
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
read: (value: VoidMethod, context: ClassMethodDecoratorContext) => VoidMethod;
|
|
5
|
+
write: (value: VoidMethod, context: ClassMethodDecoratorContext) => VoidMethod;
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
Runs the method body in the frame's `read` or `write` phase, cancelled on unmount. They are sugar over [`$read()`](../instance-methods.md#read-and-write) and `$write()`.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { Base, component, read, write } from '@studiometa/js-toolkit';
|
|
14
|
+
|
|
15
|
+
@component({ name: 'Measure' })
|
|
16
|
+
class Measure extends Base {
|
|
17
|
+
#height = 0;
|
|
18
|
+
|
|
19
|
+
@read
|
|
20
|
+
measure() {
|
|
21
|
+
this.#height = this.$el.scrollHeight;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@write
|
|
25
|
+
apply() {
|
|
26
|
+
this.$el.style.setProperty('--height', `${this.#height}px`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
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.
|
|
32
|
+
|
|
33
|
+
## They are leaf-method sugar: the phase belongs to the call site
|
|
34
|
+
|
|
35
|
+
**Decorate a method nobody overrides**
|
|
36
|
+
|
|
37
|
+
A phase decorator returns a wrapper around the method it decorates, and that wrapper is a property of **that class**. A subclass that overrides the method defines its own, undecorated, and `this.method()` resolves to it — so the base's scheduling disappears and the body runs in whatever phase the caller was in.
|
|
38
|
+
|
|
39
|
+
A **template method** — a base that schedules work its subclasses implement — schedules at the call site instead:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
// in the base, where the call is
|
|
43
|
+
state.subscribe((value) => this.$write(() => this.update(value)));
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The alternative, dispatching a decorated method through something a subclass cannot replace, was refused: it would make a decorator's behaviour depend on inheritance depth, which nothing else in v4 does, to buy a convenience no consumer has asked for.
|
|
47
|
+
|
|
48
|
+
## Stacking with `@on`
|
|
49
|
+
|
|
50
|
+
The skip is keyed by the method name, so the two stack in either order — and **the order decides what the listener calls**:
|
|
51
|
+
|
|
52
|
+
| Written | Schedules |
|
|
53
|
+
| ------------------------- | ----------------------- |
|
|
54
|
+
| `@on(...)` above `@write` | the body of the handler |
|
|
55
|
+
| `@write` above `@on(...)` | direct calls only |
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { Base, component, on, write } from '@studiometa/js-toolkit';
|
|
59
|
+
@component({ name: 'Demo' })
|
|
60
|
+
class Demo extends Base {
|
|
61
|
+
@on('click')
|
|
62
|
+
@write
|
|
63
|
+
paint() {}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Write `@read` and `@write` closest to the method body.**
|
|
68
|
+
|
|
69
|
+
## The function form
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import { Base } from '@studiometa/js-toolkit';
|
|
73
|
+
|
|
74
|
+
class Measure extends Base {
|
|
75
|
+
static config = { name: 'Measure' };
|
|
76
|
+
|
|
77
|
+
#height = 0;
|
|
78
|
+
|
|
79
|
+
measure() {
|
|
80
|
+
return this.$read(() => {
|
|
81
|
+
this.#height = this.$el.scrollHeight;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
apply() {
|
|
86
|
+
return this.$write(() => {
|
|
87
|
+
this.$el.style.setProperty('--height', `${this.#height}px`);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The function form also gives you the [`ScheduledTask`](../scheduler/defaultScheduler.md) handle — its promise and its `cancel()` — which the decorator discards.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# DIAGNOSTICS
|
|
2
|
+
|
|
3
|
+
A deeply frozen object of every code the framework reports. `ToolkitDiagnosticCode` is the exact union of its strings, plus any `'<namespace>.<name>'` a consumer reports.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import { DIAGNOSTICS } from '@studiometa/js-toolkit';
|
|
7
|
+
|
|
8
|
+
DIAGNOSTICS.component.loadFailed; // 'component.load-failed'
|
|
9
|
+
DIAGNOSTICS.responsive.unknownBreakpoint; // 'responsive.unknown-breakpoint'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## `attribute`
|
|
13
|
+
|
|
14
|
+
| Key | Code | Reported when |
|
|
15
|
+
| ------------------ | ----------------------------- | --------------------------------------------------------------------------- |
|
|
16
|
+
| `unknownQualifier` | `attribute.unknown-qualifier` | a watched namespace with a finite vocabulary gets an unknown qualifier head |
|
|
17
|
+
|
|
18
|
+
## `callback`
|
|
19
|
+
|
|
20
|
+
Every one of these is **reported and continued**: the failing callback is skipped, the others run.
|
|
21
|
+
|
|
22
|
+
| Key | Code |
|
|
23
|
+
| -------------------------------- | -------------------------------------------- |
|
|
24
|
+
| `signalFailed` | `callback.signal-failed` |
|
|
25
|
+
| `contextSubscriptionFailed` | `callback.context-subscription-failed` |
|
|
26
|
+
| `contextTeardownFailed` | `callback.context-teardown-failed` |
|
|
27
|
+
| `attributeWatcherFailed` | `callback.attribute-watcher-failed` |
|
|
28
|
+
| `serviceFailed` | `callback.service-failed` |
|
|
29
|
+
| `schedulerTickFailed` | `callback.scheduler-tick-failed` |
|
|
30
|
+
| `scheduledTaskFailed` | `callback.scheduled-task-failed` |
|
|
31
|
+
| `domUpdateRunnerFailed` | `callback.dom-update-runner-failed` |
|
|
32
|
+
| `extendableEventExtensionFailed` | `callback.extendable-event-extension-failed` |
|
|
33
|
+
|
|
34
|
+
A scheduled task also **rejects its own promise** with the same value.
|
|
35
|
+
|
|
36
|
+
## `component`
|
|
37
|
+
|
|
38
|
+
| Key | Code | Reported when |
|
|
39
|
+
| -------------------------- | -------------------------------------- | ----------------------------------------------------------------- |
|
|
40
|
+
| `loadFailed` | `component.load-failed` | a lazy import fails — once per name, never retried |
|
|
41
|
+
| `mountFailed` | `component.mount-failed` | a mount fails |
|
|
42
|
+
| `lifecycleFailed` | `component.lifecycle-failed` | `mounted()` or `unmounted()` throws |
|
|
43
|
+
| `invalidMountStrategy` | `component.invalid-mount-strategy` | a `data-mount` value is unusable — the component stays unmounted |
|
|
44
|
+
| `invalidFamilyDeclaration` | `component.invalid-family-declaration` | a `config.components` value is neither a `Base` class nor a thunk |
|
|
45
|
+
| `configConflict` | `component.config-conflict` | `@component` and `static config` declare one key differently |
|
|
46
|
+
|
|
47
|
+
## `event`
|
|
48
|
+
|
|
49
|
+
| Key | Code | Reported when |
|
|
50
|
+
| -------------------- | ---------------------------- | ---------------------------------------------- |
|
|
51
|
+
| `invalidEmitPayload` | `event.invalid-emit-payload` | `$emit()` gets a payload that is not an object |
|
|
52
|
+
|
|
53
|
+
The event still dispatches.
|
|
54
|
+
|
|
55
|
+
## `manifest`
|
|
56
|
+
|
|
57
|
+
| Key | Code | Reported when |
|
|
58
|
+
| ---------------- | -------------------------- | ----------------------------------------- |
|
|
59
|
+
| `duplicateToken` | `manifest.duplicate-token` | a manifest declares a token already owned |
|
|
60
|
+
|
|
61
|
+
## `option`
|
|
62
|
+
|
|
63
|
+
| Key | Code | Reported when |
|
|
64
|
+
| ---------------- | ------------------------ | ---------------------------------------------------------- |
|
|
65
|
+
| `literalDefault` | `option.literal-default` | an `Array` or `Object` default is a literal, not a factory |
|
|
66
|
+
|
|
67
|
+
The value is then used as declared and **shared** between instances.
|
|
68
|
+
|
|
69
|
+
## `protocol`
|
|
70
|
+
|
|
71
|
+
| Key | Code | Reported when |
|
|
72
|
+
| -------------------- | ------------------------------- | ------------------------------------------------------------------- |
|
|
73
|
+
| `lateRegistration` | `protocol.late-registration` | a negotiated-event listener calls `wrap`/`waitUntil` after dispatch |
|
|
74
|
+
| `unappliedDomUpdate` | `protocol.unapplied-dom-update` | a claimed runner resolves without calling `apply` |
|
|
75
|
+
|
|
76
|
+
In both cases the change still happens.
|
|
77
|
+
|
|
78
|
+
## `ref`
|
|
79
|
+
|
|
80
|
+
| Key | Code | Reported when |
|
|
81
|
+
| ---------- | -------------- | ------------------------------------------------------------------------------------ |
|
|
82
|
+
| `mismatch` | `ref.mismatch` | `config.refs` and `data-ref` disagree on the `[]` suffix — once per instance and ref |
|
|
83
|
+
|
|
84
|
+
## `registry`
|
|
85
|
+
|
|
86
|
+
| Key | Code | Reported when |
|
|
87
|
+
| ------------------ | ----------------------------- | ----------------------------------------------------- |
|
|
88
|
+
| `conflict` | `registry.conflict` | a name is registered twice — the second is ignored |
|
|
89
|
+
| `lazyNameMismatch` | `registry.lazy-name-mismatch` | a loaded class's `config.name` differs from its token |
|
|
90
|
+
|
|
91
|
+
## `responsive`
|
|
92
|
+
|
|
93
|
+
| Key | Code | Reported when |
|
|
94
|
+
| ------------------- | ------------------------------- | --------------------------------------------------------------------------- |
|
|
95
|
+
| `unknownBreakpoint` | `responsive.unknown-breakpoint` | a `:<suffix>` names no configured breakpoint — including the v3 list syntax |
|
|
96
|
+
|
|
97
|
+
## `scheduler`
|
|
98
|
+
|
|
99
|
+
| Key | Code | Reported when |
|
|
100
|
+
| ---------------------- | ---------------------------------- | ---------------------------------- |
|
|
101
|
+
| `backgroundPostFailed` | `scheduler.background-post-failed` | a background turn cannot be posted |
|
|
102
|
+
|
|
103
|
+
## `service`
|
|
104
|
+
|
|
105
|
+
| Key | Code | Reported when |
|
|
106
|
+
| --------------- | ------------------------ | ---------------------------------------------------------------------------------------------------- |
|
|
107
|
+
| `missingTarget` | `service.missing-target` | a caller-supplied mixin `target` resolver returns `undefined` or `null` — **no subscription starts** |
|
|
108
|
+
|
|
109
|
+
## `storage`
|
|
110
|
+
|
|
111
|
+
| Key | Code | Reported when |
|
|
112
|
+
| ------------------- | ---------------------------- | ------------------------------------------------ |
|
|
113
|
+
| `accessFailed` | `storage.access-failed` | a provider's area is refused or full |
|
|
114
|
+
| `serializeFailed` | `storage.serialize-failed` | serialization throws — **nothing is written** |
|
|
115
|
+
| `deserializeFailed` | `storage.deserialize-failed` | deserialization throws — the default is returned |
|
|
116
|
+
|
|
117
|
+
## `swap`
|
|
118
|
+
|
|
119
|
+
| Key | Code | Reported when |
|
|
120
|
+
| ------------- | ------------------- | --------------------------------------------------------------------- |
|
|
121
|
+
| `selfIgnored` | `swap.self-ignored` | `self` is passed with `append`/`prepend`, or content holds no element |
|
|
122
|
+
|
|
123
|
+
## Consumer codes
|
|
124
|
+
|
|
125
|
+
`ToolkitDiagnosticCode` also accepts any `` `${string}.${string}` ``, so a consumer reports on the same channel with its own namespace:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { warn } from '@studiometa/js-toolkit';
|
|
129
|
+
|
|
130
|
+
warn('carousel.no-slides', 'A Carousel with no slide does nothing.', { component: 'Carousel' });
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Import it on its own
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
import { DIAGNOSTICS } from '@studiometa/js-toolkit/DIAGNOSTICS';
|
|
137
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# EVENTS
|
|
2
|
+
|
|
3
|
+
A deeply frozen object of the events the framework itself dispatches, all namespaced `js-toolkit:`.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import { EVENTS } from '@studiometa/js-toolkit';
|
|
7
|
+
|
|
8
|
+
EVENTS.component.mounted; // 'js-toolkit:component:mounted'
|
|
9
|
+
EVENTS.component.unmounted; // 'js-toolkit:component:unmounted'
|
|
10
|
+
EVENTS.dom.update; // 'js-toolkit:dom:update'
|
|
11
|
+
EVENTS.diagnostic; // 'js-toolkit:diagnostic'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## The events
|
|
15
|
+
|
|
16
|
+
| Constant | Name | Dispatched from | Cancelable |
|
|
17
|
+
| ---------------------------- | -------------------------------- | ------------------------- | ---------- |
|
|
18
|
+
| `EVENTS.component.mounted` | `js-toolkit:component:mounted` | the element, **bubbling** | no |
|
|
19
|
+
| `EVENTS.component.unmounted` | `js-toolkit:component:unmounted` | `document` | no |
|
|
20
|
+
| `EVENTS.dom.update` | `js-toolkit:dom:update` | the given node, bubbling | no |
|
|
21
|
+
| `EVENTS.diagnostic` | `js-toolkit:diagnostic` | the element or `document` | **yes** |
|
|
22
|
+
|
|
23
|
+
See [Instance events](../instance-events.md) for the lifecycle pair, [`domUpdate()`](../dom/domUpdate.md) for the update event, and [Diagnostics](index.md) for the diagnostic.
|
|
24
|
+
|
|
25
|
+
## The convention
|
|
26
|
+
|
|
27
|
+
- **Public framework events** use this object and the `js-toolkit:` namespace.
|
|
28
|
+
- **Private framework transports** use module-local namespaced constants and **do not join `EVENTS`** — the context request is one.
|
|
29
|
+
- **Component events** are typed lower-kebab string literals declared through the props type's `$emits` key. They have no namespace: a component event is `open`, not `js-toolkit:open`.
|
|
30
|
+
- A payload is **one object** in `CustomEvent.detail`, or the platform value `null`.
|
|
31
|
+
- Diagnostic events are cancelable, so monitoring can suppress the default output only.
|
|
32
|
+
|
|
33
|
+
Only what a consumer can usefully listen to is exported. That is the whole rule for whether something belongs here.
|
|
34
|
+
|
|
35
|
+
## Import it on its own
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
import { EVENTS } from '@studiometa/js-toolkit/EVENTS';
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The generated subpath keeps the frozen object importable without the root barrel — which matters on a page with no build step, where a barrel import downloads the barrel's whole graph.
|
|
42
|
+
|
|
43
|
+
## `EVENTS.error` is removed
|
|
44
|
+
|
|
45
|
+
With **no alias**. `EVENTS.error`, `ToolkitErrorDetail` and `ToolkitErrorStage` are gone; `EVENTS.diagnostic` and the [`DIAGNOSTICS`](DIAGNOSTICS.md) codes replace them.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Diagnostics
|
|
2
|
+
|
|
3
|
+
Everything the framework recovers from is reported on **one** cancelable event, and nothing in core calls `console.warn()` or `console.error()` directly.
|
|
4
|
+
|
|
5
|
+
## The exports
|
|
6
|
+
|
|
7
|
+
- [`EVENTS`](EVENTS.md) — the framework event names
|
|
8
|
+
- [`DIAGNOSTICS`](DIAGNOSTICS.md) — every code
|
|
9
|
+
- [`reportDiagnostic(code, message, error, ctx?)`](reportDiagnostic.md) — report an error
|
|
10
|
+
- [`warn(code, message, ctx?)`](warn.md) — report a warning
|
|
11
|
+
|
|
12
|
+
From inside a component, `$warn()` and `$error()` fill the component name in.
|
|
13
|
+
|
|
14
|
+
## The detail
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
type ToolkitDiagnosticDetail =
|
|
18
|
+
| {
|
|
19
|
+
readonly severity: 'warning';
|
|
20
|
+
readonly code: ToolkitDiagnosticCode;
|
|
21
|
+
readonly message: string;
|
|
22
|
+
readonly component?: string;
|
|
23
|
+
readonly error?: never;
|
|
24
|
+
}
|
|
25
|
+
| {
|
|
26
|
+
readonly severity: 'error';
|
|
27
|
+
readonly code: ToolkitDiagnosticCode;
|
|
28
|
+
readonly message: string;
|
|
29
|
+
readonly component?: string;
|
|
30
|
+
readonly error: unknown;
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
An **error** detail requires the original caught value; a **warning** carries none. That is a union, not an optional field, so a listener that reads `detail.error` has already narrowed on `severity`.
|
|
35
|
+
|
|
36
|
+
## Listening
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { DIAGNOSTICS, EVENTS, type ToolkitDiagnosticDetail } from '@studiometa/js-toolkit';
|
|
40
|
+
|
|
41
|
+
declare function monitor(code: string, detail: ToolkitDiagnosticDetail): void;
|
|
42
|
+
document.addEventListener(EVENTS.diagnostic, (rawEvent) => {
|
|
43
|
+
const event = rawEvent as CustomEvent<ToolkitDiagnosticDetail>;
|
|
44
|
+
monitor(event.detail.code, event.detail);
|
|
45
|
+
|
|
46
|
+
if (event.detail.code === DIAGNOSTICS.responsive.unknownBreakpoint) {
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Every diagnostic starts on its relevant connected element, or on `document` when there is none, with `{ bubbles: true, composed: true, cancelable: true }`.
|
|
53
|
+
|
|
54
|
+
**Dispatch always happens before the default output.** An uncancelled warning calls `console.warn()` once with exactly `[js-toolkit:<code>] <message>`; an uncancelled error calls `reportError(detail.error)` with the original value. `preventDefault()` suppresses **that output only** and changes no framework decision.
|
|
55
|
+
|
|
56
|
+
## Read next
|
|
57
|
+
|
|
58
|
+
[Diagnostics](../../guide/going-further/handling-diagnostics.md).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# reportDiagnostic
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
reportDiagnostic(
|
|
5
|
+
code: ToolkitDiagnosticCode,
|
|
6
|
+
message: string,
|
|
7
|
+
error: unknown,
|
|
8
|
+
context?: { component?: string; target?: Element },
|
|
9
|
+
): CustomEvent<ToolkitDiagnosticDetail>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Reports an **error** on the diagnostic channel. The original caught value is required.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { reportDiagnostic } from '@studiometa/js-toolkit';
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
JSON.parse('{');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
reportDiagnostic('carousel.bad-config', 'The config attribute is not valid JSON.', error, {
|
|
23
|
+
component: 'Carousel',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Parameters**
|
|
29
|
+
|
|
30
|
+
- `code` — a namespaced string. Core codes come from [`DIAGNOSTICS`](DIAGNOSTICS.md); a consumer uses its own `'<namespace>.<name>'`.
|
|
31
|
+
- `message` — one sentence, and it should say what to do about it.
|
|
32
|
+
- `error` — **required.** The original value, passed through untouched.
|
|
33
|
+
- `context.component` — the reporting component's name.
|
|
34
|
+
- `context.target` — the element to dispatch from. Defaults to `document`.
|
|
35
|
+
|
|
36
|
+
**Return value**
|
|
37
|
+
|
|
38
|
+
- the dispatched event, so a caller can read `defaultPrevented`.
|
|
39
|
+
|
|
40
|
+
## What happens
|
|
41
|
+
|
|
42
|
+
1. The event is dispatched — **always before any output** — with `{ bubbles: true, composed: true, cancelable: true }`.
|
|
43
|
+
2. If nothing cancelled it, `reportError(detail.error)` is called with the original value.
|
|
44
|
+
|
|
45
|
+
`preventDefault()` suppresses that call and nothing else.
|
|
46
|
+
|
|
47
|
+
## From a component
|
|
48
|
+
|
|
49
|
+
`$error(code, message, error)` is the same call with the component name filled in:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
this.$error('carousel.bad-config', 'The config attribute is not valid JSON.', error);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## When to report and when to throw
|
|
56
|
+
|
|
57
|
+
| The failure is | Do |
|
|
58
|
+
| ---------------------------- | ------------------- |
|
|
59
|
+
| something you recovered from | report it |
|
|
60
|
+
| the caller's to handle | **throw or reject** |
|
|
61
|
+
|
|
62
|
+
Core follows the same rule: decorator and manifest-adapter misuse, shared-runtime incompatibility, service startup rollback, caller-owned teardown, `viewTransition()` and `swap()` all throw. A channel is for what was survived.
|
|
63
|
+
|
|
64
|
+
## See also
|
|
65
|
+
|
|
66
|
+
- [`warn()`](warn.md) — a warning, with no error value
|
|
67
|
+
- [`captureDiagnostics()`](../test/index.md) — reading the channel in a test
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# warn
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
warn(
|
|
5
|
+
code: ToolkitDiagnosticCode,
|
|
6
|
+
message: string,
|
|
7
|
+
context?: { component?: string; target?: Element },
|
|
8
|
+
): CustomEvent<ToolkitDiagnosticDetail>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Reports a **warning** on the diagnostic channel. A warning carries no error value — that is the difference from [`reportDiagnostic()`](reportDiagnostic.md), and it is a union in the type rather than an optional field.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { warn } from '@studiometa/js-toolkit';
|
|
17
|
+
|
|
18
|
+
warn('carousel.no-slides', 'A Carousel with no slide does nothing. Add `data-component="Slide"`.', {
|
|
19
|
+
component: 'Carousel',
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Return value**
|
|
24
|
+
|
|
25
|
+
- the dispatched event, so a caller can read `defaultPrevented`.
|
|
26
|
+
|
|
27
|
+
## What happens
|
|
28
|
+
|
|
29
|
+
1. The event is dispatched — **always before any output**.
|
|
30
|
+
2. If nothing cancelled it, `console.warn()` is called **once**, with exactly `[js-toolkit:<code>] <message>`:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
[js-toolkit:option.literal-default] Panel: the `tween` option default must be a factory function.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That exact format is what makes the console output greppable and what lets a listener reproduce it.
|
|
37
|
+
|
|
38
|
+
## From a component
|
|
39
|
+
|
|
40
|
+
`$warn(code, message)` fills the component name in:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
this.$warn('carousel.no-slides', 'A Carousel with no slide does nothing.');
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Deduplication
|
|
47
|
+
|
|
48
|
+
Core's own warnings are deduplicated by weak owner plus a misuse key, in a revisioned shared-runtime slot — so a misuse is reported once per instance, per element or per declaration, and it works across independently evaluated copies of the package **without retaining** instances, elements, declarations, runners or manifest inputs.
|
|
49
|
+
|
|
50
|
+
The public `warn()` does not deduplicate. Call it where the condition is detected once.
|
|
51
|
+
|
|
52
|
+
## Write a message that says what to do
|
|
53
|
+
|
|
54
|
+
The framework's own messages name the component, the thing that is wrong and the correction — `option.literal-default` names the option and the factory form to write instead. A warning a reader cannot act on is noise on a channel meant for monitoring.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Breakpoints
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
BREAKPOINTS: Readonly<Record<string, string>>
|
|
5
|
+
getBreakpoints(): Record<string, string>
|
|
6
|
+
setBreakpoints(next: Record<string, string>): void
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The named breakpoint set is the single source of truth for every responsive spelling in the framework.
|
|
10
|
+
|
|
11
|
+
## The defaults
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { BREAKPOINTS, getBreakpoints } from '@studiometa/js-toolkit';
|
|
15
|
+
|
|
16
|
+
getBreakpoints(); // the current set
|
|
17
|
+
BREAKPOINTS; // the defaults
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Name | Min width |
|
|
21
|
+
| ------ | --------- |
|
|
22
|
+
| `xxs` | `0rem` |
|
|
23
|
+
| `xs` | `30rem` |
|
|
24
|
+
| `s` | `48rem` |
|
|
25
|
+
| `m` | `64rem` |
|
|
26
|
+
| `l` | `80rem` |
|
|
27
|
+
| `xl` | `90rem` |
|
|
28
|
+
| `xxl` | `120rem` |
|
|
29
|
+
| `xxxl` | `160rem` |
|
|
30
|
+
|
|
31
|
+
These are the names every `data-component:<name>` and `data-option-<x>:<name>` suffix is checked against.
|
|
32
|
+
|
|
33
|
+
## Replacing them
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
import { setBreakpoints } from '@studiometa/js-toolkit';
|
|
37
|
+
|
|
38
|
+
setBreakpoints({
|
|
39
|
+
xs: '0rem',
|
|
40
|
+
s: '48rem',
|
|
41
|
+
m: '64rem',
|
|
42
|
+
l: '80rem',
|
|
43
|
+
xl: '96rem',
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`setBreakpoints()` is the single source of breakpoint truth, and calling it does three things at once:
|
|
48
|
+
|
|
49
|
+
1. **rebuilds the scoped attribute names** — every `data-component:<name>` and every `data-option-<x>:<name>` spelling;
|
|
50
|
+
2. **replaces that slice of the observer's `attributeFilter`**, draining the records of the previous filter first;
|
|
51
|
+
3. **emits at once** on [`useBreakpoint()`](../services/useBreakpoint.md), so a subscriber sees the new active name immediately.
|
|
52
|
+
|
|
53
|
+
## The values are in `rem`
|
|
54
|
+
|
|
55
|
+
And in a media query, `rem` resolves against the **initial** font size, not against the root element. That is deliberate: a breakpoint that moved when a component changed the root font size would not be a breakpoint.
|
|
56
|
+
|
|
57
|
+
## What uses them
|
|
58
|
+
|
|
59
|
+
| Feature | How |
|
|
60
|
+
| ------------------------------------------------------------------------------ | ---------------------------------------------- |
|
|
61
|
+
| [responsive options](../html/data-option.md#every-option-is-responsive) | `data-option-x:<name>`, cascading upwards |
|
|
62
|
+
| [responsive components](../html/data-component.md#responsive-declarations) | `data-component:<name>`, replacing not merging |
|
|
63
|
+
| [`useBreakpoint()`](../services/useBreakpoint.md) | the active name, as a service |
|
|
64
|
+
|
|
65
|
+
**A suffix that names no configured breakpoint** gives one `responsive.unknown-breakpoint` warning and is ignored — including the v3 list syntax `:xxs:xs:s`.
|
|
66
|
+
|
|
67
|
+
## What it costs
|
|
68
|
+
|
|
69
|
+
- The `MediaQueryList` objects are built **once**.
|
|
70
|
+
- The active name is **memoised for the length of one task**, through `utils/memo`. `setBreakpoints()` and the `change` handler of a running service clear that memo at once.
|
|
71
|
+
- A `matchMedia` subscription opens only for a component that declares [`option<Name>Changed()`](../methods-hooks-options.md); connected elements with a scoped `data-component` share **one** reference-counted subscription. A page with plain declarations and read-only options opens **none**.
|
|
72
|
+
|
|
73
|
+
The breakpoint service is part of the core graph on every page. What a page pays is the subscriptions it asks for.
|
|
74
|
+
|
|
75
|
+
**The defaults and Tailwind**
|
|
76
|
+
|
|
77
|
+
The current default breakpoints are **not** aligned with `@studiometa/tailwind-config`. That alignment is a separate product decision. Call `setBreakpoints()` to match your own scale.
|
|
78
|
+
|
|
79
|
+
## There is no `defineFeatures()`
|
|
80
|
+
|
|
81
|
+
Breakpoints are the one configurable feature, and `setBreakpoints()` is it. Attribute names and the attribute prefix are not configurable.
|