@tldraw/state-react 5.5.0-canary.fe69c9620403 → 5.5.0-canary.ffd1e744a5b4

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/dist-cjs/index.js CHANGED
@@ -37,7 +37,7 @@ var import_useStateTracking = require("./lib/useStateTracking");
37
37
  var import_useValue = require("./lib/useValue");
38
38
  (0, import_utils.registerTldrawLibraryVersion)(
39
39
  "@tldraw/state-react",
40
- "5.5.0-canary.fe69c9620403",
40
+ "5.5.0-canary.ffd1e744a5b4",
41
41
  "cjs"
42
42
  );
43
43
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/useReactor.ts"],
4
- "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport { throttleToNextFrame } from '@tldraw/utils'\nimport { useEffect } from 'react'\n\n/**\n * A React hook that runs a side effect in response to changes in signals (reactive state).\n *\n * The effect function will automatically track any signals (atoms, computed values) that are\n * accessed during its execution. When any of those signals change, the effect will be\n * scheduled to run again on the next animation frame.\n *\n * This is useful for performing side effects (like updating the DOM, making API calls, or\n * updating external state) in response to changes in tldraw's reactive state system, while\n * keeping those effects efficiently batched and throttled.\n *\n * @example\n * ```tsx\n * import { useReactor, useEditor } from 'tldraw'\n *\n * function MyComponent() {\n * const editor = useEditor()\n *\n * // Update document title when shapes change\n * useReactor(\n * 'update title',\n * () => {\n * const shapes = editor.getCurrentPageShapes()\n * document.title = `Shapes: ${shapes.length}`\n * },\n * [editor]\n * )\n *\n * return <div>...</div>\n * }\n * ```\n *\n * @example\n * ```tsx\n * import { useReactor, useEditor } from 'tldraw'\n *\n * function SelectionAnnouncer() {\n * const editor = useEditor()\n *\n * // Announce selection changes for accessibility\n * useReactor(\n * 'announce selection',\n * () => {\n * const selectedIds = editor.getSelectedShapeIds()\n * if (selectedIds.length > 0) {\n * console.log(`Selected ${selectedIds.length} shape(s)`)\n * }\n * },\n * [editor]\n * )\n *\n * return null\n * }\n * ```\n *\n * @remarks\n * The effect is throttled to run at most once per animation frame using `requestAnimationFrame`.\n * This makes it suitable for effects that need to respond to state changes but don't need to\n * run synchronously.\n *\n * If you need the effect to run immediately without throttling, use {@link useQuickReactor} instead.\n *\n * The effect function will be re-created when any of the `deps` change, similar to React's\n * `useEffect`. The effect automatically tracks which signals it accesses, so you don't need\n * to manually specify them as dependencies.\n *\n * @param name - A debug name for the effect, useful for debugging and performance profiling.\n * @param reactFn - The effect function to run. Any signals accessed in this function will be tracked.\n * @param deps - React dependencies array. The effect will be recreated when these change. Defaults to `[]`.\n *\n * @public\n */\nexport function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {\n\tuseEffect(() => {\n\t\tlet cancelFn: () => void | undefined\n\t\tconst scheduler = new EffectScheduler(name, reactFn, {\n\t\t\tscheduleEffect: (cb) => {\n\t\t\t\tcancelFn = throttleToNextFrame(cb)\n\t\t\t},\n\t\t})\n\t\tscheduler.attach()\n\t\tscheduler.execute()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t\tcancelFn?.()\n\t\t}\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n}\n"],
4
+ "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport { throttleToNextFrame } from '@tldraw/utils'\nimport { useEffect } from 'react'\n\n/**\n * A React hook that runs a side effect in response to changes in signals (reactive state).\n *\n * The effect function will automatically track any signals (atoms, computed values) that are\n * accessed during its execution. When any of those signals change, the effect will be\n * scheduled to run again on the next animation frame.\n *\n * This is useful for performing side effects (like updating the DOM, making API calls, or\n * updating external state) in response to changes in tldraw's reactive state system, while\n * keeping those effects efficiently batched and throttled.\n *\n * @example\n * ```tsx\n * import { useReactor, useEditor } from 'tldraw'\n *\n * function MyComponent() {\n * const editor = useEditor()\n *\n * // Update document title when shapes change\n * useReactor(\n * 'update title',\n * () => {\n * const shapes = editor.getCurrentPageShapes()\n * document.title = `Shapes: ${shapes.length}`\n * },\n * [editor]\n * )\n *\n * return <div>...</div>\n * }\n * ```\n *\n * @example\n * ```tsx\n * import { useReactor, useEditor } from 'tldraw'\n *\n * function SelectionAnnouncer() {\n * const editor = useEditor()\n *\n * // Announce selection changes for accessibility\n * useReactor(\n * 'announce selection',\n * () => {\n * const selectedIds = editor.getSelectedShapeIds()\n * if (selectedIds.length > 0) {\n * console.log(`Selected ${selectedIds.length} shape(s)`)\n * }\n * },\n * [editor]\n * )\n *\n * return null\n * }\n * ```\n *\n * @remarks\n * The effect is throttled to run at most once per animation frame using `requestAnimationFrame`.\n * This makes it suitable for effects that need to respond to state changes but don't need to\n * run synchronously.\n *\n * If you need the effect to run immediately without throttling, use {@link useQuickReactor} instead.\n *\n * The effect function will be re-created when any of the `deps` change, similar to React's\n * `useEffect`. The effect automatically tracks which signals it accesses, so you don't need\n * to manually specify them as dependencies.\n *\n * @param name - A debug name for the effect, useful for debugging and performance profiling.\n * @param reactFn - The effect function to run. Any signals accessed in this function will be tracked.\n * @param deps - React dependencies array. The effect will be recreated when these change. Defaults to `[]`.\n *\n * @public\n */\nexport function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {\n\tuseEffect(() => {\n\t\tlet cancelFn: (() => void) | undefined\n\t\tconst scheduler = new EffectScheduler(name, reactFn, {\n\t\t\tscheduleEffect: (cb) => {\n\t\t\t\tcancelFn = throttleToNextFrame(cb)\n\t\t\t},\n\t\t})\n\t\tscheduler.attach()\n\t\tscheduler.execute()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t\tcancelFn?.()\n\t\t}\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAgC;AAChC,mBAAoC;AACpC,mBAA0B;AA0EnB,SAAS,WAAW,MAAc,SAAqB,OAA0B,CAAC,GAAG;AAC3F,8BAAU,MAAM;AACf,QAAI;AACJ,UAAM,YAAY,IAAI,6BAAgB,MAAM,SAAS;AAAA,MACpD,gBAAgB,CAAC,OAAO;AACvB,uBAAW,kCAAoB,EAAE;AAAA,MAClC;AAAA,IACD,CAAC;AACD,cAAU,OAAO;AACjB,cAAU,QAAQ;AAClB,WAAO,MAAM;AACZ,gBAAU,OAAO;AACjB,iBAAW;AAAA,IACZ;AAAA,EAED,GAAG,IAAI;AACR;",
6
6
  "names": []
