@weasel-js/react 1.10.0 → 1.11.0

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 CHANGED
@@ -7,7 +7,7 @@ a value into a ref prop of either shape. No weasel domain in it.
7
7
 
8
8
  Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
9
9
  scene-graph canvas kit for React. See the
10
- [API reference](https://orochi235.github.io/weasel/api/).
10
+ [API reference](https://orochi235.github.io/weasel/#pkg/react).
11
11
 
12
12
  ## Install
13
13
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Ref, RefObject } from 'react';
1
+ import { Ref, RefObject, ReactNode } from 'react';
2
+ export { isDev } from '@weasel-js/registry';
2
3
 
3
4
  /**
4
5
  * Write `value` into a ref a caller passed in, whichever shape it has: call a
@@ -62,6 +63,11 @@ declare function useLatest<T>(value: T): {
62
63
  readonly current: T;
63
64
  };
64
65
 
66
+ /** Whether the reader has asked for less motion. False where there is no `matchMedia` to ask. */
67
+ declare function prefersReducedMotion(): boolean;
68
+ /** {@link prefersReducedMotion}, re-rendering when the preference changes. */
69
+ declare function usePrefersReducedMotion(): boolean;
70
+
65
71
  /**
66
72
  * The one place weasel decides whether a frame may run, so that "a loop does no
67
73
  * work nobody can see" holds by construction rather than by every loop author
@@ -108,11 +114,11 @@ interface VisibleRaf {
108
114
  isVisible(): boolean;
109
115
  }
110
116
  /**
111
- * @param frame Runs on the frame, with the timestamp the clock supplied.
112
- * A continuous loop calls `request()` again from inside it; a one-shot loop
113
- * does not.
117
+ * @param frame Runs on the frame, with the timestamp the clock supplied and the
118
+ * loop's own handle. A continuous loop calls `loop.request()` from inside it;
119
+ * a one-shot loop does not.
114
120
  */
115
- declare function useVisibleRaf(frame: (time: number) => void, options?: VisibleRafOptions): VisibleRaf;
121
+ declare function useVisibleRaf(frame: (time: number, loop: VisibleRaf) => void, options?: VisibleRafOptions): VisibleRaf;
116
122
 
117
123
  /** Options for {@link useFrameClock}. */
118
124
  interface FrameClockOptions {
@@ -149,4 +155,85 @@ declare function useStableByContent<T>(value: T, same: (a: T, b: T) => boolean):
149
155
  /** Same length, and the same element at every index. */
150
156
  declare function sameList<T>(a: readonly T[], b: readonly T[]): boolean;
151
157
 
152
- export { type CanvasFocusReturn, type FrameClock, type FrameClockOptions, type UseCanvasFocusOptions, type VisibleRaf, type VisibleRafOptions, type VisibleRafTarget, assignRef, sameList, useCanvasFocus, useFrameClock, useLatest, useStableByContent, useVisibleRaf };
158
+ /** A value a settings leaf can hold. */
159
+ type KitSettingLeaf = number | string | boolean;
160
+ /** `T` with every area and leaf optional: what a provider sets. */
161
+ type KitSettingsOverrides<T = KitSettings> = {
162
+ [K in keyof T]?: T[K] extends KitSettingLeaf ? T[K] : KitSettingsOverrides<T[K]>;
163
+ };
164
+ /** The dotted path of every leaf in `T`: `'tree.typeaheadMs'`. */
165
+ type KitSettingPath<T = KitSettings> = {
166
+ [K in keyof T & string]: T[K] extends KitSettingLeaf ? K : `${K}.${KitSettingPath<T[K]>}`;
167
+ }[keyof T & string];
168
+ /** The type of the leaf at `P` in `T`. */
169
+ type KitSettingAt<P extends string, T = KitSettings> = P extends `${infer H}.${infer R}` ? H extends keyof T ? KitSettingAt<R, T[H]> : never : P extends keyof T ? T[P] : never;
170
+ /** Reads settings by path and says when any of them changed. */
171
+ interface KitSettingsStore<T = KitSettings> {
172
+ /** The value at `path`: this store's own, else the nearest ancestor's, else `undefined`. */
173
+ get<P extends KitSettingPath<T>>(path: P): KitSettingAt<P, T> | undefined;
174
+ subscribe(listener: () => void): () => void;
175
+ }
176
+ /** A store whose own overrides can be replaced. */
177
+ interface WritableKitSettingsStore<T = KitSettings> extends KitSettingsStore<T> {
178
+ /** Replace this store's own overrides, notifying only when some leaf's value changed. */
179
+ set(overrides: KitSettingsOverrides<T>): void;
180
+ }
181
+ /**
182
+ * A settings store holding `overrides`, reading through to `parent` for any
183
+ * leaf it does not set. A change in the parent notifies this store's
184
+ * listeners too.
185
+ */
186
+ declare function createKitSettingsStore<T = KitSettings>(overrides?: KitSettingsOverrides<T>, parent?: KitSettingsStore<T> | null): WritableKitSettingsStore<T>;
187
+ /** The store read where no provider is mounted: it sets nothing. */
188
+ declare const NO_KIT_SETTINGS: KitSettingsStore;
189
+
190
+ /**
191
+ * Set kit settings for everything inside. Nested providers merge: a leaf this
192
+ * one leaves unset reads through to the one above.
193
+ */
194
+ declare function KitSettingsProvider({ value, children }: {
195
+ value: KitSettingsOverrides;
196
+ children?: ReactNode;
197
+ }): ReactNode;
198
+ /**
199
+ * The store the nearest provider holds, for code outside React — a gesture
200
+ * recognizer, a canvas action — to read with `get(path)` at the moment it
201
+ * needs a value.
202
+ */
203
+ declare function useKitSettings(): KitSettingsStore;
204
+ /**
205
+ * The setting at `path`, or `undefined` where no provider sets it; the caller
206
+ * falls back to its own constant. Re-renders only when this path's value changes.
207
+ */
208
+ declare function useKitSetting<P extends KitSettingPath>(path: P): KitSettingAt<P> | undefined;
209
+
210
+ /**
211
+ * The kit's settings, one key per area: behavior a user sets once for every
212
+ * component, such as how long a tree waits between typeahead keys. A package
213
+ * adds its area by augmenting this interface from its own source:
214
+ *
215
+ * ```ts
216
+ * declare module '@weasel-js/react' {
217
+ * interface KitSettings { tree: { typeaheadMs: number } }
218
+ * }
219
+ * ```
220
+ *
221
+ * Declared here in the barrel, not in `kitSettings.ts`: an augmentation
222
+ * merges through one re-export hop, and a package re-exporting this one adds
223
+ * a second.
224
+ *
225
+ * Every leaf is a number, string or boolean. Nothing here holds a default:
226
+ * a reader falls back to its own constant when no provider sets the value.
227
+ */
228
+ interface KitSettings {
229
+ }
230
+ /**
231
+ * The `targets` area's leaves — hit and snap radii — which more than one
232
+ * package adds to. Each augments this interface with its own keys and declares
233
+ * `targets: KitTargetSettings` in its `KitSettings` augmentation: two packages
234
+ * declaring `targets` as different object types do not merge.
235
+ */
236
+ interface KitTargetSettings {
237
+ }
238
+
239
+ export { type CanvasFocusReturn, type FrameClock, type FrameClockOptions, type KitSettingAt, type KitSettingLeaf, type KitSettingPath, type KitSettings, type KitSettingsOverrides, KitSettingsProvider, type KitSettingsStore, type KitTargetSettings, NO_KIT_SETTINGS, type UseCanvasFocusOptions, type VisibleRaf, type VisibleRafOptions, type VisibleRafTarget, type WritableKitSettingsStore, assignRef, createKitSettingsStore, prefersReducedMotion, sameList, useCanvasFocus, useFrameClock, useKitSetting, useKitSettings, useLatest, usePrefersReducedMotion, useStableByContent, useVisibleRaf };
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
- import { useState, useRef, useCallback, useMemo, useInsertionEffect, useLayoutEffect, useEffect } from 'react';
1
+ import { createEmitter } from '@weasel-js/registry';
2
+ export { isDev } from '@weasel-js/registry';
3
+ import { createContext, useState, useRef, useCallback, useMemo, useInsertionEffect, useSyncExternalStore, useLayoutEffect, useEffect, useContext, createElement } from 'react';
2
4
 
3
5
  // src/assignRef.ts
4
6
  function assignRef(ref, value) {
@@ -42,6 +44,20 @@ function useLatest(value) {
42
44
  });
43
45
  return ref;
44
46
  }
47
+ var QUERY = "(prefers-reduced-motion: reduce)";
48
+ function prefersReducedMotion() {
49
+ return typeof matchMedia === "function" && matchMedia(QUERY).matches;
50
+ }
51
+ function subscribe(onChange) {
52
+ if (typeof matchMedia !== "function") return () => {
53
+ };
54
+ const list = matchMedia(QUERY);
55
+ list.addEventListener?.("change", onChange);
56
+ return () => list.removeEventListener?.("change", onChange);
57
+ }
58
+ function usePrefersReducedMotion() {
59
+ return useSyncExternalStore(subscribe, prefersReducedMotion, () => false);
60
+ }
45
61
  var documentHidden = () => typeof document !== "undefined" && document.hidden;
46
62
  var resolveTarget = (target) => typeof target === "function" ? target() : target?.current ?? null;
47
63
  function useVisibleRaf(frame, options = {}) {
@@ -54,6 +70,7 @@ function useVisibleRaf(frame, options = {}) {
54
70
  const intersectingRef = useRef(true);
55
71
  const observerRef = useRef(null);
56
72
  const observedRef = useRef(null);
73
+ const handleRef = useRef(null);
57
74
  const isVisible = useCallback(
58
75
  () => optsRef.current.dangerouslyRunWhenHidden === true || !documentHidden() && intersectingRef.current,
59
76
  [optsRef]
@@ -69,7 +86,7 @@ function useVisibleRaf(frame, options = {}) {
69
86
  rafRef.current = null;
70
87
  if (!aliveRef.current || !pendingRef.current || !isVisible()) return;
71
88
  pendingRef.current = false;
72
- frameRef.current(time);
89
+ frameRef.current(time, handleRef.current);
73
90
  });
74
91
  }, [frameRef, isVisible, optsRef]);
75
92
  const syncObserver = useCallback(() => {
@@ -131,20 +148,24 @@ function useVisibleRaf(frame, options = {}) {
131
148
  suspendedRef.current = false;
132
149
  };
133
150
  }, [cancelRaf, syncGate, syncObserver]);
134
- return useMemo(() => ({ request, cancel, isVisible }), [request, cancel, isVisible]);
151
+ const handle = useMemo(() => ({ request, cancel, isVisible }), [request, cancel, isVisible]);
152
+ useLayoutEffect(() => {
153
+ handleRef.current = handle;
154
+ }, [handle]);
155
+ return handle;
135
156
  }
