@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.
Files changed (125) hide show
  1. package/README.md +10 -0
  2. package/package.json +5 -2
  3. package/skills/studiometa-js-toolkit/SKILL.md +87 -0
  4. package/skills/studiometa-js-toolkit/references/api/configuration.md +169 -0
  5. package/skills/studiometa-js-toolkit/references/api/context/createContext.md +46 -0
  6. package/skills/studiometa-js-toolkit/references/api/context/createGroup.md +67 -0
  7. package/skills/studiometa-js-toolkit/references/api/context/index.md +69 -0
  8. package/skills/studiometa-js-toolkit/references/api/context/injectContext.md +58 -0
  9. package/skills/studiometa-js-toolkit/references/api/context/injectContextSync.md +44 -0
  10. package/skills/studiometa-js-toolkit/references/api/context/provideContext.md +61 -0
  11. package/skills/studiometa-js-toolkit/references/api/context/provideRootContext.md +42 -0
  12. package/skills/studiometa-js-toolkit/references/api/context/signal.md +85 -0
  13. package/skills/studiometa-js-toolkit/references/api/context/subscribeContext.md +71 -0
  14. package/skills/studiometa-js-toolkit/references/api/decorators/children.md +92 -0
  15. package/skills/studiometa-js-toolkit/references/api/decorators/component.md +75 -0
  16. package/skills/studiometa-js-toolkit/references/api/decorators/index.md +41 -0
  17. package/skills/studiometa-js-toolkit/references/api/decorators/inject.md +73 -0
  18. package/skills/studiometa-js-toolkit/references/api/decorators/on.md +149 -0
  19. package/skills/studiometa-js-toolkit/references/api/decorators/provide.md +77 -0
  20. package/skills/studiometa-js-toolkit/references/api/decorators/read-write.md +93 -0
  21. package/skills/studiometa-js-toolkit/references/api/diagnostics/DIAGNOSTICS.md +137 -0
  22. package/skills/studiometa-js-toolkit/references/api/diagnostics/EVENTS.md +45 -0
  23. package/skills/studiometa-js-toolkit/references/api/diagnostics/index.md +58 -0
  24. package/skills/studiometa-js-toolkit/references/api/diagnostics/reportDiagnostic.md +67 -0
  25. package/skills/studiometa-js-toolkit/references/api/diagnostics/warn.md +54 -0
  26. package/skills/studiometa-js-toolkit/references/api/dom/breakpoints.md +81 -0
  27. package/skills/studiometa-js-toolkit/references/api/dom/domUpdate.md +70 -0
  28. package/skills/studiometa-js-toolkit/references/api/dom/emitExtendable.md +68 -0
  29. package/skills/studiometa-js-toolkit/references/api/dom/index.md +26 -0
  30. package/skills/studiometa-js-toolkit/references/api/dom/swap.md +105 -0
  31. package/skills/studiometa-js-toolkit/references/api/dom/watchAttributeNamespace.md +96 -0
  32. package/skills/studiometa-js-toolkit/references/api/dom/watchAttributes.md +56 -0
  33. package/skills/studiometa-js-toolkit/references/api/dom/whenDOMSettled.md +41 -0
  34. package/skills/studiometa-js-toolkit/references/api/html/data-component.md +105 -0
  35. package/skills/studiometa-js-toolkit/references/api/html/data-mount.md +69 -0
  36. package/skills/studiometa-js-toolkit/references/api/html/data-option.md +106 -0
  37. package/skills/studiometa-js-toolkit/references/api/html/data-ref.md +63 -0
  38. package/skills/studiometa-js-toolkit/references/api/html/index.md +32 -0
  39. package/skills/studiometa-js-toolkit/references/api/index.md +73 -0
  40. package/skills/studiometa-js-toolkit/references/api/instance-events.md +75 -0
  41. package/skills/studiometa-js-toolkit/references/api/instance-methods.md +267 -0
  42. package/skills/studiometa-js-toolkit/references/api/instance-properties.md +90 -0
  43. package/skills/studiometa-js-toolkit/references/api/methods-hooks-events.md +149 -0
  44. package/skills/studiometa-js-toolkit/references/api/methods-hooks-lifecycle.md +90 -0
  45. package/skills/studiometa-js-toolkit/references/api/methods-hooks-options.md +96 -0
  46. package/skills/studiometa-js-toolkit/references/api/methods-hooks-services.md +164 -0
  47. package/skills/studiometa-js-toolkit/references/api/registry/defineManifest.md +57 -0
  48. package/skills/studiometa-js-toolkit/references/api/registry/fromMetaGlob.md +33 -0
  49. package/skills/studiometa-js-toolkit/references/api/registry/fromWebpackContext.md +37 -0
  50. package/skills/studiometa-js-toolkit/references/api/registry/getInstance.md +52 -0
  51. package/skills/studiometa-js-toolkit/references/api/registry/getInstances.md +48 -0
  52. package/skills/studiometa-js-toolkit/references/api/registry/getMountedInstances.md +39 -0
  53. package/skills/studiometa-js-toolkit/references/api/registry/getUnmountedInstances.md +46 -0
  54. package/skills/studiometa-js-toolkit/references/api/registry/index.md +49 -0
  55. package/skills/studiometa-js-toolkit/references/api/registry/registerComponent.md +93 -0
  56. package/skills/studiometa-js-toolkit/references/api/registry/registerComponents.md +37 -0
  57. package/skills/studiometa-js-toolkit/references/api/registry/registerManifest.md +72 -0
  58. package/skills/studiometa-js-toolkit/references/api/scheduler/defaultScheduler.md +104 -0
  59. package/skills/studiometa-js-toolkit/references/api/scheduler/index.md +27 -0
  60. package/skills/studiometa-js-toolkit/references/api/scheduler/nextFrame.md +31 -0
  61. package/skills/studiometa-js-toolkit/references/api/scheduler/viewTransition.md +53 -0
  62. package/skills/studiometa-js-toolkit/references/api/services/createService.md +95 -0
  63. package/skills/studiometa-js-toolkit/references/api/services/createServiceMixin.md +81 -0
  64. package/skills/studiometa-js-toolkit/references/api/services/index.md +94 -0
  65. package/skills/studiometa-js-toolkit/references/api/services/mixins.md +148 -0
  66. package/skills/studiometa-js-toolkit/references/api/services/perTarget.md +65 -0
  67. package/skills/studiometa-js-toolkit/references/api/services/toggle.md +80 -0
  68. package/skills/studiometa-js-toolkit/references/api/services/until.md +44 -0
  69. package/skills/studiometa-js-toolkit/references/api/services/useBreakpoint.md +42 -0
  70. package/skills/studiometa-js-toolkit/references/api/services/useDrag.md +119 -0
  71. package/skills/studiometa-js-toolkit/references/api/services/useInView.md +64 -0
  72. package/skills/studiometa-js-toolkit/references/api/services/useKey.md +105 -0
  73. package/skills/studiometa-js-toolkit/references/api/services/useMediaQuery.md +45 -0
  74. package/skills/studiometa-js-toolkit/references/api/services/useMutation.md +69 -0
  75. package/skills/studiometa-js-toolkit/references/api/services/usePointer.md +91 -0
  76. package/skills/studiometa-js-toolkit/references/api/services/usePrefersReducedMotion.md +42 -0
  77. package/skills/studiometa-js-toolkit/references/api/services/useRaf.md +91 -0
  78. package/skills/studiometa-js-toolkit/references/api/services/useResize.md +60 -0
  79. package/skills/studiometa-js-toolkit/references/api/services/useScroll.md +88 -0
  80. package/skills/studiometa-js-toolkit/references/api/services/useScrollProgress.md +88 -0
  81. package/skills/studiometa-js-toolkit/references/api/services/useWindowScroll.md +25 -0
  82. package/skills/studiometa-js-toolkit/references/api/services/useWindowSize.md +25 -0
  83. package/skills/studiometa-js-toolkit/references/api/storage/createStorage.md +126 -0
  84. package/skills/studiometa-js-toolkit/references/api/storage/index.md +33 -0
  85. package/skills/studiometa-js-toolkit/references/api/storage/presets.md +58 -0
  86. package/skills/studiometa-js-toolkit/references/api/storage/providers.md +109 -0
  87. package/skills/studiometa-js-toolkit/references/api/test/index.md +214 -0
  88. package/skills/studiometa-js-toolkit/references/guide/concepts/attribute-grammar.md +93 -0
  89. package/skills/studiometa-js-toolkit/references/guide/concepts/mutation-engine.md +71 -0
  90. package/skills/studiometa-js-toolkit/references/guide/concepts/philosophy.md +78 -0
  91. package/skills/studiometa-js-toolkit/references/guide/concepts/the-registry.md +110 -0
  92. package/skills/studiometa-js-toolkit/references/guide/going-further/autoloading.md +104 -0
  93. package/skills/studiometa-js-toolkit/references/guide/going-further/handling-diagnostics.md +104 -0
  94. package/skills/studiometa-js-toolkit/references/guide/going-further/mount-strategies.md +95 -0
  95. package/skills/studiometa-js-toolkit/references/guide/going-further/scheduling-work.md +124 -0
  96. package/skills/studiometa-js-toolkit/references/guide/going-further/sharing-state.md +218 -0
  97. package/skills/studiometa-js-toolkit/references/guide/going-further/swapping-content.md +92 -0
  98. package/skills/studiometa-js-toolkit/references/guide/going-further/testing-components.md +137 -0
  99. package/skills/studiometa-js-toolkit/references/guide/going-further/typing-components.md +190 -0
  100. package/skills/studiometa-js-toolkit/references/guide/going-further/using-decorators.md +168 -0
  101. package/skills/studiometa-js-toolkit/references/guide/going-further/using-storage.md +125 -0
  102. package/skills/studiometa-js-toolkit/references/guide/index.md +163 -0
  103. package/skills/studiometa-js-toolkit/references/guide/introduction/installation.md +144 -0
  104. package/skills/studiometa-js-toolkit/references/guide/introduction/lifecycle-hooks.md +174 -0
  105. package/skills/studiometa-js-toolkit/references/guide/introduction/managing-components.md +229 -0
  106. package/skills/studiometa-js-toolkit/references/guide/introduction/managing-options.md +235 -0
  107. package/skills/studiometa-js-toolkit/references/guide/introduction/managing-refs.md +174 -0
  108. package/skills/studiometa-js-toolkit/references/guide/introduction/using-services.md +218 -0
  109. package/skills/studiometa-js-toolkit/references/guide/introduction/working-with-events.md +251 -0
  110. package/skills/studiometa-js-toolkit/references/guide/migration/v3-to-v4.md +373 -0
  111. package/skills/studiometa-js-toolkit/references/utils/css.md +98 -0
  112. package/skills/studiometa-js-toolkit/references/utils/dom.md +54 -0
  113. package/skills/studiometa-js-toolkit/references/utils/easings.md +217 -0
  114. package/skills/studiometa-js-toolkit/references/utils/focus.md +84 -0
  115. package/skills/studiometa-js-toolkit/references/utils/history.md +69 -0
  116. package/skills/studiometa-js-toolkit/references/utils/index.md +53 -0
  117. package/skills/studiometa-js-toolkit/references/utils/is.md +84 -0
  118. package/skills/studiometa-js-toolkit/references/utils/load.md +59 -0
  119. package/skills/studiometa-js-toolkit/references/utils/math.md +129 -0
  120. package/skills/studiometa-js-toolkit/references/utils/motion.md +218 -0
  121. package/skills/studiometa-js-toolkit/references/utils/objects.md +68 -0
  122. package/skills/studiometa-js-toolkit/references/utils/scroll.md +128 -0
  123. package/skills/studiometa-js-toolkit/references/utils/strings.md +176 -0
  124. package/skills/studiometa-js-toolkit/references/utils/timing.md +111 -0
  125. package/skills/studiometa-js-toolkit/references/utils/transitions.md +122 -0