7
7
  }
@@ -47,7 +47,7 @@ function useStateTracking(name, render, deps = []) {
47
47
  const scheduler2 = new import_state.EffectScheduler(
48
48
  `useStateTracking(${name})`,
49
49
  // this is what `scheduler.execute()` will call
50
- () => renderRef.current?.(),
50
+ () => renderRef.current(),
51
51
  // this is what will be invoked when @tldraw/state detects a change in an upstream reactive value
52
52
  {
53
53
  scheduleEffect() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/useStateTracking.ts"],
4
- "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport React from 'react'\n\n/**\n * Wraps some synchronous react render logic in a reactive tracking context.\n *\n * This allows you to use reactive values transparently.\n *\n * See the `track` component wrapper, which uses this under the hood.\n *\n * @param name - A debug name for the reactive tracking context\n * @param render - The render function that accesses reactive values\n * @param deps - Optional dependency array to control when the tracking context is recreated\n * @returns The result of calling the render function\n *\n * @example\n * ```ts\n * function MyComponent() {\n * return useStateTracking('MyComponent', () => {\n * const editor = useEditor()\n * return <div>Num shapes: {editor.getCurrentPageShapes().length}</div>\n * })\n * }\n * ```\n *\n *\n * @public\n */\nexport function useStateTracking<T>(name: string, render: () => T, deps: unknown[] = []): T {\n\t// This hook creates an effect scheduler that will trigger re-renders when its reactive dependencies change, but it\n\t// defers the actual execution of the effect to the consumer of this hook.\n\n\t// We need the exec fn to always be up-to-date when calling scheduler.execute() but it'd be wasteful to\n\t// instantiate a new EffectScheduler on every render, so we use an immediately-updated ref\n\t// to wrap it\n\tconst renderRef = React.useRef(render)\n\trenderRef.current = render\n\n\tconst [scheduler, subscribe, getSnapshot] = React.useMemo(() => {\n\t\tlet scheduleUpdate = null as null | (() => void)\n\t\t// useSyncExternalStore requires a subscribe function that returns an unsubscribe function\n\t\tconst subscribe = (cb: () => void) => {\n\t\t\tscheduleUpdate = cb\n\t\t\treturn () => {\n\t\t\t\tscheduleUpdate = null\n\t\t\t}\n\t\t}\n\n\t\tconst scheduler = new EffectScheduler(\n\t\t\t`useStateTracking(${name})`,\n\t\t\t// this is what `scheduler.execute()` will call\n\t\t\t() => renderRef.current?.(),\n\t\t\t// this is what will be invoked when @tldraw/state detects a change in an upstream reactive value\n\t\t\t{\n\t\t\t\tscheduleEffect() {\n\t\t\t\t\tscheduleUpdate?.()\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\n\t\t// we use an incrementing number based on when this\n\t\tconst getSnapshot = () => scheduler.scheduleCount\n\n\t\treturn [scheduler, subscribe, getSnapshot]\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [name, ...deps])\n\n\tReact.useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n\t// reactive dependencies are captured when `scheduler.execute()` is called\n\t// and then to make it reactive we wait for a `useEffect` to 'attach'\n\t// this allows us to avoid rendering outside of React's render phase\n\t// and avoid 'zombie' components that try to render with bad/deleted data before\n\t// react has a chance to umount them.\n\tReact.useEffect(() => {\n\t\tscheduler.attach()\n\t\t// do not execute, we only do that in render\n\t\tscheduler.maybeScheduleEffect()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t}\n\t}, [scheduler])\n\n\treturn scheduler.execute()\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAgC;AAChC,mBAAkB;AA2BX,SAAS,iBAAoB,MAAc,QAAiB,OAAkB,CAAC,GAAM;AAO3F,QAAM,YAAY,aAAAA,QAAM,OAAO,MAAM;AACrC,YAAU,UAAU;AAEpB,QAAM,CAAC,WAAW,WAAW,WAAW,IAAI,aAAAA,QAAM,QAAQ,MAAM;AAC/D,QAAI,iBAAiB;AAErB,UAAMC,aAAY,CAAC,OAAmB;AACrC,uBAAiB;AACjB,aAAO,MAAM;AACZ,yBAAiB;AAAA,MAClB;AAAA,IACD;AAEA,UAAMC,aAAY,IAAI;AAAA,MACrB,oBAAoB,IAAI;AAAA;AAAA,MAExB,MAAM,UAAU,UAAU;AAAA;AAAA,MAE1B;AAAA,QACC,iBAAiB;AAChB,2BAAiB;AAAA,QAClB;AAAA,MACD;AAAA,IACD;AAGA,UAAMC,eAAc,MAAMD,WAAU;AAEpC,WAAO,CAACA,YAAWD,YAAWE,YAAW;AAAA,EAE1C,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC;AAElB,eAAAH,QAAM,qBAAqB,WAAW,aAAa,WAAW;AAO9D,eAAAA,QAAM,UAAU,MAAM;AACrB,cAAU,OAAO;AAEjB,cAAU,oBAAoB;AAC9B,WAAO,MAAM;AACZ,gBAAU,OAAO;AAAA,IAClB;AAAA,EACD,GAAG,CAAC,SAAS,CAAC;AAEd,SAAO,UAAU,QAAQ;AAC1B;",
4
+ "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport React from 'react'\n\n/**\n * Wraps some synchronous react render logic in a reactive tracking context.\n *\n * This allows you to use reactive values transparently.\n *\n * See the `track` component wrapper, which uses this under the hood.\n *\n * @param name - A debug name for the reactive tracking context\n * @param render - The render function that accesses reactive values\n * @param deps - Optional dependency array to control when the tracking context is recreated\n * @returns The result of calling the render function\n *\n * @example\n * ```ts\n * function MyComponent() {\n * return useStateTracking('MyComponent', () => {\n * const editor = useEditor()\n * return <div>Num shapes: {editor.getCurrentPageShapes().length}</div>\n * })\n * }\n * ```\n *\n *\n * @public\n */\nexport function useStateTracking<T>(name: string, render: () => T, deps: unknown[] = []): T {\n\t// This hook creates an effect scheduler that will trigger re-renders when its reactive dependencies change, but it\n\t// defers the actual execution of the effect to the consumer of this hook.\n\n\t// We need the exec fn to always be up-to-date when calling scheduler.execute() but it'd be wasteful to\n\t// instantiate a new EffectScheduler on every render, so we use an immediately-updated ref\n\t// to wrap it\n\tconst renderRef = React.useRef(render)\n\trenderRef.current = render\n\n\tconst [scheduler, subscribe, getSnapshot] = React.useMemo(() => {\n\t\tlet scheduleUpdate = null as null | (() => void)\n\t\t// useSyncExternalStore requires a subscribe function that returns an unsubscribe function\n\t\tconst subscribe = (cb: () => void) => {\n\t\t\tscheduleUpdate = cb\n\t\t\treturn () => {\n\t\t\t\tscheduleUpdate = null\n\t\t\t}\n\t\t}\n\n\t\tconst scheduler = new EffectScheduler(\n\t\t\t`useStateTracking(${name})`,\n\t\t\t// this is what `scheduler.execute()` will call\n\t\t\t() => renderRef.current(),\n\t\t\t// this is what will be invoked when @tldraw/state detects a change in an upstream reactive value\n\t\t\t{\n\t\t\t\tscheduleEffect() {\n\t\t\t\t\tscheduleUpdate?.()\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\n\t\t// we use an incrementing number based on when this\n\t\tconst getSnapshot = () => scheduler.scheduleCount\n\n\t\treturn [scheduler, subscribe, getSnapshot]\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [name, ...deps])\n\n\tReact.useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n\t// reactive dependencies are captured when `scheduler.execute()` is called\n\t// and then to make it reactive we wait for a `useEffect` to 'attach'\n\t// this allows us to avoid rendering outside of React's render phase\n\t// and avoid 'zombie' components that try to render with bad/deleted data before\n\t// react has a chance to umount them.\n\tReact.useEffect(() => {\n\t\tscheduler.attach()\n\t\t// do not execute, we only do that in render\n\t\tscheduler.maybeScheduleEffect()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t}\n\t}, [scheduler])\n\n\treturn scheduler.execute()\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAgC;AAChC,mBAAkB;AA2BX,SAAS,iBAAoB,MAAc,QAAiB,OAAkB,CAAC,GAAM;AAO3F,QAAM,YAAY,aAAAA,QAAM,OAAO,MAAM;AACrC,YAAU,UAAU;AAEpB,QAAM,CAAC,WAAW,WAAW,WAAW,IAAI,aAAAA,QAAM,QAAQ,MAAM;AAC/D,QAAI,iBAAiB;AAErB,UAAMC,aAAY,CAAC,OAAmB;AACrC,uBAAiB;AACjB,aAAO,MAAM;AACZ,yBAAiB;AAAA,MAClB;AAAA,IACD;AAEA,UAAMC,aAAY,IAAI;AAAA,MACrB,oBAAoB,IAAI;AAAA;AAAA,MAExB,MAAM,UAAU,QAAQ;AAAA;AAAA,MAExB;AAAA,QACC,iBAAiB;AAChB,2BAAiB;AAAA,QAClB;AAAA,MACD;AAAA,IACD;AAGA,UAAMC,eAAc,MAAMD,WAAU;AAEpC,WAAO,CAACA,YAAWD,YAAWE,YAAW;AAAA,EAE1C,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC;AAElB,eAAAH,QAAM,qBAAqB,WAAW,aAAa,WAAW;AAO9D,eAAAA,QAAM,UAAU,MAAM;AACrB,cAAU,OAAO;AAEjB,cAAU,oBAAoB;AAC9B,WAAO,MAAM;AACZ,gBAAU,OAAO;AAAA,IAClB;AAAA,EACD,GAAG,CAAC,SAAS,CAAC;AAEd,SAAO,UAAU,QAAQ;AAC1B;",
6
6
  "names": ["React", "subscribe", "scheduler", "getSnapshot"]
7
7
  }
@@ -8,7 +8,7 @@ import { useStateTracking } from "./lib/useStateTracking.mjs";
8
8
  import { useValue } from "./lib/useValue.mjs";
9
9
  registerTldrawLibraryVersion(
10
10
  "@tldraw/state-react",
11
- "5.5.0-canary.fe69c9620403",
11
+ "5.5.0-canary.ffd1e744a5b4",
12
12
  "esm"
13
13
  );
14
14
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/useReactor.ts"],
4
- "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport { throttleToNextFrame } from '@tldraw/utils'\nimport { useEffect } from 'react'\n\n/**\n * A React hook that runs a side effect in response to changes in signals (reactive state).\n *\n * The effect function will automatically track any signals (atoms, computed values) that are\n * accessed during its execution. When any of those signals change, the effect will be\n * scheduled to run again on the next animation frame.\n *\n * This is useful for performing side effects (like updating the DOM, making API calls, or\n * updating external state) in response to changes in tldraw's reactive state system, while\n * keeping those effects efficiently batched and throttled.\n *\n * @example\n * ```tsx\n * import { useReactor, useEditor } from 'tldraw'\n *\n * function MyComponent() {\n * const editor = useEditor()\n *\n * // Update document title when shapes change\n * useReactor(\n * 'update title',\n * () => {\n * const shapes = editor.getCurrentPageShapes()\n * document.title = `Shapes: ${shapes.length}`\n * },\n * [editor]\n * )\n *\n * return <div>...</div>\n * }\n * ```\n *\n * @example\n * ```tsx\n * import { useReactor, useEditor } from 'tldraw'\n *\n * function SelectionAnnouncer() {\n * const editor = useEditor()\n *\n * // Announce selection changes for accessibility\n * useReactor(\n * 'announce selection',\n * () => {\n * const selectedIds = editor.getSelectedShapeIds()\n * if (selectedIds.length > 0) {\n * console.log(`Selected ${selectedIds.length} shape(s)`)\n * }\n * },\n * [editor]\n * )\n *\n * return null\n * }\n * ```\n *\n * @remarks\n * The effect is throttled to run at most once per animation frame using `requestAnimationFrame`.\n * This makes it suitable for effects that need to respond to state changes but don't need to\n * run synchronously.\n *\n * If you need the effect to run immediately without throttling, use {@link useQuickReactor} instead.\n *\n * The effect function will be re-created when any of the `deps` change, similar to React's\n * `useEffect`. The effect automatically tracks which signals it accesses, so you don't need\n * to manually specify them as dependencies.\n *\n * @param name - A debug name for the effect, useful for debugging and performance profiling.\n * @param reactFn - The effect function to run. Any signals accessed in this function will be tracked.\n * @param deps - React dependencies array. The effect will be recreated when these change. Defaults to `[]`.\n *\n * @public\n */\nexport function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {\n\tuseEffect(() => {\n\t\tlet cancelFn: () => void | undefined\n\t\tconst scheduler = new EffectScheduler(name, reactFn, {\n\t\t\tscheduleEffect: (cb) => {\n\t\t\t\tcancelFn = throttleToNextFrame(cb)\n\t\t\t},\n\t\t})\n\t\tscheduler.attach()\n\t\tscheduler.execute()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t\tcancelFn?.()\n\t\t}\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n}\n"],
4
+ "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport { throttleToNextFrame } from '@tldraw/utils'\nimport { useEffect } from 'react'\n\n/**\n * A React hook that runs a side effect in response to changes in signals (reactive state).\n *\n * The effect function will automatically track any signals (atoms, computed values) that are\n * accessed during its execution. When any of those signals change, the effect will be\n * scheduled to run again on the next animation frame.\n *\n * This is useful for performing side effects (like updating the DOM, making API calls, or\n * updating external state) in response to changes in tldraw's reactive state system, while\n * keeping those effects efficiently batched and throttled.\n *\n * @example\n * ```tsx\n * import { useReactor, useEditor } from 'tldraw'\n *\n * function MyComponent() {\n * const editor = useEditor()\n *\n * // Update document title when shapes change\n * useReactor(\n * 'update title',\n * () => {\n * const shapes = editor.getCurrentPageShapes()\n * document.title = `Shapes: ${shapes.length}`\n * },\n * [editor]\n * )\n *\n * return <div>...</div>\n * }\n * ```\n *\n * @example\n * ```tsx\n * import { useReactor, useEditor } from 'tldraw'\n *\n * function SelectionAnnouncer() {\n * const editor = useEditor()\n *\n * // Announce selection changes for accessibility\n * useReactor(\n * 'announce selection',\n * () => {\n * const selectedIds = editor.getSelectedShapeIds()\n * if (selectedIds.length > 0) {\n * console.log(`Selected ${selectedIds.length} shape(s)`)\n * }\n * },\n * [editor]\n * )\n *\n * return null\n * }\n * ```\n *\n * @remarks\n * The effect is throttled to run at most once per animation frame using `requestAnimationFrame`.\n * This makes it suitable for effects that need to respond to state changes but don't need to\n * run synchronously.\n *\n * If you need the effect to run immediately without throttling, use {@link useQuickReactor} instead.\n *\n * The effect function will be re-created when any of the `deps` change, similar to React's\n * `useEffect`. The effect automatically tracks which signals it accesses, so you don't need\n * to manually specify them as dependencies.\n *\n * @param name - A debug name for the effect, useful for debugging and performance profiling.\n * @param reactFn - The effect function to run. Any signals accessed in this function will be tracked.\n * @param deps - React dependencies array. The effect will be recreated when these change. Defaults to `[]`.\n *\n * @public\n */\nexport function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {\n\tuseEffect(() => {\n\t\tlet cancelFn: (() => void) | undefined\n\t\tconst scheduler = new EffectScheduler(name, reactFn, {\n\t\t\tscheduleEffect: (cb) => {\n\t\t\t\tcancelFn = throttleToNextFrame(cb)\n\t\t\t},\n\t\t})\n\t\tscheduler.attach()\n\t\tscheduler.execute()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t\tcancelFn?.()\n\t\t}\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n}\n"],
5
5
  "mappings": "AAAA,SAAS,uBAAuB;AAChC,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AA0EnB,SAAS,WAAW,MAAc,SAAqB,OAA0B,CAAC,GAAG;AAC3F,YAAU,MAAM;AACf,QAAI;AACJ,UAAM,YAAY,IAAI,gBAAgB,MAAM,SAAS;AAAA,MACpD,gBAAgB,CAAC,OAAO;AACvB,mBAAW,oBAAoB,EAAE;AAAA,MAClC;AAAA,IACD,CAAC;AACD,cAAU,OAAO;AACjB,cAAU,QAAQ;AAClB,WAAO,MAAM;AACZ,gBAAU,OAAO;AACjB,iBAAW;AAAA,IACZ;AAAA,EAED,GAAG,IAAI;AACR;",