136
157
  function useFrameClock(options = {}) {
137
158
  const { paused = false, requestFrame, cancelFrame } = options;
138
159
  const elapsed = useRef(options.start ?? 0);
139
160
  const last = useRef(null);
140
- const listeners = useRef(/* @__PURE__ */ new Set());
161
+ const [ticked] = useState(() => createEmitter());
141
162
  const pausedRef = useLatest(paused);
142
163
  const loop = useVisibleRaf(
143
- (now) => {
164
+ (now, loop2) => {
144
165
  if (last.current !== null) elapsed.current += (now - last.current) / 1e3;
145
166
  last.current = now;
146
- for (const listener of [...listeners.current]) listener();
147
- if (listeners.current.size > 0 && !pausedRef.current) loop.request();
167
+ ticked.emit();
168
+ if (ticked.size > 0 && !pausedRef.current) loop2.request();
148
169
  },
149
170
  { requestFrame, cancelFrame, onResume: () => {
150
171
  last.current = null;
@@ -153,22 +174,21 @@ function useFrameClock(options = {}) {
153
174
  useEffect(() => {
154
175
  if (paused) return;
155
176
  last.current = null;
156
- if (listeners.current.size > 0) loop.request();
177
+ if (ticked.size > 0) loop.request();
157
178
  return () => loop.cancel();
158
- }, [paused, loop]);
179
+ }, [paused, loop, ticked]);
159
180
  return useMemo(() => ({
160
181
  seconds: () => elapsed.current,
161
182
  subscribe(listener) {
162
- const set = listeners.current;
163
- if (set.size === 0) last.current = null;
164
- set.add(listener);
183
+ if (ticked.size === 0) last.current = null;
184
+ const off = ticked.subscribe(listener);
165
185
  if (!pausedRef.current) loop.request();
166
186
  return () => {
167
- set.delete(listener);
168
- if (set.size === 0) loop.cancel();
187
+ off();
188
+ if (ticked.size === 0) loop.cancel();
169
189
  };
170
190
  }
171
- }), [loop, pausedRef]);
191
+ }), [loop, pausedRef, ticked]);
172
192
  }