@@ -0,0 +1,217 @@
1
+ # Easings
2
+
3
+ ```js
4
+ import { easeInOutCubic, easeOutQuad } from '@studiometa/js-toolkit/utils';
5
+
6
+ easeOutQuad(0.5); // 0.75
7
+ ```
8
+
9
+ An `EasingFunction` takes a `0 → 1` progress and returns a shaped `0 → 1` value.
10
+
11
+ ## Choosing one
12
+
13
+ Eight curves, three directions each. Every `out` and `in-out` is derived from its `in`.
14
+
15
+ | Curve | In | Out | In-out |
16
+ | ------ | ----------------------------- | ------------------------------- | ----------------------------------- |
17
+ | linear | [`easeLinear`](#easelinear) | — | — |
18
+ | quad | [`easeInQuad`](#easeinquad) | [`easeOutQuad`](#easeoutquad) | [`easeInOutQuad`](#easeinoutquad) |
19
+ | cubic | [`easeInCubic`](#easeincubic) | [`easeOutCubic`](#easeoutcubic) | [`easeInOutCubic`](#easeinoutcubic) |
20
+ | quart | [`easeInQuart`](#easeinquart) | [`easeOutQuart`](#easeoutquart) | [`easeInOutQuart`](#easeinoutquart) |
21
+ | quint | [`easeInQuint`](#easeinquint) | [`easeOutQuint`](#easeoutquint) | [`easeInOutQuint`](#easeinoutquint) |
22
+ | sine | [`easeInSine`](#easeinsine) | [`easeOutSine`](#easeoutsine) | [`easeInOutSine`](#easeinoutsine) |
23
+ | circ | [`easeInCirc`](#easeincirc) | [`easeOutCirc`](#easeoutcirc) | [`easeInOutCirc`](#easeinoutcirc) |
24
+ | expo | [`easeInExpo`](#easeinexpo) | [`easeOutExpo`](#easeoutexpo) | [`easeInOutExpo`](#easeinoutexpo) |
25
+
26
+ ## Deriving one
27
+
28
+ ```ts
29
+ createEaseOut(easeIn: EasingFunction): EasingFunction
30
+ createEaseInOut(easeIn: EasingFunction): EasingFunction
31
+ ```
32
+
33
+ Every `out` and `in-out` above is one of these applied to its `in`. Which means a custom curve gets its whole family for two lines:
34
+
35
+ ```ts
36
+ import { createEaseInOut, createEaseOut, type EasingFunction } from '@studiometa/js-toolkit/utils';
37
+
38
+ const easeInBack: EasingFunction = (progress) => progress * progress * (2.7 * progress - 1.7);
39
+
40
+ const easeOutBack = createEaseOut(easeInBack);
41
+ const easeInOutBack = createEaseInOut(easeInBack);
42
+ ```
43
+
44
+ That is why only the eight `in` functions are written out and the other sixteen are derived: an `out` is an `in` run backwards, and writing it twice is how the two drift apart.
45
+
46
+ ### createEaseOut
47
+
48
+ ```ts
49
+ createEaseOut(easeIn: EasingFunction): EasingFunction
50
+ ```
51
+
52
+ Mirror an `in` curve into its `out`.
53
+
54
+ ### createEaseInOut
55
+
56
+ ```ts
57
+ createEaseInOut(easeIn: EasingFunction): EasingFunction
58
+ ```
59
+
60
+ Compose an `in` curve into its symmetric `in-out`.
61
+
62
+ ## Using one
63
+
64
+ ```js
65
+ import { easeOutCubic, lerp } from '@studiometa/js-toolkit/utils';
66
+
67
+ function positionAt(progress) {
68
+ return lerp(0, 400, easeOutCubic(progress));
69
+ }
70
+ ```
71
+
72
+ An easing shapes a **progress**, so it pairs with a value that already runs `0 → 1`: a [`useScrollProgress()`](../api/services/useScrollProgress.md) subscriber, or a `map()` of anything else.
73
+
74
+ ## They do not animate
75
+
76
+ There is no clock here — an easing is a pure function of a progress you already have.
77
+
78
+ - For a value chasing a target frame by frame, use [`damp()` or `smoothTo()`](motion.md).
79
+ - For time-based playback, stagger and sequencing, that is the separate `ui-animation` package. `tween` and `animate` are not shipped.
80
+
81
+ ## The functions
82
+
83
+ Each takes a `0 → 1` progress and returns the shaped value.
84
+
85
+ ### easeLinear
86
+
87
+ ```ts
88
+ easeLinear(progress: number): number
89
+ ```
90
+
91
+ The identity. It exists so a call site can name "no easing" rather than branch on `undefined`.
92
+
93
+ ### easeInQuad
94
+
95
+ ```ts
96
+ easeInQuad(progress: number): number
97
+ ```
98
+
99
+ ### easeOutQuad
100
+
101
+ ```ts
102
+ easeOutQuad(progress: number): number
103
+ ```
104
+
105
+ ### easeInOutQuad
106
+
107
+ ```ts
108
+ easeInOutQuad(progress: number): number
109
+ ```
110
+
111
+ ### easeInCubic
112
+
113
+ ```ts
114
+ easeInCubic(progress: number): number
115
+ ```
116
+
117
+ ### easeOutCubic
118
+
119
+ ```ts
120
+ easeOutCubic(progress: number): number
121
+ ```
122
+
123
+ ### easeInOutCubic
124
+
125
+ ```ts
126
+ easeInOutCubic(progress: number): number
127
+ ```
128
+
129
+ ### easeInQuart
130
+
131
+ ```ts
132
+ easeInQuart(progress: number): number
133
+ ```
134
+
135
+ ### easeOutQuart
136
+
137
+ ```ts
138
+ easeOutQuart(progress: number): number
139
+ ```
140
+
141
+ ### easeInOutQuart
142
+
143
+ ```ts
144
+ easeInOutQuart(progress: number): number
145
+ ```
146
+
147
+ ### easeInQuint
148
+
149
+ ```ts
150
+ easeInQuint(progress: number): number
151
+ ```
152
+
153
+ ### easeOutQuint
154
+
155
+ ```ts
156
+ easeOutQuint(progress: number): number
157
+ ```
158
+
159
+ ### easeInOutQuint
160
+
161
+ ```ts
162
+ easeInOutQuint(progress: number): number
163
+ ```
164
+
165
+ ### easeInSine
166
+
167
+ ```ts
168
+ easeInSine(progress: number): number
169
+ ```
170
+
171
+ ### easeOutSine
172
+
173
+ ```ts
174
+ easeOutSine(progress: number): number
175
+ ```
176
+
177
+ ### easeInOutSine
178
+
179
+ ```ts
180
+ easeInOutSine(progress: number): number
181
+ ```
182
+
183
+ ### easeInCirc
184
+
185
+ ```ts
186
+ easeInCirc(progress: number): number
187
+ ```
188
+
189
+ ### easeOutCirc
190
+
191
+ ```ts
192
+ easeOutCirc(progress: number): number
193
+ ```
194
+
195
+ ### easeInOutCirc
196
+
197
+ ```ts
198
+ easeInOutCirc(progress: number): number
199
+ ```
200
+
201
+ ### easeInExpo
202
+
203
+ ```ts
204
+ easeInExpo(progress: number): number
205
+ ```
206
+
207
+ ### easeOutExpo
208
+
209
+ ```ts
210
+ easeOutExpo(progress: number): number
211
+ ```
212
+
213
+ ### easeInOutExpo
214
+
215
+ ```ts
216
+ easeInOutExpo(progress: number): number
217
+ ```
@@ -0,0 +1,84 @@
1
+ # Focus
2
+
3
+ ```js
4
+ import { saveActiveElement, trapFocus, untrapFocus } from '@studiometa/js-toolkit/utils';
5
+ ```
6
+
7
+ The three calls a modal surface needs.
8
+
9
+ ## Usage
10
+
11
+ `trapFocus()` takes **the keyboard event**, so it is called from a key handler rather than installing a listener of its own:
12
+
13
+ ```js
14
+ import { Base } from '@studiometa/js-toolkit';
15
+ import { saveActiveElement, trapFocus, untrapFocus } from '@studiometa/js-toolkit/utils';
16
+
17
+ class Dialog extends Base {
18
+ static config = { name: 'Dialog' };
19
+
20
+ open() {
21
+ saveActiveElement();
22
+ this.$el.removeAttribute('hidden');
23
+ }
24
+
25
+ close() {
26
+ this.$el.setAttribute('hidden', '');
27
+ untrapFocus();
28
+ }
29
+
30
+ onKeydown(event) {
31
+ trapFocus(this.$el, event);
32
+ }
33
+ }
34
+ ```
35
+
36
+ With [`useKey()`](../api/services/useKey.md) the event is in the props:
37
+
38
+ ```js
39
+ mounted() {
40
+ return useKey(this.$el).subscribe(({ event, TAB, isDown }) => {
41
+ if (TAB && isDown && event) trapFocus(this.$el, event);
42
+ });
43
+ }
44
+ ```
45
+
46
+ **This is why the key listeners are not passive**
47
+
48
+ `trapFocus()` calls `preventDefault()` on the event it is handed, and a passive listener cannot. See [`useKey()`](../api/services/useKey.md#the-listeners-are-neither-passive-nor-capturing).
49
+
50
+ ## The three
51
+
52
+ ### saveActiveElement
53
+
54
+ ```ts
55
+ saveActiveElement(): void
56
+ ```
57
+
58
+ Remembers what had focus, so `untrapFocus()` can give it back.
59
+
60
+ ### trapFocus
61
+
62
+ ```ts
63
+ trapFocus(el: HTMLElement, event: KeyboardEvent): void
64
+ ```
65
+
66
+ Keeps `Tab` and `Shift+Tab` inside `el`.
67
+
68
+ ### untrapFocus
69
+
70
+ ```ts
71
+ untrapFocus(): void
72
+ ```
73
+
74
+ Releases the trap and restores focus to the saved element.
75
+
76
+ ## The saved element is shared across copies
77
+
78
+ Through the shared runtime, for the same reason the scroll lock counts: **there is one focus per document**. Two independently evaluated copies of the package must not each think they own it.
79
+
80
+ ## `<dialog>` already does some of this
81
+
82
+ `showModal()` gives the top layer, the backdrop, a focus trap and `Escape`. What it does **not** do is stop the page behind it from scrolling — see [`lockScroll()`](scroll.md#lockscroll).
83
+
84
+ Reach for these three when the surface is not a native `<dialog>`.
@@ -0,0 +1,69 @@
1
+ # History
2
+
3
+ ```js
4
+ import { historyPush, historyReplace, objectToURLSearchParams } from '@studiometa/js-toolkit/utils';
5
+ ```
6
+
7
+ ## Writing the URL
8
+
9
+ ```js
10
+ import { historyPush, historyReplace } from '@studiometa/js-toolkit/utils';
11
+
12
+ historyPush({ path: '/products', search: { page: 2, sort: 'price' } });
13
+ historyReplace({ search: { page: 3 } });
14
+ historyPush({ hash: 'section-2' });
15
+ ```
16
+
17
+ ```ts
18
+ interface HistoryOptions {
19
+ path?: string;
20
+ search?: URLSearchParams | Record<string, SearchParamInput>;
21
+ hash?: string;
22
+ }
23
+ ```
24
+
25
+ **Each part is optional, and an omitted part is kept.** `historyReplace({ search })` keeps the path and the hash; `historyPush({ hash })` keeps the path and the query string.
26
+
27
+ That is the whole reason these exist over `history.pushState()`: rebuilding a URL from `location` by hand is where a filter update loses the hash, or a hash update drops the query string.
28
+
29
+ ### historyPush
30
+
31
+ ```ts
32
+ historyPush(options: HistoryOptions, data?: unknown, title?: string): void
33
+ ```
34
+
35
+ Adds a history entry. This is what makes a state navigable with the back button.
36
+
37
+ ### historyReplace
38
+
39
+ ```ts
40
+ historyReplace(options: HistoryOptions, data?: unknown, title?: string): void
41
+ ```
42
+
43
+ Replaces the current entry. This is what keeps a scroll position or a tab index out of the history.
44
+
45
+ ## Building the query string
46
+
47
+ ### objectToURLSearchParams
48
+
49
+ ```ts
50
+ objectToURLSearchParams(
51
+ object: Record<string, SearchParamInput>,
52
+ defaultSearch?: string,
53
+ ): URLSearchParams
54
+ ```
55
+
56
+ ```js
57
+ import { objectToURLSearchParams } from '@studiometa/js-toolkit/utils';
58
+
59
+ objectToURLSearchParams({ page: 2, tags: ['a', 'b'], open: true });
60
+ objectToURLSearchParams({ page: 2 }, location.search);
61
+ ```
62
+
63
+ `SearchParamInput` accepts a string, a number, a boolean, `null`, `undefined`, an array of those, or a nested object of those — so a filter state serializes without being flattened first.
64
+
65
+ `defaultSearch` is the starting point, so the result **merges into** an existing query string instead of replacing it.
66
+
67
+ ## See also
68
+
69
+ For a URL that is also a store you can read, write and observe, use [`createUrlSearchParamsStorage()`](../api/storage/presets.md) — it is these functions behind a typed key-value surface, with `popstate` wired up.
@@ -0,0 +1,53 @@
1
+ # Utils
2
+
3
+ ```js
4
+ import { clamp, damp } from '@studiometa/js-toolkit/utils';
5
+ ```
6
+
7
+ Every one of them also has a subpath of its own:
8
+
9
+ ```js
10
+ import { clamp } from '@studiometa/js-toolkit/utils/clamp';
11
+ ```
12
+
13
+ With a bundler, prefer the barrel — tree-shaking removes what you do not import. From an ESM CDN with no build step, prefer the subpath: there is no bundler to shake the graph.
14
+
15
+ ## The categories
16
+
17
+ | Category | Contents |
18
+ | ---------------------------------- | ---------------------------------------------------------------------- |
19
+ | [Type guards](is.md) | `isString`, `isNumber`, `isObject`, `isDefined`, … |
20
+ | [Strings](strings.md) | case conversion, leading and trailing characters |
21
+ | [Math](math.md) | `clamp`, `lerp`, `map`, `wrap`, `fold`, `round`, `mean`, `createRange` |
22
+ | [Easings](easings.md) | the 24 easing functions, `createEaseOut`, `createEaseInOut` |
23
+ | [Motion](motion.md) | `damp`, `spring`, `smoothTo`, the inertia family |
24
+ | [CSS](css.md) | `transform`, `matrix`, `getOffsetSizes`, `setClassesOrStyles` |
25
+ | [Transitions](transitions.md) | `transition`, `enterTransition`, `leaveTransition` |
26
+ | [DOM](dom.md) | `createElement`, `selectorFor` |
27
+ | [Focus](focus.md) | `trapFocus`, `untrapFocus`, `saveActiveElement` |
28
+ | [Scroll](scroll.md) | `scrollTo`, `scrollPosition`, `lockScroll` |
29
+ | [History](history.md) | `historyPush`, `historyReplace`, `objectToURLSearchParams` |
30
+ | [Loading](load.md) | `loadImage`, `loadScript`, `loadLink` |
31
+ | [Timing](timing.md) | `debounce`, `throttle`, `wait`, `memo`, `noop` |
32
+ | [Objects & random](objects.md) | `deepmerge`, `random`, `randomInt`, `randomItem` |
33
+
34
+ ## What v3 shipped and v4 does not
35
+
36
+ | Removed | Because |
37
+ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
38
+ | `tween`, `animate` | time-based playback moves to a separate `ui-animation` package |
39
+ | `domScheduler`, `useScheduler` | [one scheduler](../api/scheduler/index.md) is the clock |
40
+ | `Queue`, `SmartQueue` | the scheduler's lanes replace them |
41
+ | the collision helpers | not ported |
42
+ | `keyCodes` | [`useKey()`](../api/services/useKey.md) delivers named booleans, so nothing is left to compare against |
43
+ | `nextTick`, `nextMicrotask` | `await Promise.resolve()` and `queueMicrotask()` say it |
44
+ | `isEmpty`, `isEmptyString`, `isArray`, `isDev` | the platform or a one-liner says it |
45
+ | `cache`, `memoize` | [`memo`](timing.md#memo) covers the one case core needed |
46
+ | `addClass`, `removeClass`, `toggleClass`, `addStyle`, `removeStyle` | `classList` and `style` say it |
47
+ | `loadIframe`, `loadElement` | not ported |
48
+
49
+ ## How a utility earns its place
50
+
51
+ **It is judged by consumer need, not by core's own use.** Some of these are not called anywhere in core — they ship because a component author would otherwise write them again, and getting them wrong is easy. `deepmerge` is the clearest case.
52
+
53
+ The reverse also holds: a function core happens to use internally is not public for that reason alone.
@@ -0,0 +1,84 @@
1
+ # Type guards
2
+
3
+ ```js
4
+ import { isDefined, isNumber, isObject, isString } from '@studiometa/js-toolkit/utils';
5
+ ```
6
+
7
+ Each one narrows the type.
8
+
9
+ ## Usage
10
+
11
+ ```ts
12
+ import { isDefined, isObject, isString } from '@studiometa/js-toolkit/utils';
13
+
14
+ function label(value: unknown): string {
15
+ if (isString(value)) return value; // string
16
+ if (isObject(value)) return JSON.stringify(value); // Record<string, unknown>
17
+ return '';
18
+ }
19
+
20
+ function first<T>(items: (T | undefined)[]): T[] {
21
+ return items.filter(isDefined); // T[]
22
+ }
23
+ ```
24
+
25
+ `isDefined` as a `filter` predicate is the case that earns the export: it is the one narrowing TypeScript will not do from a truthiness check.
26
+
27
+ ## The guards
28
+
29
+ ### isNull
30
+
31
+ ```ts
32
+ isNull(value: unknown): value is null
33
+ ```
34
+
35
+ ### isDefined
36
+
37
+ ```ts
38
+ isDefined<T>(value: T | undefined): value is T
39
+ ```
40
+
41
+ Narrows `T | undefined` to `T`. Passing it by reference to `filter` is what it is for.
42
+
43
+ ### isString
44
+
45
+ ```ts
46
+ isString(value: unknown): value is string
47
+ ```
48
+
49
+ ### isNumber
50
+
51
+ ```ts
52
+ isNumber(value: unknown): value is number
53
+ ```
54
+
55
+ **It rejects `NaN`**, because a `NaN` that passes a number check is a bug that surfaces three functions later.
56
+
57
+ ### isBoolean
58
+
59
+ ```ts
60
+ isBoolean(value: unknown): value is boolean
61
+ ```
62
+
63
+ ### isFunction
64
+
65
+ ```ts
66
+ isFunction(value: unknown): value is (...args: unknown[]) => unknown
67
+ ```
68
+
69
+ ### isObject
70
+
71
+ ```ts
72
+ isObject(value: unknown): value is Record<string, unknown>
73
+ ```
74
+
75
+ ## What is not here
76
+
77
+ | Not shipped | Write |
78
+ | --------------- | ----------------------------------- |
79
+ | `isArray` | `Array.isArray(value)` |
80
+ | `isEmpty` | the check the caller actually means |
81
+ | `isEmptyString` | `value === ''` |
82
+ | `isDev` | your bundler's own flag |
83
+
84
+ A guard earns its place by narrowing something the platform does not, or by being a predicate you pass by reference.
@@ -0,0 +1,59 @@
1
+ # Loading
2
+
3
+ ```js
4
+ import { loadImage, loadLink, loadScript } from '@studiometa/js-toolkit/utils';
5
+ ```
6
+
7
+ Three promises over three tags. Each resolves with the **element** it created, so a caller can read the loaded image's natural size, or keep the script tag to remove it later.
8
+
9
+ ## Usage
10
+
11
+ ```js
12
+ import { loadImage, loadLink, loadScript } from '@studiometa/js-toolkit/utils';
13
+
14
+ async function load() {
15
+ const img = await loadImage('/hero.webp');
16
+
17
+ await loadScript('https://example.com/widget.js', { async: '', crossorigin: 'anonymous' });
18
+
19
+ await loadLink('/print.css', { rel: 'stylesheet', media: 'print' });
20
+ }
21
+ ```
22
+
23
+ ### loadImage
24
+
25
+ ```ts
26
+ loadImage(src: string): Promise<HTMLImageElement>
27
+ ```
28
+
29
+ ### loadScript
30
+
31
+ ```ts
32
+ loadScript(src: string, attributes?: Record<string, string>): Promise<HTMLScriptElement>
33
+ ```
34
+
35
+ ### loadLink
36
+
37
+ ```ts
38
+ loadLink(href: string, attributes: { rel: string; [key: string]: string }): Promise<HTMLLinkElement>
39
+ ```
40
+
41
+ ## Where they belong
42
+
43
+ A third-party widget, a heavy stylesheet or a decorative image is exactly what a deferred mount is for. Reach for these **inside** a component whose mount is already deferred, rather than as a substitute for deferring it:
44
+
45
+ ```html
46
+ <div data-component="Widget" data-mount="interaction:page"></div>
47
+ ```
48
+
49
+ ```js
50
+ mounted() {
51
+ return loadScript('https://example.com/widget.js').then(() => this.init());
52
+ }
53
+ ```
54
+
55
+ The [mount strategy](../guide/going-further/mount-strategies.md) decides _when_, and these decide _what_.
56
+
57
+ ## What is not here
58
+
59
+ `loadIframe` and `loadElement` are not ported. An iframe is one `createElement()` and one `load` listener, and a generic element loader had no caller that a specific one did not serve better.
@@ -0,0 +1,129 @@
1
+ # Math
2
+
3
+ ```js
4
+ import { clamp, lerp, map } from '@studiometa/js-toolkit/utils';
5
+ ```
6
+
7
+ Values in, values out. For anything with a time dimension — damping, springs, inertia — see [Motion](motion.md).
8
+
9
+ ## Ranges
10
+
11
+ ```js
12
+ import { clamp, clamp01 } from '@studiometa/js-toolkit/utils';
13
+
14
+ clamp(15, 0, 10); // 10
15
+ clamp(-5, 0, 10); // 0
16
+ clamp01(1.5); // 1
17
+ ```
18
+
19
+ ### clamp
20
+
21
+ ```ts
22
+ clamp(value: number, min: number, max: number): number
23
+ ```
24
+
25
+ Keep a value inside a range.
26
+
27
+ ### clamp01
28
+
29
+ ```ts
30
+ clamp01(value: number): number
31
+ ```
32
+
33
+ Keep a value inside `0 → 1`. It is the case that comes up in every progress calculation, so it has its own name and no arguments to get the wrong way round.
34
+
35
+ ## Interpolation
36
+
37
+ ```js
38
+ import { lerp, map } from '@studiometa/js-toolkit/utils';
39
+
40
+ lerp(0, 100, 0.5); // 50
41
+ map(5, 0, 10, 0, 100); // 50
42
+ ```
43
+
44
+ ### lerp
45
+
46
+ ```ts
47
+ lerp(min: number, max: number, ratio: number): number
48
+ ```
49
+
50
+ Interpolate between two values by a `0 → 1` ratio.
51
+
52
+ ### map
53
+
54
+ ```ts
55
+ map(value: number, inputMin: number, inputMax: number, outputMin: number, outputMax: number): number
56
+ ```
57
+
58
+ Re-scale one range onto another. It is `lerp` with the input range worked out for you.
59
+
60
+ ## Wrapping and folding
61
+
62
+ ```js
63
+ import { fold, wrap } from '@studiometa/js-toolkit/utils';
64
+
65
+ wrap(11, 0, 10); // 1 — it comes back round
66
+ fold(11, 0, 10); // 9 — it bounces back
67
+ ```
68
+
69
+ Two behaviours, two names, no flag.
70
+
71
+ ### wrap
72
+
73
+ ```ts
74
+ wrap(value: number, min: number, max: number): number
75
+ ```
76
+
77
+ A carousel that loops: past the end, it comes back round to the start.
78
+
79
+ ### fold
80
+
81
+ ```ts
82
+ fold(value: number, min: number, max: number): number
83
+ ```
84
+
85
+ A value that reverses at the edges rather than looping.
86
+
87
+ ## Rounding and averaging
88
+
89
+ ```js
90
+ import { mean, round } from '@studiometa/js-toolkit/utils';
91
+
92
+ round(1.2345, 2); // 1.23
93
+ mean([1, 2, 3, 4]); // 2.5
94
+ ```
95
+
96
+ ### round
97
+
98
+ ```ts
99
+ round(value: number, decimals?: number): number
100
+ ```
101
+
102
+ Round to a number of decimals.
103
+
104
+ ### mean
105
+
106
+ ```ts
107
+ mean(numbers: readonly number[]): number
108
+ ```
109
+
110
+ The arithmetic mean of a list.
111
+
112
+ ## Series
113
+
114
+ ### createRange
115
+
116
+ ```ts
117
+ createRange(min: number, max: number, step: number): number[]
118
+ ```
119
+
120
+ ```js
121
+ import { createRange } from '@studiometa/js-toolkit/utils';
122
+
123
+ createRange(0, 1, 0.25); // [0, 0.25, 0.5, 0.75, 1]
124
+ ```
125
+
126
+ ## See also
127
+
128
+ - [Easings](easings.md) — shaping a `0 → 1` progress
129
+ - [Motion](motion.md) — everything with an elapsed time