6
6
  "names": []
7
7
  }
@@ -14,7 +14,7 @@ function useStateTracking(name, render, deps = []) {
14
14
  const scheduler2 = new EffectScheduler(
15
15
  `useStateTracking(${name})`,
16
16
  // this is what `scheduler.execute()` will call
17
- () => renderRef.current?.(),
17
+ () => renderRef.current(),
18
18
  // this is what will be invoked when @tldraw/state detects a change in an upstream reactive value
19
19
  {
20
20
  scheduleEffect() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/useStateTracking.ts"],
4
- "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport React from 'react'\n\n/**\n * Wraps some synchronous react render logic in a reactive tracking context.\n *\n * This allows you to use reactive values transparently.\n *\n * See the `track` component wrapper, which uses this under the hood.\n *\n * @param name - A debug name for the reactive tracking context\n * @param render - The render function that accesses reactive values\n * @param deps - Optional dependency array to control when the tracking context is recreated\n * @returns The result of calling the render function\n *\n * @example\n * ```ts\n * function MyComponent() {\n * return useStateTracking('MyComponent', () => {\n * const editor = useEditor()\n * return <div>Num shapes: {editor.getCurrentPageShapes().length}</div>\n * })\n * }\n * ```\n *\n *\n * @public\n */\nexport function useStateTracking<T>(name: string, render: () => T, deps: unknown[] = []): T {\n\t// This hook creates an effect scheduler that will trigger re-renders when its reactive dependencies change, but it\n\t// defers the actual execution of the effect to the consumer of this hook.\n\n\t// We need the exec fn to always be up-to-date when calling scheduler.execute() but it'd be wasteful to\n\t// instantiate a new EffectScheduler on every render, so we use an immediately-updated ref\n\t// to wrap it\n\tconst renderRef = React.useRef(render)\n\trenderRef.current = render\n\n\tconst [scheduler, subscribe, getSnapshot] = React.useMemo(() => {\n\t\tlet scheduleUpdate = null as null | (() => void)\n\t\t// useSyncExternalStore requires a subscribe function that returns an unsubscribe function\n\t\tconst subscribe = (cb: () => void) => {\n\t\t\tscheduleUpdate = cb\n\t\t\treturn () => {\n\t\t\t\tscheduleUpdate = null\n\t\t\t}\n\t\t}\n\n\t\tconst scheduler = new EffectScheduler(\n\t\t\t`useStateTracking(${name})`,\n\t\t\t// this is what `scheduler.execute()` will call\n\t\t\t() => renderRef.current?.(),\n\t\t\t// this is what will be invoked when @tldraw/state detects a change in an upstream reactive value\n\t\t\t{\n\t\t\t\tscheduleEffect() {\n\t\t\t\t\tscheduleUpdate?.()\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\n\t\t// we use an incrementing number based on when this\n\t\tconst getSnapshot = () => scheduler.scheduleCount\n\n\t\treturn [scheduler, subscribe, getSnapshot]\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [name, ...deps])\n\n\tReact.useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n\t// reactive dependencies are captured when `scheduler.execute()` is called\n\t// and then to make it reactive we wait for a `useEffect` to 'attach'\n\t// this allows us to avoid rendering outside of React's render phase\n\t// and avoid 'zombie' components that try to render with bad/deleted data before\n\t// react has a chance to umount them.\n\tReact.useEffect(() => {\n\t\tscheduler.attach()\n\t\t// do not execute, we only do that in render\n\t\tscheduler.maybeScheduleEffect()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t}\n\t}, [scheduler])\n\n\treturn scheduler.execute()\n}\n"],
5
- "mappings": "AAAA,SAAS,uBAAuB;AAChC,OAAO,WAAW;AA2BX,SAAS,iBAAoB,MAAc,QAAiB,OAAkB,CAAC,GAAM;AAO3F,QAAM,YAAY,MAAM,OAAO,MAAM;AACrC,YAAU,UAAU;AAEpB,QAAM,CAAC,WAAW,WAAW,WAAW,IAAI,MAAM,QAAQ,MAAM;AAC/D,QAAI,iBAAiB;AAErB,UAAMA,aAAY,CAAC,OAAmB;AACrC,uBAAiB;AACjB,aAAO,MAAM;AACZ,yBAAiB;AAAA,MAClB;AAAA,IACD;AAEA,UAAMC,aAAY,IAAI;AAAA,MACrB,oBAAoB,IAAI;AAAA;AAAA,MAExB,MAAM,UAAU,UAAU;AAAA;AAAA,MAE1B;AAAA,QACC,iBAAiB;AAChB,2BAAiB;AAAA,QAClB;AAAA,MACD;AAAA,IACD;AAGA,UAAMC,eAAc,MAAMD,WAAU;AAEpC,WAAO,CAACA,YAAWD,YAAWE,YAAW;AAAA,EAE1C,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC;AAElB,QAAM,qBAAqB,WAAW,aAAa,WAAW;AAO9D,QAAM,UAAU,MAAM;AACrB,cAAU,OAAO;AAEjB,cAAU,oBAAoB;AAC9B,WAAO,MAAM;AACZ,gBAAU,OAAO;AAAA,IAClB;AAAA,EACD,GAAG,CAAC,SAAS,CAAC;AAEd,SAAO,UAAU,QAAQ;AAC1B;",
4
+ "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport React from 'react'\n\n/**\n * Wraps some synchronous react render logic in a reactive tracking context.\n *\n * This allows you to use reactive values transparently.\n *\n * See the `track` component wrapper, which uses this under the hood.\n *\n * @param name - A debug name for the reactive tracking context\n * @param render - The render function that accesses reactive values\n * @param deps - Optional dependency array to control when the tracking context is recreated\n * @returns The result of calling the render function\n *\n * @example\n * ```ts\n * function MyComponent() {\n * return useStateTracking('MyComponent', () => {\n * const editor = useEditor()\n * return <div>Num shapes: {editor.getCurrentPageShapes().length}</div>\n * })\n * }\n * ```\n *\n *\n * @public\n */\nexport function useStateTracking<T>(name: string, render: () => T, deps: unknown[] = []): T {\n\t// This hook creates an effect scheduler that will trigger re-renders when its reactive dependencies change, but it\n\t// defers the actual execution of the effect to the consumer of this hook.\n\n\t// We need the exec fn to always be up-to-date when calling scheduler.execute() but it'd be wasteful to\n\t// instantiate a new EffectScheduler on every render, so we use an immediately-updated ref\n\t// to wrap it\n\tconst renderRef = React.useRef(render)\n\trenderRef.current = render\n\n\tconst [scheduler, subscribe, getSnapshot] = React.useMemo(() => {\n\t\tlet scheduleUpdate = null as null | (() => void)\n\t\t// useSyncExternalStore requires a subscribe function that returns an unsubscribe function\n\t\tconst subscribe = (cb: () => void) => {\n\t\t\tscheduleUpdate = cb\n\t\t\treturn () => {\n\t\t\t\tscheduleUpdate = null\n\t\t\t}\n\t\t}\n\n\t\tconst scheduler = new EffectScheduler(\n\t\t\t`useStateTracking(${name})`,\n\t\t\t// this is what `scheduler.execute()` will call\n\t\t\t() => renderRef.current(),\n\t\t\t// this is what will be invoked when @tldraw/state detects a change in an upstream reactive value\n\t\t\t{\n\t\t\t\tscheduleEffect() {\n\t\t\t\t\tscheduleUpdate?.()\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\n\t\t// we use an incrementing number based on when this\n\t\tconst getSnapshot = () => scheduler.scheduleCount\n\n\t\treturn [scheduler, subscribe, getSnapshot]\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [name, ...deps])\n\n\tReact.useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n\t// reactive dependencies are captured when `scheduler.execute()` is called\n\t// and then to make it reactive we wait for a `useEffect` to 'attach'\n\t// this allows us to avoid rendering outside of React's render phase\n\t// and avoid 'zombie' components that try to render with bad/deleted data before\n\t// react has a chance to umount them.\n\tReact.useEffect(() => {\n\t\tscheduler.attach()\n\t\t// do not execute, we only do that in render\n\t\tscheduler.maybeScheduleEffect()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t}\n\t}, [scheduler])\n\n\treturn scheduler.execute()\n}\n"],
5
+ "mappings": "AAAA,SAAS,uBAAuB;AAChC,OAAO,WAAW;AA2BX,SAAS,iBAAoB,MAAc,QAAiB,OAAkB,CAAC,GAAM;AAO3F,QAAM,YAAY,MAAM,OAAO,MAAM;AACrC,YAAU,UAAU;AAEpB,QAAM,CAAC,WAAW,WAAW,WAAW,IAAI,MAAM,QAAQ,MAAM;AAC/D,QAAI,iBAAiB;AAErB,UAAMA,aAAY,CAAC,OAAmB;AACrC,uBAAiB;AACjB,aAAO,MAAM;AACZ,yBAAiB;AAAA,MAClB;AAAA,IACD;AAEA,UAAMC,aAAY,IAAI;AAAA,MACrB,oBAAoB,IAAI;AAAA;AAAA,MAExB,MAAM,UAAU,QAAQ;AAAA;AAAA,MAExB;AAAA,QACC,iBAAiB;AAChB,2BAAiB;AAAA,QAClB;AAAA,MACD;AAAA,IACD;AAGA,UAAMC,eAAc,MAAMD,WAAU;AAEpC,WAAO,CAACA,YAAWD,YAAWE,YAAW;AAAA,EAE1C,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC;AAElB,QAAM,qBAAqB,WAAW,aAAa,WAAW;AAO9D,QAAM,UAAU,MAAM;AACrB,cAAU,OAAO;AAEjB,cAAU,oBAAoB;AAC9B,WAAO,MAAM;AACZ,gBAAU,OAAO;AAAA,IAClB;AAAA,EACD,GAAG,CAAC,SAAS,CAAC;AAEd,SAAO,UAAU,QAAQ;AAC1B;",
6
6
  "names": ["subscribe", "scheduler", "getSnapshot"]
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/state-react",
3
3
  "description": "tldraw infinite canvas SDK (react bindings for state).",
4
- "version": "5.5.0-canary.fe69c9620403",
4
+ "version": "5.5.0-canary.ffd1e744a5b4",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -50,8 +50,8 @@
50
50
  "lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
51
51
  },