173
193
  function useStableByContent(value, same) {
174
194
  const held = useRef(value);
@@ -182,6 +202,79 @@ function sameList(a, b) {
182
202
  return a.length === b.length && a.every((t, i) => t === b[i]);
183
203
  }
184
204
 
185
- export { assignRef, sameList, useCanvasFocus, useFrameClock, useLatest, useStableByContent, useVisibleRaf };
205
+ // src/kitSettings.ts
206
+ function leafAt(tree, path) {
207
+ let at = tree;
208
+ for (const key of path.split(".")) {
209
+ if (at === null || typeof at !== "object" || !Object.hasOwn(at, key)) return void 0;
210
+ at = at[key];
211
+ }
212
+ return at;
213
+ }
214
+ function flatten(tree, prefix, out) {
215
+ if (tree === null || typeof tree !== "object") return out;
216
+ for (const [key, value] of Object.entries(tree)) {
217
+ if (value === void 0) continue;
218
+ const path = prefix === "" ? key : `${prefix}.${key}`;
219
+ if (value !== null && typeof value === "object") flatten(value, path, out);
220
+ else out.set(path, value);
221
+ }
222
+ return out;
223
+ }
224
+ var sameLeaves = (a, b) => a.size === b.size && [...a].every(([path, value]) => b.has(path) && Object.is(b.get(path), value));
225
+ function createKitSettingsStore(overrides = {}, parent = null) {
226
+ let own = overrides;
227
+ let leaves = flatten(own, "", /* @__PURE__ */ new Map());
228
+ const listeners = /* @__PURE__ */ new Set();
229
+ const notify = () => {
230
+ for (const listener of [...listeners]) listener();
231
+ };
232
+ let unhookParent = null;
233
+ return {
234
+ get(path) {
235
+ const value = leafAt(own, path);
236
+ return value !== void 0 ? value : parent?.get(path);
237
+ },
238
+ subscribe(listener) {
239
+ listeners.add(listener);
240
+ if (parent && unhookParent === null) unhookParent = parent.subscribe(notify);
241
+ return () => {
242
+ listeners.delete(listener);
243
+ if (listeners.size === 0 && unhookParent) {
244
+ unhookParent();
245
+ unhookParent = null;
246
+ }
247
+ };
248
+ },
249
+ set(next) {
250
+ const nextLeaves = flatten(next, "", /* @__PURE__ */ new Map());
251
+ own = next;
252
+ if (sameLeaves(leaves, nextLeaves)) return;
253
+ leaves = nextLeaves;
254
+ notify();
255
+ }
256
+ };
257
+ }
258
+ var NO_KIT_SETTINGS = createKitSettingsStore();
259
+ var KitSettingsContext = createContext(NO_KIT_SETTINGS);
260
+ function KitSettingsProvider({ value, children }) {
261
+ const parent = useContext(KitSettingsContext);
262
+ const [store, setStore] = useState(() => ({ parent, store: createKitSettingsStore(value, parent) }));
263
+ if (store.parent !== parent) setStore({ parent, store: createKitSettingsStore(value, parent) });
264
+ useLayoutEffect(() => {
265
+ store.store.set(value);
266
+ }, [store, value]);
267
+ return createElement(KitSettingsContext.Provider, { value: store.store }, children);
268
+ }
269
+ function useKitSettings() {
270
+ return useContext(KitSettingsContext);
271
+ }
272
+ function useKitSetting(path) {
273
+ const store = useKitSettings();
274
+ const read = useCallback(() => store.get(path), [store, path]);
275
+ return useSyncExternalStore(store.subscribe, read, read);
276
+ }
277
+
278
+ export { KitSettingsProvider, NO_KIT_SETTINGS, assignRef, createKitSettingsStore, prefersReducedMotion, sameList, useCanvasFocus, useFrameClock, useKitSetting, useKitSettings, useLatest, usePrefersReducedMotion, useStableByContent, useVisibleRaf };
186
279
  //# sourceMappingURL=index.js.map
187
280
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/assignRef.ts","../src/useCanvasFocus.ts","../src/useLatest.ts","../src/useVisibleRaf.ts","../src/useFrameClock.ts","../src/useStableByContent.ts"],"names":["useRef","useCallback","useMemo","useInsertionEffect"],"mappings":"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","file":"index.js","sourcesContent":["import type { Ref } from 'react';\n\n/**\n * Write `value` into a ref a caller passed in, whichever shape it has: call a\n * callback ref, set an object ref's `current`, ignore `null` or `undefined`.\n *\n * For a component that keeps the element (or handle) for itself and also\n * forwards it to a ref prop — call this from the component's own callback ref.\n */\nexport function assignRef<T>(ref: Ref<T> | undefined, value: T | null): void {\n if (typeof ref === 'function') ref(value);\n else if (ref) (ref as { current: T | null }).current = value;\n}\n","/**\n * Track DOM focus on a canvas (or any focusable host element). Returns the\n * live `focused` boolean, props to spread onto the focusable element, and a\n * stable `getFocused()` getter for use inside per-frame closures (notably\n * `RenderLayer.draw`, which is constructed once but called every frame).\n *\n * No Canvas/SceneCanvas integration. Consumers compose the hook with core's\n * `gateLayer` (or their own gate) to hide affordances when blurred.\n */\nimport { useCallback, useMemo, useRef, useState } from 'react';\n\n/** Options for `useCanvasFocus`. */\nexport interface UseCanvasFocusOptions {\n /** Initial focus state. Default `false` — handles hidden until first click. */\n initial?: boolean;\n /** `tabIndex` to set on the focusable element. Default `0` (focusable in\n * document tab order). Set to `-1` for programmatic-only focus. */\n tabIndex?: number;\n}\n\n/** What `useCanvasFocus` hands back: the focus state, the props that\n * maintain it, and a getter for reading it from per-frame code. */\nexport interface CanvasFocusReturn {\n /** Live boolean — true when the element has DOM focus. Triggers re-renders\n * when the focus state changes (set via React state). */\n focused: boolean;\n /** Spread onto the focusable element (e.g. the canvas). Includes\n * `tabIndex`, `onFocus`, `onBlur`. */\n focusProps: {\n tabIndex: number;\n onFocus: () => void;\n onBlur: () => void;\n };\n /** Stable getter for the live focused state. Use inside per-frame closures\n * like `RenderLayer.draw`, where the layer is constructed once and\n * reading `focused` directly would capture the value at construction\n * time. The getter always returns the current value. */\n getFocused: () => boolean;\n /** Imperative focus / blur. Useful for tests and programmatic control. */\n setFocused: (next: boolean) => void;\n}\n\n/** Track DOM focus on the canvas, so chrome that should only show while the\n * canvas is focused can be gated on it. */\nexport function useCanvasFocus(options: UseCanvasFocusOptions = {}): CanvasFocusReturn {\n const { initial = false, tabIndex = 0 } = options;\n const [focused, setFocusedState] = useState<boolean>(initial);\n // Every state change goes through a handler below that writes this first.\n const focusedRef = useRef<boolean>(initial);\n\n const onFocus = useCallback(() => {\n if (!focusedRef.current) {\n focusedRef.current = true;\n setFocusedState(true);\n }\n }, []);\n const onBlur = useCallback(() => {\n if (focusedRef.current) {\n focusedRef.current = false;\n setFocusedState(false);\n }\n }, []);\n const getFocused = useCallback(() => focusedRef.current, []);\n const setFocused = useCallback((next: boolean) => {\n if (focusedRef.current === next) return;\n focusedRef.current = next;\n setFocusedState(next);\n }, []);\n\n // Stable until tabIndex changes, so it can be spread onto memoized elements\n // without retriggering downstream effects.\n const focusProps = useMemo(() => ({ tabIndex, onFocus, onBlur }), [tabIndex, onFocus, onBlur]);\n\n return {\n focused,\n focusProps,\n getFocused,\n setFocused,\n };\n}\n","import { useInsertionEffect, useRef } from 'react';\n\n/**\n * A ref holding the `value` of the last **committed** render, for event\n * handlers, frame loops and other code that runs outside render and must not\n * re-subscribe every time `value` changes.\n *\n * Writing `ref.current = value` in a render body is not the same thing: React\n * may start a render and throw it away (a transition that suspends, a render\n * interrupted by a more urgent update), and the value that render computed\n * would stay behind for the next event to read. This writes from\n * `useInsertionEffect`, so only a render that commits ever lands, and it lands\n * before any layout effect in that commit runs.\n *\n * Don't read `.current` during render expecting this render's value: until the\n * commit it holds the previous committed one (on mount, this first `value`).\n * Render code should use `value` itself.\n */\nexport function useLatest<T>(value: T): { readonly current: T } {\n const ref = useRef(value);\n useInsertionEffect(() => {\n ref.current = value;\n });\n return ref;\n}\n","/**\n * The one place weasel decides whether a frame may run, so that \"a loop does no\n * work nobody can see\" holds by construction rather than by every loop author\n * remembering. Nothing runs while the document is hidden; a loop that names an\n * element also stops while that element is outside the viewport.\n *\n * A request made while suspended is held, not dropped, and re-armed when the\n * surface comes back — so a loop never needs to poll visibility itself.\n */\n\nimport { type RefObject, useCallback, useLayoutEffect, useMemo, useRef } from 'react';\nimport { useLatest } from './useLatest';\n\n/** What a loop names as its element, resolved every time the gate is consulted\n * so a ref filled in after mount still starts the observer. */\nexport type VisibleRafTarget = RefObject<Element | null> | (() => Element | null);\n\n/** Options for {@link useVisibleRaf}. */\nexport interface VisibleRafOptions {\n /** Also gate on this element's intersection with the viewport. Omit it and\n * the loop is gated on document visibility alone. */\n target?: VisibleRafTarget;\n /**\n * Run frames regardless of visibility. The escape hatch for a loop that is\n * not painting for a viewer — an offscreen recording, an export driving its\n * own frames. It is the wrong fix for a loop that stalls after a tab switch:\n * that is a missing `request()`, and setting this hides it.\n */\n dangerouslyRunWhenHidden?: boolean;\n /** Frame clock. Read live, so a test may inject one after mount. */\n requestFrame?: (cb: FrameRequestCallback) => number;\n cancelFrame?: (handle: number) => void;\n /**\n * Runs once when the loop leaves suspension, before the frame that follows\n * it. Where a loop that measures elapsed time drops the interval it spent\n * suspended — without it, an hour hidden arrives as one hour-long frame.\n */\n onResume?: () => void;\n}\n\n/** The handle {@link useVisibleRaf} returns. Nothing runs until `request()` is called. */\nexport interface VisibleRaf {\n /** Ask for a frame. Idempotent while one is outstanding, and held rather than\n * dropped while suspended. Identity is stable for the component's lifetime. */\n request(): void;\n /** Drop the outstanding request, held or scheduled. */\n cancel(): void;\n /** Whether a frame would run right now. */\n isVisible(): boolean;\n}\n\nconst documentHidden = (): boolean => typeof document !== 'undefined' && document.hidden;\n\nconst resolveTarget = (target: VisibleRafTarget | undefined): Element | null =>\n typeof target === 'function' ? target() : (target?.current ?? null);\n\n/**\n * @param frame Runs on the frame, with the timestamp the clock supplied.\n * A continuous loop calls `request()` again from inside it; a one-shot loop\n * does not.\n */\nexport function useVisibleRaf(\n frame: (time: number) => void,\n options: VisibleRafOptions = {},\n): VisibleRaf {\n const frameRef = useLatest(frame);\n const optsRef = useLatest(options);\n\n const rafRef = useRef<number | null>(null);\n const pendingRef = useRef(false);\n const suspendedRef = useRef(false);\n const aliveRef = useRef(true);\n const intersectingRef = useRef(true);\n const observerRef = useRef<IntersectionObserver | null>(null);\n const observedRef = useRef<Element | null>(null);\n\n const isVisible = useCallback(\n (): boolean =>\n optsRef.current.dangerouslyRunWhenHidden === true ||\n (!documentHidden() && intersectingRef.current),\n [optsRef],\n );\n\n const cancelRaf = useCallback(() => {\n if (rafRef.current === null) return;\n (optsRef.current.cancelFrame ?? cancelAnimationFrame)(rafRef.current);\n rafRef.current = null;\n }, [optsRef]);\n\n const scheduleRaf = useCallback(() => {\n if (rafRef.current !== null || !aliveRef.current) return;\n rafRef.current = (optsRef.current.requestFrame ?? requestAnimationFrame)((time) => {\n // Released before the frame runs, so a `request()` issued from inside it\n // schedules the next one instead of being swallowed.\n rafRef.current = null;\n if (!aliveRef.current || !pendingRef.current || !isVisible()) return;\n pendingRef.current = false;\n frameRef.current(time);\n });\n }, [frameRef, isVisible, optsRef]);\n\n /** Attaches the observer once the named element exists. Ref callbacks and\n * lazily-mounted hosts fill their refs after this hook's effect has run. */\n const syncObserver = useCallback(() => {\n const io = observerRef.current;\n if (!io) return;\n const el = resolveTarget(optsRef.current.target);\n if (el === observedRef.current) return;\n if (observedRef.current) io.unobserve(observedRef.current);\n observedRef.current = el;\n // An element that has gone away stops gating rather than stopping the loop.\n intersectingRef.current = true;\n if (el) io.observe(el);\n }, [optsRef]);\n\n /** Re-reads the gate after something that could have changed it. Suspending\n * drops the outstanding frame but keeps the request; resuming hands the\n * loop its rebase before re-arming. */\n const syncGate = useCallback(() => {\n if (!isVisible()) {\n if (suspendedRef.current) return;\n suspendedRef.current = true;\n cancelRaf();\n return;\n }\n if (!suspendedRef.current) return;\n suspendedRef.current = false;\n if (!aliveRef.current) return;\n optsRef.current.onResume?.();\n if (pendingRef.current) scheduleRaf();\n }, [cancelRaf, isVisible, optsRef, scheduleRaf]);\n\n const request = useCallback(() => {\n if (!aliveRef.current) return;\n syncObserver();\n pendingRef.current = true;\n if (!isVisible()) {\n suspendedRef.current = true;\n return;\n }\n scheduleRaf();\n }, [isVisible, scheduleRaf, syncObserver]);\n\n const cancel = useCallback(() => {\n pendingRef.current = false;\n cancelRaf();\n }, [cancelRaf]);\n\n // Re-armed in setup because the refs survive StrictMode's simulated remount, and\n // a layout effect so the arming precedes the host's own layout effects — a loop\n // driven from one of those would otherwise be declined on the second mount.\n useLayoutEffect(() => {\n aliveRef.current = true;\n document.addEventListener('visibilitychange', syncGate);\n if (typeof IntersectionObserver !== 'undefined') {\n observerRef.current = new IntersectionObserver((entries) => {\n intersectingRef.current = entries.some((e) => e.isIntersecting);\n syncGate();\n });\n syncObserver();\n }\n return () => {\n aliveRef.current = false;\n document.removeEventListener('visibilitychange', syncGate);\n observerRef.current?.disconnect();\n observerRef.current = null;\n observedRef.current = null;\n intersectingRef.current = true;\n cancelRaf();\n pendingRef.current = false;\n suspendedRef.current = false;\n };\n }, [cancelRaf, syncGate, syncObserver]);\n\n // Stable identity: `requestRedraw` and friends outlive the render that made\n // them, and consumers capture the handle.\n return useMemo(() => ({ request, cancel, isVisible }), [request, cancel, isVisible]);\n}\n","import { useEffect, useMemo, useRef } from 'react';\nimport { useLatest } from './useLatest';\nimport { useVisibleRaf } from './useVisibleRaf';\n\n/** Options for {@link useFrameClock}. */\nexport interface FrameClockOptions {\n /** Stop the clock where it is. Time spent paused is not counted. */\n paused?: boolean;\n /** What `seconds()` reads before any frame has run. Read once, at mount. */\n start?: number;\n /** Frame source, as on `useVisibleRaf`. */\n requestFrame?: (cb: FrameRequestCallback) => number;\n cancelFrame?: (handle: number) => void;\n}\n\n/** A clock a paint reads without rendering. It has `subscribe`, so a render\n * layer can name it as its redraw source. */\nexport interface FrameClock {\n /** Seconds of frames that ran: time hidden, paused, or with no subscriber is left out. */\n seconds(): number;\n /** Called once per frame. The clock runs while it has a subscriber. */\n subscribe(listener: () => void): () => void;\n}\n\n/**\n * Elapsed time for something that animates in its paint and nowhere else: a\n * shader uniform, a procedural layer. Each frame notifies the subscribers and\n * changes no React state, so the component that owns it does not re-render.\n */\nexport function useFrameClock(options: FrameClockOptions = {}): FrameClock {\n const { paused = false, requestFrame, cancelFrame } = options;\n const elapsed = useRef(options.start ?? 0);\n const last = useRef<number | null>(null);\n const listeners = useRef(new Set<() => void>());\n const pausedRef = useLatest(paused);\n\n const loop = useVisibleRaf(\n (now) => {\n if (last.current !== null) elapsed.current += (now - last.current) / 1000;\n last.current = now;\n for (const listener of [...listeners.current]) listener();\n if (listeners.current.size > 0 && !pausedRef.current) loop.request();\n },\n { requestFrame, cancelFrame, onResume: () => { last.current = null; } },\n );\n\n useEffect(() => {\n if (paused) return;\n last.current = null;\n if (listeners.current.size > 0) loop.request();\n return () => loop.cancel();\n }, [paused, loop]);\n\n return useMemo<FrameClock>(() => ({\n seconds: () => elapsed.current,\n subscribe(listener) {\n const set = listeners.current;\n if (set.size === 0) last.current = null;\n set.add(listener);\n if (!pausedRef.current) loop.request();\n return () => {\n set.delete(listener);\n if (set.size === 0) loop.cancel();\n };\n },\n }), [loop, pausedRef]);\n}\n","import { useInsertionEffect, useRef } from 'react';\n\n/**\n * `value`, or the one the last committed render returned when `same` says the\n * two are equal — so a caller rebuilding an equivalent object every render\n * hands its readers one identity. Compared against the committed value only:\n * a render React throws away never becomes the one the next render keeps.\n */\nexport function useStableByContent<T>(value: T, same: (a: T, b: T) => boolean): T {\n const held = useRef(value);\n const out = held.current === value || same(held.current, value) ? held.current : value;\n useInsertionEffect(() => {\n held.current = out;\n });\n return out;\n}\n\n/** Same length, and the same element at every index. */\nexport function sameList<T>(a: readonly T[], b: readonly T[]): boolean {\n return a.length === b.length && a.every((t, i) => t === b[i]);\n}\n"]}
1
+ {"version":3,"sources":["../src/assignRef.ts","../src/useCanvasFocus.ts","../src/useLatest.ts","../src/reducedMotion.ts","../src/useVisibleRaf.ts","../src/useFrameClock.ts","../src/useStableByContent.ts","../src/kitSettings.ts","../src/useKitSetting.ts"],"names":["useRef","useCallback","useMemo","useState","loop","useInsertionEffect","useLayoutEffect","useSyncExternalStore"],"mappings":"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","file":"index.js","sourcesContent":["import type { Ref } from 'react';\n\n/**\n * Write `value` into a ref a caller passed in, whichever shape it has: call a\n * callback ref, set an object ref's `current`, ignore `null` or `undefined`.\n *\n * For a component that keeps the element (or handle) for itself and also\n * forwards it to a ref prop — call this from the component's own callback ref.\n */\nexport function assignRef<T>(ref: Ref<T> | undefined, value: T | null): void {\n if (typeof ref === 'function') ref(value);\n else if (ref) (ref as { current: T | null }).current = value;\n}\n","/**\n * Track DOM focus on a canvas (or any focusable host element). Returns the\n * live `focused` boolean, props to spread onto the focusable element, and a\n * stable `getFocused()` getter for use inside per-frame closures (notably\n * `RenderLayer.draw`, which is constructed once but called every frame).\n *\n * No Canvas/SceneCanvas integration. Consumers compose the hook with core's\n * `gateLayer` (or their own gate) to hide affordances when blurred.\n */\nimport { useCallback, useMemo, useRef, useState } from 'react';\n\n/** Options for `useCanvasFocus`. */\nexport interface UseCanvasFocusOptions {\n /** Initial focus state. Default `false` — handles hidden until first click. */\n initial?: boolean;\n /** `tabIndex` to set on the focusable element. Default `0` (focusable in\n * document tab order). Set to `-1` for programmatic-only focus. */\n tabIndex?: number;\n}\n\n/** What `useCanvasFocus` hands back: the focus state, the props that\n * maintain it, and a getter for reading it from per-frame code. */\nexport interface CanvasFocusReturn {\n /** Live boolean — true when the element has DOM focus. Triggers re-renders\n * when the focus state changes (set via React state). */\n focused: boolean;\n /** Spread onto the focusable element (e.g. the canvas). Includes\n * `tabIndex`, `onFocus`, `onBlur`. */\n focusProps: {\n tabIndex: number;\n onFocus: () => void;\n onBlur: () => void;\n };\n /** Stable getter for the live focused state. Use inside per-frame closures\n * like `RenderLayer.draw`, where the layer is constructed once and\n * reading `focused` directly would capture the value at construction\n * time. The getter always returns the current value. */\n getFocused: () => boolean;\n /** Imperative focus / blur. Useful for tests and programmatic control. */\n setFocused: (next: boolean) => void;\n}\n\n/** Track DOM focus on the canvas, so chrome that should only show while the\n * canvas is focused can be gated on it. */\nexport function useCanvasFocus(options: UseCanvasFocusOptions = {}): CanvasFocusReturn {\n const { initial = false, tabIndex = 0 } = options;\n const [focused, setFocusedState] = useState<boolean>(initial);\n // Every state change goes through a handler below that writes this first.\n const focusedRef = useRef<boolean>(initial);\n\n const onFocus = useCallback(() => {\n if (!focusedRef.current) {\n focusedRef.current = true;\n setFocusedState(true);\n }\n }, []);\n const onBlur = useCallback(() => {\n if (focusedRef.current) {\n focusedRef.current = false;\n setFocusedState(false);\n }\n }, []);\n const getFocused = useCallback(() => focusedRef.current, []);\n const setFocused = useCallback((next: boolean) => {\n if (focusedRef.current === next) return;\n focusedRef.current = next;\n setFocusedState(next);\n }, []);\n\n // Stable until tabIndex changes, so it can be spread onto memoized elements\n // without retriggering downstream effects.\n const focusProps = useMemo(() => ({ tabIndex, onFocus, onBlur }), [tabIndex, onFocus, onBlur]);\n\n return {\n focused,\n focusProps,\n getFocused,\n setFocused,\n };\n}\n","import { useInsertionEffect, useRef } from 'react';\n\n/**\n * A ref holding the `value` of the last **committed** render, for event\n * handlers, frame loops and other code that runs outside render and must not\n * re-subscribe every time `value` changes.\n *\n * Writing `ref.current = value` in a render body is not the same thing: React\n * may start a render and throw it away (a transition that suspends, a render\n * interrupted by a more urgent update), and the value that render computed\n * would stay behind for the next event to read. This writes from\n * `useInsertionEffect`, so only a render that commits ever lands, and it lands\n * before any layout effect in that commit runs.\n *\n * Don't read `.current` during render expecting this render's value: until the\n * commit it holds the previous committed one (on mount, this first `value`).\n * Render code should use `value` itself.\n */\nexport function useLatest<T>(value: T): { readonly current: T } {\n const ref = useRef(value);\n useInsertionEffect(() => {\n ref.current = value;\n });\n return ref;\n}\n","import { useSyncExternalStore } from 'react';\n\nconst QUERY = '(prefers-reduced-motion: reduce)';\n\n/** Whether the reader has asked for less motion. False where there is no `matchMedia` to ask. */\nexport function prefersReducedMotion(): boolean {\n return typeof matchMedia === 'function' && matchMedia(QUERY).matches;\n}\n\nfunction subscribe(onChange: () => void): () => void {\n if (typeof matchMedia !== 'function') return () => {};\n const list = matchMedia(QUERY);\n list.addEventListener?.('change', onChange);\n return () => list.removeEventListener?.('change', onChange);\n}\n\n/** {@link prefersReducedMotion}, re-rendering when the preference changes. */\nexport function usePrefersReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, prefersReducedMotion, () => false);\n}\n","/**\n * The one place weasel decides whether a frame may run, so that \"a loop does no\n * work nobody can see\" holds by construction rather than by every loop author\n * remembering. Nothing runs while the document is hidden; a loop that names an\n * element also stops while that element is outside the viewport.\n *\n * A request made while suspended is held, not dropped, and re-armed when the\n * surface comes back — so a loop never needs to poll visibility itself.\n */\n\nimport { type RefObject, useCallback, useLayoutEffect, useMemo, useRef } from 'react';\nimport { useLatest } from './useLatest';\n\n/** What a loop names as its element, resolved every time the gate is consulted\n * so a ref filled in after mount still starts the observer. */\nexport type VisibleRafTarget = RefObject<Element | null> | (() => Element | null);\n\n/** Options for {@link useVisibleRaf}. */\nexport interface VisibleRafOptions {\n /** Also gate on this element's intersection with the viewport. Omit it and\n * the loop is gated on document visibility alone. */\n target?: VisibleRafTarget;\n /**\n * Run frames regardless of visibility. The escape hatch for a loop that is\n * not painting for a viewer — an offscreen recording, an export driving its\n * own frames. It is the wrong fix for a loop that stalls after a tab switch:\n * that is a missing `request()`, and setting this hides it.\n */\n dangerouslyRunWhenHidden?: boolean;\n /** Frame clock. Read live, so a test may inject one after mount. */\n requestFrame?: (cb: FrameRequestCallback) => number;\n cancelFrame?: (handle: number) => void;\n /**\n * Runs once when the loop leaves suspension, before the frame that follows\n * it. Where a loop that measures elapsed time drops the interval it spent\n * suspended — without it, an hour hidden arrives as one hour-long frame.\n */\n onResume?: () => void;\n}\n\n/** The handle {@link useVisibleRaf} returns. Nothing runs until `request()` is called. */\nexport interface VisibleRaf {\n /** Ask for a frame. Idempotent while one is outstanding, and held rather than\n * dropped while suspended. Identity is stable for the component's lifetime. */\n request(): void;\n /** Drop the outstanding request, held or scheduled. */\n cancel(): void;\n /** Whether a frame would run right now. */\n isVisible(): boolean;\n}\n\nconst documentHidden = (): boolean => typeof document !== 'undefined' && document.hidden;\n\nconst resolveTarget = (target: VisibleRafTarget | undefined): Element | null =>\n typeof target === 'function' ? target() : (target?.current ?? null);\n\n/**\n * @param frame Runs on the frame, with the timestamp the clock supplied and the\n * loop's own handle. A continuous loop calls `loop.request()` from inside it;\n * a one-shot loop does not.\n */\nexport function useVisibleRaf(\n frame: (time: number, loop: VisibleRaf) => void,\n options: VisibleRafOptions = {},\n): VisibleRaf {\n const frameRef = useLatest(frame);\n const optsRef = useLatest(options);\n\n const rafRef = useRef<number | null>(null);\n const pendingRef = useRef(false);\n const suspendedRef = useRef(false);\n const aliveRef = useRef(true);\n const intersectingRef = useRef(true);\n const observerRef = useRef<IntersectionObserver | null>(null);\n const observedRef = useRef<Element | null>(null);\n const handleRef = useRef<VisibleRaf | null>(null);\n\n const isVisible = useCallback(\n (): boolean =>\n optsRef.current.dangerouslyRunWhenHidden === true ||\n (!documentHidden() && intersectingRef.current),\n [optsRef],\n );\n\n const cancelRaf = useCallback(() => {\n if (rafRef.current === null) return;\n (optsRef.current.cancelFrame ?? cancelAnimationFrame)(rafRef.current);\n rafRef.current = null;\n }, [optsRef]);\n\n const scheduleRaf = useCallback(() => {\n if (rafRef.current !== null || !aliveRef.current) return;\n rafRef.current = (optsRef.current.requestFrame ?? requestAnimationFrame)((time) => {\n // Released before the frame runs, so a `request()` issued from inside it\n // schedules the next one instead of being swallowed.\n rafRef.current = null;\n if (!aliveRef.current || !pendingRef.current || !isVisible()) return;\n pendingRef.current = false;\n frameRef.current(time, handleRef.current!);\n });\n }, [frameRef, isVisible, optsRef]);\n\n /** Attaches the observer once the named element exists. Ref callbacks and\n * lazily-mounted hosts fill their refs after this hook's effect has run. */\n const syncObserver = useCallback(() => {\n const io = observerRef.current;\n if (!io) return;\n const el = resolveTarget(optsRef.current.target);\n if (el === observedRef.current) return;\n if (observedRef.current) io.unobserve(observedRef.current);\n observedRef.current = el;\n // An element that has gone away stops gating rather than stopping the loop.\n intersectingRef.current = true;\n if (el) io.observe(el);\n }, [optsRef]);\n\n /** Re-reads the gate after something that could have changed it. Suspending\n * drops the outstanding frame but keeps the request; resuming hands the\n * loop its rebase before re-arming. */\n const syncGate = useCallback(() => {\n if (!isVisible()) {\n if (suspendedRef.current) return;\n suspendedRef.current = true;\n cancelRaf();\n return;\n }\n if (!suspendedRef.current) return;\n suspendedRef.current = false;\n if (!aliveRef.current) return;\n optsRef.current.onResume?.();\n if (pendingRef.current) scheduleRaf();\n }, [cancelRaf, isVisible, optsRef, scheduleRaf]);\n\n const request = useCallback(() => {\n if (!aliveRef.current) return;\n syncObserver();\n pendingRef.current = true;\n if (!isVisible()) {\n suspendedRef.current = true;\n return;\n }\n scheduleRaf();\n }, [isVisible, scheduleRaf, syncObserver]);\n\n const cancel = useCallback(() => {\n pendingRef.current = false;\n cancelRaf();\n }, [cancelRaf]);\n\n // Re-armed in setup because the refs survive StrictMode's simulated remount, and\n // a layout effect so the arming precedes the host's own layout effects — a loop\n // driven from one of those would otherwise be declined on the second mount.\n useLayoutEffect(() => {\n aliveRef.current = true;\n document.addEventListener('visibilitychange', syncGate);\n if (typeof IntersectionObserver !== 'undefined') {\n observerRef.current = new IntersectionObserver((entries) => {\n intersectingRef.current = entries.some((e) => e.isIntersecting);\n syncGate();\n });\n syncObserver();\n }\n return () => {\n aliveRef.current = false;\n document.removeEventListener('visibilitychange', syncGate);\n observerRef.current?.disconnect();\n observerRef.current = null;\n observedRef.current = null;\n intersectingRef.current = true;\n cancelRaf();\n pendingRef.current = false;\n suspendedRef.current = false;\n };\n }, [cancelRaf, syncGate, syncObserver]);\n\n // Stable identity: `requestRedraw` and friends outlive the render that made\n // them, and consumers capture the handle.\n const handle = useMemo(() => ({ request, cancel, isVisible }), [request, cancel, isVisible]);\n useLayoutEffect(() => { handleRef.current = handle; }, [handle]);\n return handle;\n}\n","import { createEmitter } from '@weasel-js/registry';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { useLatest } from './useLatest';\nimport { useVisibleRaf } from './useVisibleRaf';\n\n/** Options for {@link useFrameClock}. */\nexport interface FrameClockOptions {\n /** Stop the clock where it is. Time spent paused is not counted. */\n paused?: boolean;\n /** What `seconds()` reads before any frame has run. Read once, at mount. */\n start?: number;\n /** Frame source, as on `useVisibleRaf`. */\n requestFrame?: (cb: FrameRequestCallback) => number;\n cancelFrame?: (handle: number) => void;\n}\n\n/** A clock a paint reads without rendering. It has `subscribe`, so a render\n * layer can name it as its redraw source. */\nexport interface FrameClock {\n /** Seconds of frames that ran: time hidden, paused, or with no subscriber is left out. */\n seconds(): number;\n /** Called once per frame. The clock runs while it has a subscriber. */\n subscribe(listener: () => void): () => void;\n}\n\n/**\n * Elapsed time for something that animates in its paint and nowhere else: a\n * shader uniform, a procedural layer. Each frame notifies the subscribers and\n * changes no React state, so the component that owns it does not re-render.\n */\nexport function useFrameClock(options: FrameClockOptions = {}): FrameClock {\n const { paused = false, requestFrame, cancelFrame } = options;\n const elapsed = useRef(options.start ?? 0);\n const last = useRef<number | null>(null);\n const [ticked] = useState(() => createEmitter());\n const pausedRef = useLatest(paused);\n\n const loop = useVisibleRaf(\n (now, loop) => {\n if (last.current !== null) elapsed.current += (now - last.current) / 1000;\n last.current = now;\n ticked.emit();\n if (ticked.size > 0 && !pausedRef.current) loop.request();\n },\n { requestFrame, cancelFrame, onResume: () => { last.current = null; } },\n );\n\n useEffect(() => {\n if (paused) return;\n last.current = null;\n if (ticked.size > 0) loop.request();\n return () => loop.cancel();\n }, [paused, loop, ticked]);\n\n return useMemo<FrameClock>(() => ({\n seconds: () => elapsed.current,\n subscribe(listener) {\n if (ticked.size === 0) last.current = null;\n const off = ticked.subscribe(listener);\n if (!pausedRef.current) loop.request();\n return () => {\n off();\n if (ticked.size === 0) loop.cancel();\n };\n },\n }), [loop, pausedRef, ticked]);\n}\n","import { useInsertionEffect, useRef } from 'react';\n\n/**\n * `value`, or the one the last committed render returned when `same` says the\n * two are equal — so a caller rebuilding an equivalent object every render\n * hands its readers one identity. Compared against the committed value only:\n * a render React throws away never becomes the one the next render keeps.\n */\nexport function useStableByContent<T>(value: T, same: (a: T, b: T) => boolean): T {\n const held = useRef(value);\n const out = held.current === value || same(held.current, value) ? held.current : value;\n useInsertionEffect(() => {\n held.current = out;\n });\n return out;\n}\n\n/** Same length, and the same element at every index. */\nexport function sameList<T>(a: readonly T[], b: readonly T[]): boolean {\n return a.length === b.length && a.every((t, i) => t === b[i]);\n}\n","import type { KitSettings } from './index';\n\n/** A value a settings leaf can hold. */\nexport type KitSettingLeaf = number | string | boolean;\n\n/** `T` with every area and leaf optional: what a provider sets. */\nexport type KitSettingsOverrides<T = KitSettings> = {\n [K in keyof T]?: T[K] extends KitSettingLeaf ? T[K] : KitSettingsOverrides<T[K]>;\n};\n\n/** The dotted path of every leaf in `T`: `'tree.typeaheadMs'`. */\nexport type KitSettingPath<T = KitSettings> = {\n [K in keyof T & string]: T[K] extends KitSettingLeaf ? K : `${K}.${KitSettingPath<T[K]>}`;\n}[keyof T & string];\n\n/** The type of the leaf at `P` in `T`. */\nexport type KitSettingAt<P extends string, T = KitSettings> = P extends `${infer H}.${infer R}`\n ? H extends keyof T ? KitSettingAt<R, T[H]> : never\n : P extends keyof T ? T[P] : never;\n\n/** Reads settings by path and says when any of them changed. */\nexport interface KitSettingsStore<T = KitSettings> {\n /** The value at `path`: this store's own, else the nearest ancestor's, else `undefined`. */\n get<P extends KitSettingPath<T>>(path: P): KitSettingAt<P, T> | undefined;\n subscribe(listener: () => void): () => void;\n}\n\n/** A store whose own overrides can be replaced. */\nexport interface WritableKitSettingsStore<T = KitSettings> extends KitSettingsStore<T> {\n /** Replace this store's own overrides, notifying only when some leaf's value changed. */\n set(overrides: KitSettingsOverrides<T>): void;\n}\n\nfunction leafAt(tree: unknown, path: string): unknown {\n let at = tree;\n for (const key of path.split('.')) {\n if (at === null || typeof at !== 'object' || !Object.hasOwn(at, key)) return undefined;\n at = (at as Record<string, unknown>)[key];\n }\n return at;\n}\n\nfunction flatten(tree: unknown, prefix: string, out: Map<string, unknown>): Map<string, unknown> {\n if (tree === null || typeof tree !== 'object') return out;\n for (const [key, value] of Object.entries(tree)) {\n if (value === undefined) continue;\n const path = prefix === '' ? key : `${prefix}.${key}`;\n if (value !== null && typeof value === 'object') flatten(value, path, out);\n else out.set(path, value);\n }\n return out;\n}\n\nconst sameLeaves = (a: Map<string, unknown>, b: Map<string, unknown>): boolean =>\n a.size === b.size && [...a].every(([path, value]) => b.has(path) && Object.is(b.get(path), value));\n\n/**\n * A settings store holding `overrides`, reading through to `parent` for any\n * leaf it does not set. A change in the parent notifies this store's\n * listeners too.\n */\nexport function createKitSettingsStore<T = KitSettings>(\n overrides: KitSettingsOverrides<T> = {},\n parent: KitSettingsStore<T> | null = null,\n): WritableKitSettingsStore<T> {\n let own: unknown = overrides;\n let leaves = flatten(own, '', new Map());\n const listeners = new Set<() => void>();\n const notify = (): void => {\n for (const listener of [...listeners]) listener();\n };\n let unhookParent: (() => void) | null = null;\n\n return {\n get(path) {\n const value = leafAt(own, path);\n return (value !== undefined ? value : parent?.get(path)) as never;\n },\n subscribe(listener) {\n listeners.add(listener);\n if (parent && unhookParent === null) unhookParent = parent.subscribe(notify);\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0 && unhookParent) {\n unhookParent();\n unhookParent = null;\n }\n };\n },\n set(next) {\n const nextLeaves = flatten(next, '', new Map());\n own = next;\n if (sameLeaves(leaves, nextLeaves)) return;\n leaves = nextLeaves;\n notify();\n },\n };\n}\n\n/** The store read where no provider is mounted: it sets nothing. */\nexport const NO_KIT_SETTINGS: KitSettingsStore = createKitSettingsStore();\n","import { createContext, createElement, useCallback, useContext, useLayoutEffect, useState, useSyncExternalStore, type ReactNode } from 'react';\nimport {\n createKitSettingsStore,\n NO_KIT_SETTINGS,\n type KitSettingAt,\n type KitSettingPath,\n type KitSettingsOverrides,\n type KitSettingsStore,\n} from './kitSettings';\n\nconst KitSettingsContext = createContext<KitSettingsStore>(NO_KIT_SETTINGS);\n\n/**\n * Set kit settings for everything inside. Nested providers merge: a leaf this\n * one leaves unset reads through to the one above.\n */\nexport function KitSettingsProvider({ value, children }: { value: KitSettingsOverrides; children?: ReactNode }): ReactNode {\n const parent = useContext(KitSettingsContext);\n const [store, setStore] = useState(() => ({ parent, store: createKitSettingsStore(value, parent) }));\n if (store.parent !== parent) setStore({ parent, store: createKitSettingsStore(value, parent) });\n useLayoutEffect(() => {\n store.store.set(value);\n }, [store, value]);\n return createElement(KitSettingsContext.Provider, { value: store.store }, children);\n}\n\n/**\n * The store the nearest provider holds, for code outside React — a gesture\n * recognizer, a canvas action — to read with `get(path)` at the moment it\n * needs a value.\n */\nexport function useKitSettings(): KitSettingsStore {\n return useContext(KitSettingsContext);\n}\n\n/**\n * The setting at `path`, or `undefined` where no provider sets it; the caller\n * falls back to its own constant. Re-renders only when this path's value changes.\n */\nexport function useKitSetting<P extends KitSettingPath>(path: P): KitSettingAt<P> | undefined {\n const store = useKitSettings();\n const read = useCallback(() => store.get(path), [store, path]);\n return useSyncExternalStore(store.subscribe, read, read);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weasel-js/react",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Generic React hooks the weasel packages share: a committed-value ref and content-stable identity. No weasel domain in it.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,6 +21,9 @@
21
21
  "peerDependenciesMeta": {
22
22
  "react": { "optional": true }
23
23
  },
24
+ "dependencies": {
25
+ "@weasel-js/registry": "1.11.0"
26
+ },
24
27
  "author": "orochi235",
25
28
  "homepage": "https://orochi235.github.io/weasel/",
26
29
  "repository": {