52
52
  "dependencies": {
53
- "@tldraw/state": "5.5.0-canary.fe69c9620403",
54
- "@tldraw/utils": "5.5.0-canary.fe69c9620403"
53
+ "@tldraw/state": "5.5.0-canary.ffd1e744a5b4",
54
+ "@tldraw/utils": "5.5.0-canary.ffd1e744a5b4"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@testing-library/dom": "^10.0.0",
@@ -76,7 +76,7 @@ import { useEffect } from 'react'
76
76
  */
77
77
  export function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {
78
78
  useEffect(() => {
79
- let cancelFn: () => void | undefined
79
+ let cancelFn: (() => void) | undefined
80
80
  const scheduler = new EffectScheduler(name, reactFn, {
81
81
  scheduleEffect: (cb) => {
82
82
  cancelFn = throttleToNextFrame(cb)
@@ -49,7 +49,7 @@ export function useStateTracking<T>(name: string, render: () => T, deps: unknown
49
49
  const scheduler = new EffectScheduler(
50
50
  `useStateTracking(${name})`,
51
51
  // this is what `scheduler.execute()` will call
52
- () => renderRef.current?.(),
52
+ () => renderRef.current(),
53
53
  // this is what will be invoked when @tldraw/state detects a change in an upstream reactive value
54
54
  {
55
55
  scheduleEffect() {