@tldraw/state-react 4.0.2 → 4.1.0-canary.0259516ffb8c
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.d.ts +241 -35
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/track.js.map +2 -2
- package/dist-cjs/lib/useComputed.js.map +2 -2
- package/dist-cjs/lib/useQuickReactor.js.map +2 -2
- package/dist-cjs/lib/useReactor.js.map +2 -2
- package/dist-cjs/lib/useStateTracking.js +1 -1
- package/dist-cjs/lib/useStateTracking.js.map +2 -2
- package/dist-cjs/lib/useValue.js.map +2 -2
- package/dist-esm/index.d.mts +241 -35
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/track.mjs.map +2 -2
- package/dist-esm/lib/useComputed.mjs.map +2 -2
- package/dist-esm/lib/useQuickReactor.mjs.map +2 -2
- package/dist-esm/lib/useReactor.mjs.map +2 -2
- package/dist-esm/lib/useStateTracking.mjs.map +2 -2
- package/dist-esm/lib/useValue.mjs.map +2 -2
- package/package.json +3 -3
- package/src/lib/track.test.tsx +1 -1
- package/src/lib/track.ts +65 -2
- package/src/lib/useComputed.ts +62 -11
- package/src/lib/useQuickReactor.test.tsx +405 -0
- package/src/lib/useQuickReactor.ts +40 -1
- package/src/lib/useReactor.test.tsx +128 -0
- package/src/lib/useReactor.ts +72 -1
- package/src/lib/useStateTracking.ts +5 -0
- package/src/lib/useValue.ts +55 -22
package/dist-cjs/index.d.ts
CHANGED
|
@@ -11,18 +11,41 @@ import { Signal } from '@tldraw/state';
|
|
|
11
11
|
* Any signals whose values are read while the component renders will be tracked.
|
|
12
12
|
* If any of the tracked signals change later it will cause the component to re-render.
|
|
13
13
|
*
|
|
14
|
-
* This also wraps the component in a React.memo() call, so it will only re-render
|
|
14
|
+
* This also wraps the component in a React.memo() call, so it will only re-render
|
|
15
|
+
* when props change OR when any tracked signals change. This provides optimal
|
|
16
|
+
* performance by preventing unnecessary re-renders while maintaining reactivity.
|
|
17
|
+
*
|
|
18
|
+
* The function handles special React component types like forwardRef and memo
|
|
19
|
+
* components automatically, preserving their behavior while adding reactivity.
|
|
20
|
+
*
|
|
21
|
+
* @param baseComponent - The React functional component to make reactive to signal changes
|
|
22
|
+
* @returns A memoized component that re-renders when props or tracked signals change
|
|
15
23
|
*
|
|
16
24
|
* @example
|
|
17
25
|
* ```ts
|
|
26
|
+
* import { atom } from '@tldraw/state'
|
|
27
|
+
* import { track, useAtom } from '@tldraw/state-react'
|
|
28
|
+
*
|
|
18
29
|
* const Counter = track(function Counter(props: CounterProps) {
|
|
19
30
|
* const count = useAtom('count', 0)
|
|
20
31
|
* const increment = useCallback(() => count.set(count.get() + 1), [count])
|
|
21
32
|
* return <button onClick={increment}>{count.get()}</button>
|
|
22
33
|
* })
|
|
34
|
+
*
|
|
35
|
+
* // Component automatically re-renders when count signal changes
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* // Works with forwardRef components
|
|
41
|
+
* const TrackedInput = track(React.forwardRef<HTMLInputElement, InputProps>(
|
|
42
|
+
* function TrackedInput(props, ref) {
|
|
43
|
+
* const theme = useValue(themeSignal)
|
|
44
|
+
* return <input ref={ref} style={{ color: theme.textColor }} {...props} />
|
|
45
|
+
* }
|
|
46
|
+
* ))
|
|
23
47
|
* ```
|
|
24
48
|
*
|
|
25
|
-
* @param baseComponent - The base component to track.
|
|
26
49
|
* @public
|
|
27
50
|
*/
|
|
28
51
|
export declare function track<T extends FunctionComponent<any>>(baseComponent: T): React_2.NamedExoticComponent<React_2.ComponentProps<T>>;
|
|
@@ -49,33 +72,185 @@ export declare function track<T extends FunctionComponent<any>>(baseComponent: T
|
|
|
49
72
|
*/
|
|
50
73
|
export declare function useAtom<Value, Diff = unknown>(name: string, valueOrInitialiser: (() => Value) | Value, options?: AtomOptions<Value, Diff>): Atom<Value, Diff>;
|
|
51
74
|
|
|
52
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Creates a new computed signal that automatically tracks its dependencies and recalculates when they change.
|
|
77
|
+
* This overload is for basic computed values without custom options.
|
|
78
|
+
*
|
|
79
|
+
* @param name - A descriptive name for the computed signal, used for debugging and identification
|
|
80
|
+
* @param compute - A function that computes the value, automatically tracking any signal dependencies
|
|
81
|
+
* @param deps - React dependency array that controls when the computed signal is recreated
|
|
82
|
+
* @returns A computed signal containing the calculated value
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const firstName = atom('firstName', 'John')
|
|
87
|
+
* const lastName = atom('lastName', 'Doe')
|
|
88
|
+
*
|
|
89
|
+
* function UserProfile() {
|
|
90
|
+
* const fullName = useComputed(
|
|
91
|
+
* 'fullName',
|
|
92
|
+
* () => `${firstName.get()} ${lastName.get()}`,
|
|
93
|
+
* [firstName, lastName]
|
|
94
|
+
* )
|
|
95
|
+
*
|
|
96
|
+
* return <div>Welcome, {fullName.get()}!</div>
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* @public
|
|
101
|
+
*/
|
|
53
102
|
export declare function useComputed<Value>(name: string, compute: () => Value, deps: any[]): Computed<Value>;
|
|
54
103
|
|
|
55
104
|
/**
|
|
56
|
-
* Creates a new computed signal
|
|
105
|
+
* Creates a new computed signal with custom options for advanced behavior like custom equality checking,
|
|
106
|
+
* diff computation, and history tracking. The computed signal will be created only once.
|
|
107
|
+
*
|
|
108
|
+
* @param name - A descriptive name for the computed signal, used for debugging and identification
|
|
109
|
+
* @param compute - A function that computes the value, automatically tracking any signal dependencies
|
|
110
|
+
* @param opts - Configuration options for the computed signal
|
|
111
|
+
* - isEqual - Custom equality function to determine if the computed value has changed
|
|
112
|
+
* - computeDiff - Function to compute diffs between old and new values for history tracking
|
|
113
|
+
* - historyLength - Maximum number of diffs to keep in history buffer for time-travel functionality
|
|
114
|
+
* @param deps - React dependency array that controls when the computed signal is recreated
|
|
115
|
+
* @returns A computed signal containing the calculated value with the specified options
|
|
57
116
|
*
|
|
58
117
|
* @example
|
|
59
118
|
* ```ts
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* lastName: Signal<string>
|
|
63
|
-
* }
|
|
119
|
+
* function ShoppingCart() {
|
|
120
|
+
* const items = useAtom('items', [])
|
|
64
121
|
*
|
|
65
|
-
*
|
|
66
|
-
* const
|
|
67
|
-
*
|
|
68
|
-
*
|
|
122
|
+
* // Computed with custom equality to avoid recalculation for equivalent arrays
|
|
123
|
+
* const sortedItems = useComputed(
|
|
124
|
+
* 'sortedItems',
|
|
125
|
+
* () => items.get().sort((a, b) => a.name.localeCompare(b.name)),
|
|
126
|
+
* {
|
|
127
|
+
* isEqual: (a, b) => a.length === b.length && a.every((item, i) => item.id === b[i].id)
|
|
128
|
+
* },
|
|
129
|
+
* [items]
|
|
130
|
+
* )
|
|
131
|
+
*
|
|
132
|
+
* return <ItemList items={sortedItems.get()} />
|
|
133
|
+
* }
|
|
69
134
|
* ```
|
|
70
135
|
*
|
|
71
136
|
* @public
|
|
72
137
|
*/
|
|
73
138
|
export declare function useComputed<Value, Diff = unknown>(name: string, compute: () => Value, opts: ComputedOptions<Value, Diff>, deps: any[]): Computed<Value>;
|
|
74
139
|
|
|
75
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* A React hook that runs side effects immediately in response to signal changes, without throttling.
|
|
142
|
+
* Unlike useReactor which batches updates to animation frames, useQuickReactor executes the effect
|
|
143
|
+
* function immediately when dependencies change, making it ideal for critical updates that cannot wait.
|
|
144
|
+
*
|
|
145
|
+
* The effect runs immediately when the component mounts and whenever tracked signals change.
|
|
146
|
+
* Updates are not throttled, so the effect executes synchronously on every change.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* function DataSynchronizer() {
|
|
151
|
+
* const criticalData = useAtom('criticalData', null)
|
|
152
|
+
*
|
|
153
|
+
* useQuickReactor('sync-data', () => {
|
|
154
|
+
* const data = criticalData.get()
|
|
155
|
+
* if (data) {
|
|
156
|
+
* // Send immediately - don't wait for next frame
|
|
157
|
+
* sendToServer(data)
|
|
158
|
+
* }
|
|
159
|
+
* }, [criticalData])
|
|
160
|
+
*
|
|
161
|
+
* return <div>Sync status updated</div>
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* function CursorUpdater({ editor }) {
|
|
168
|
+
* useQuickReactor('update-cursor', () => {
|
|
169
|
+
* const cursor = editor.getInstanceState().cursor
|
|
170
|
+
* document.body.style.cursor = cursor.type
|
|
171
|
+
* }, [])
|
|
172
|
+
* }
|
|
173
|
+
* ```
|
|
174
|
+
*
|
|
175
|
+
* @param name - A descriptive name for the reactor, used for debugging and performance profiling
|
|
176
|
+
* @param reactFn - The effect function to execute when signals change. Should not return a value.
|
|
177
|
+
* @param deps - Optional dependency array that controls when the reactor is recreated. Works like useEffect deps.
|
|
178
|
+
* @public
|
|
179
|
+
*/
|
|
76
180
|
export declare function useQuickReactor(name: string, reactFn: () => void, deps?: any[]): void;
|
|
77
181
|
|
|
78
|
-
/**
|
|
182
|
+
/**
|
|
183
|
+
* A React hook that runs a side effect in response to changes in signals (reactive state).
|
|
184
|
+
*
|
|
185
|
+
* The effect function will automatically track any signals (atoms, computed values) that are
|
|
186
|
+
* accessed during its execution. When any of those signals change, the effect will be
|
|
187
|
+
* scheduled to run again on the next animation frame.
|
|
188
|
+
*
|
|
189
|
+
* This is useful for performing side effects (like updating the DOM, making API calls, or
|
|
190
|
+
* updating external state) in response to changes in tldraw's reactive state system, while
|
|
191
|
+
* keeping those effects efficiently batched and throttled.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```tsx
|
|
195
|
+
* import { useReactor, useEditor } from 'tldraw'
|
|
196
|
+
*
|
|
197
|
+
* function MyComponent() {
|
|
198
|
+
* const editor = useEditor()
|
|
199
|
+
*
|
|
200
|
+
* // Update document title when shapes change
|
|
201
|
+
* useReactor(
|
|
202
|
+
* 'update title',
|
|
203
|
+
* () => {
|
|
204
|
+
* const shapes = editor.getCurrentPageShapes()
|
|
205
|
+
* document.title = `Shapes: ${shapes.length}`
|
|
206
|
+
* },
|
|
207
|
+
* [editor]
|
|
208
|
+
* )
|
|
209
|
+
*
|
|
210
|
+
* return <div>...</div>
|
|
211
|
+
* }
|
|
212
|
+
* ```
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```tsx
|
|
216
|
+
* import { useReactor, useEditor } from 'tldraw'
|
|
217
|
+
*
|
|
218
|
+
* function SelectionAnnouncer() {
|
|
219
|
+
* const editor = useEditor()
|
|
220
|
+
*
|
|
221
|
+
* // Announce selection changes for accessibility
|
|
222
|
+
* useReactor(
|
|
223
|
+
* 'announce selection',
|
|
224
|
+
* () => {
|
|
225
|
+
* const selectedIds = editor.getSelectedShapeIds()
|
|
226
|
+
* if (selectedIds.length > 0) {
|
|
227
|
+
* console.log(`Selected ${selectedIds.length} shape(s)`)
|
|
228
|
+
* }
|
|
229
|
+
* },
|
|
230
|
+
* [editor]
|
|
231
|
+
* )
|
|
232
|
+
*
|
|
233
|
+
* return null
|
|
234
|
+
* }
|
|
235
|
+
* ```
|
|
236
|
+
*
|
|
237
|
+
* @remarks
|
|
238
|
+
* The effect is throttled to run at most once per animation frame using `requestAnimationFrame`.
|
|
239
|
+
* This makes it suitable for effects that need to respond to state changes but don't need to
|
|
240
|
+
* run synchronously.
|
|
241
|
+
*
|
|
242
|
+
* If you need the effect to run immediately without throttling, use {@link useQuickReactor} instead.
|
|
243
|
+
*
|
|
244
|
+
* The effect function will be re-created when any of the `deps` change, similar to React's
|
|
245
|
+
* `useEffect`. The effect automatically tracks which signals it accesses, so you don't need
|
|
246
|
+
* to manually specify them as dependencies.
|
|
247
|
+
*
|
|
248
|
+
* @param name - A debug name for the effect, useful for debugging and performance profiling.
|
|
249
|
+
* @param reactFn - The effect function to run. Any signals accessed in this function will be tracked.
|
|
250
|
+
* @param deps - React dependencies array. The effect will be recreated when these change. Defaults to `[]`.
|
|
251
|
+
*
|
|
252
|
+
* @public
|
|
253
|
+
*/
|
|
79
254
|
export declare function useReactor(name: string, reactFn: () => void, deps?: any[] | undefined): void;
|
|
80
255
|
|
|
81
256
|
/**
|
|
@@ -85,6 +260,11 @@ export declare function useReactor(name: string, reactFn: () => void, deps?: any
|
|
|
85
260
|
*
|
|
86
261
|
* See the `track` component wrapper, which uses this under the hood.
|
|
87
262
|
*
|
|
263
|
+
* @param name - A debug name for the reactive tracking context
|
|
264
|
+
* @param render - The render function that accesses reactive values
|
|
265
|
+
* @param deps - Optional dependency array to control when the tracking context is recreated
|
|
266
|
+
* @returns The result of calling the render function
|
|
267
|
+
*
|
|
88
268
|
* @example
|
|
89
269
|
* ```ts
|
|
90
270
|
* function MyComponent() {
|
|
@@ -100,40 +280,66 @@ export declare function useReactor(name: string, reactFn: () => void, deps?: any
|
|
|
100
280
|
*/
|
|
101
281
|
export declare function useStateTracking<T>(name: string, render: () => T, deps?: unknown[]): T;
|
|
102
282
|
|
|
103
|
-
/** @public */
|
|
104
|
-
export declare function useValue<Value>(value: Signal<Value>): Value;
|
|
105
|
-
|
|
106
283
|
/**
|
|
107
|
-
* Extracts the value from a signal and subscribes to
|
|
284
|
+
* Extracts the current value from a signal and subscribes the component to changes.
|
|
285
|
+
*
|
|
286
|
+
* This is the most straightforward way to read signal values in React components.
|
|
287
|
+
* When the signal changes, the component will automatically re-render with the new value.
|
|
108
288
|
*
|
|
109
|
-
* Note
|
|
289
|
+
* Note: You do not need to use this hook if you are wrapping the component with {@link track},
|
|
290
|
+
* as tracked components automatically subscribe to any signals accessed with `.get()`.
|
|
291
|
+
*
|
|
292
|
+
* @param value - The signal to read the value from
|
|
293
|
+
* @returns The current value of the signal
|
|
110
294
|
*
|
|
111
295
|
* @example
|
|
112
296
|
* ```ts
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
297
|
+
* import { atom } from '@tldraw/state'
|
|
298
|
+
* import { useValue } from '@tldraw/state-react'
|
|
299
|
+
*
|
|
300
|
+
* const count = atom('count', 0)
|
|
301
|
+
*
|
|
302
|
+
* function Counter() {
|
|
303
|
+
* const currentCount = useValue(count)
|
|
304
|
+
* return (
|
|
305
|
+
* <button onClick={() => count.set(currentCount + 1)}>
|
|
306
|
+
* Count: {currentCount}
|
|
307
|
+
* </button>
|
|
308
|
+
* )
|
|
118
309
|
* }
|
|
119
310
|
* ```
|
|
120
311
|
*
|
|
121
|
-
*
|
|
312
|
+
* @public
|
|
313
|
+
*/
|
|
314
|
+
export declare function useValue<Value>(value: Signal<Value>): Value;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Creates a computed value with automatic dependency tracking and subscribes to changes.
|
|
318
|
+
*
|
|
319
|
+
* This overload allows you to compute a value from one or more signals with automatic
|
|
320
|
+
* memoization. The computed function will only re-execute when its dependencies change,
|
|
321
|
+
* and the component will only re-render when the computed result changes.
|
|
322
|
+
*
|
|
323
|
+
* @param name - A descriptive name for debugging purposes
|
|
324
|
+
* @param fn - Function that computes the value, should call `.get()` on any signals it depends on
|
|
325
|
+
* @param deps - Array of signals that the computed function depends on
|
|
326
|
+
* @returns The computed value
|
|
122
327
|
*
|
|
123
328
|
* @example
|
|
124
329
|
* ```ts
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
330
|
+
* import { atom } from '@tldraw/state'
|
|
331
|
+
* import { useValue } from '@tldraw/state-react'
|
|
332
|
+
*
|
|
333
|
+
* const firstName = atom('firstName', 'John')
|
|
334
|
+
* const lastName = atom('lastName', 'Doe')
|
|
335
|
+
*
|
|
336
|
+
* function UserGreeting() {
|
|
337
|
+
* const fullName = useValue('fullName', () => {
|
|
338
|
+
* return `${firstName.get()} ${lastName.get()}`
|
|
339
|
+
* }, [firstName, lastName])
|
|
129
340
|
*
|
|
130
|
-
* const Greeter = track(function Greeter({ firstName, lastName }: GreeterProps) {
|
|
131
|
-
* const fullName = useValue('fullName', () => `${firstName.get()} ${lastName.get()}`, [
|
|
132
|
-
* firstName,
|
|
133
|
-
* lastName,
|
|
134
|
-
* ])
|
|
135
341
|
* return <div>Hello {fullName}!</div>
|
|
136
|
-
* }
|
|
342
|
+
* }
|
|
137
343
|
* ```
|
|
138
344
|
*
|
|
139
345
|
* @public
|
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
|
-
"4.0.
|
|
40
|
+
"4.1.0-canary.0259516ffb8c",
|
|
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/track.ts"],
|
|
4
|
-
"sourcesContent": ["import React, { forwardRef, FunctionComponent, memo } from 'react'\nimport { useStateTracking } from './useStateTracking'\n\nexport const ProxyHandlers = {\n\t/**\n\t * This is a function call trap for functional components. When this is called, we know it means\n\t * React did run 'Component()', that means we can use any hooks here to setup our effect and\n\t * store.\n\t *\n\t * With the native Proxy, all other calls such as access/setting to/of properties will be\n\t * forwarded to the target Component, so we don't need to copy the Component's own or inherited\n\t * properties.\n\t *\n\t * @see https://github.com/facebook/react/blob/2d80a0cd690bb5650b6c8a6c079a87b5dc42bd15/packages/react-reconciler/src/ReactFiberHooks.old.js#L460\n\t */\n\tapply(Component: FunctionComponent, thisArg: any, argumentsList: any) {\n\t\t// eslint-disable-next-line react-hooks/rules-of-hooks\n\t\treturn useStateTracking(Component.displayName ?? Component.name ?? 'tracked(???)', () =>\n\t\t\tComponent.apply(thisArg, argumentsList)\n\t\t)\n\t},\n}\n\nexport const ReactMemoSymbol = Symbol.for('react.memo')\nexport const ReactForwardRefSymbol = Symbol.for('react.forward_ref')\n\n/**\n * Returns a tracked version of the given component.\n * Any signals whose values are read while the component renders will be tracked.\n * If any of the tracked signals change later it will cause the component to re-render.\n *\n * This also wraps the component in a React.memo() call, so it will only re-render
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA2D;AAC3D,8BAAiC;
|
|
4
|
+
"sourcesContent": ["import React, { forwardRef, FunctionComponent, memo } from 'react'\nimport { useStateTracking } from './useStateTracking'\n\n/**\n * Proxy handlers object used to intercept function calls to React components.\n * This enables automatic signal tracking by wrapping component execution\n * in reactive tracking context.\n *\n * The proxy intercepts the function call (apply trap) and wraps it with\n * useStateTracking to enable automatic dependency tracking for signals\n * accessed during render.\n *\n * @example\n * ```ts\n * // Used internally by track() function\n * const ProxiedComponent = new Proxy(MyComponent, ProxyHandlers)\n * ```\n *\n * @internal\n */\nexport const ProxyHandlers = {\n\t/**\n\t * This is a function call trap for functional components. When this is called, we know it means\n\t * React did run 'Component()', that means we can use any hooks here to setup our effect and\n\t * store.\n\t *\n\t * With the native Proxy, all other calls such as access/setting to/of properties will be\n\t * forwarded to the target Component, so we don't need to copy the Component's own or inherited\n\t * properties.\n\t *\n\t * @see https://github.com/facebook/react/blob/2d80a0cd690bb5650b6c8a6c079a87b5dc42bd15/packages/react-reconciler/src/ReactFiberHooks.old.js#L460\n\t */\n\tapply(Component: FunctionComponent, thisArg: any, argumentsList: any) {\n\t\t// eslint-disable-next-line react-hooks/rules-of-hooks\n\t\treturn useStateTracking(Component.displayName ?? Component.name ?? 'tracked(???)', () =>\n\t\t\tComponent.apply(thisArg, argumentsList)\n\t\t)\n\t},\n}\n\n/**\n * React internal symbol for identifying memoized components.\n * Used to detect if a component is already wrapped with React.memo().\n *\n * @example\n * ```ts\n * const isMemoComponent = component['$$typeof'] === ReactMemoSymbol\n * ```\n *\n * @internal\n */\nexport const ReactMemoSymbol = Symbol.for('react.memo')\n\n/**\n * React internal symbol for identifying forward ref components.\n * Used to detect if a component is wrapped with React.forwardRef().\n *\n * @example\n * ```ts\n * const isForwardRefComponent = component['$$typeof'] === ReactForwardRefSymbol\n * ```\n *\n * @internal\n */\nexport const ReactForwardRefSymbol = Symbol.for('react.forward_ref')\n\n/**\n * Returns a tracked version of the given component.\n * Any signals whose values are read while the component renders will be tracked.\n * If any of the tracked signals change later it will cause the component to re-render.\n *\n * This also wraps the component in a React.memo() call, so it will only re-render\n * when props change OR when any tracked signals change. This provides optimal\n * performance by preventing unnecessary re-renders while maintaining reactivity.\n *\n * The function handles special React component types like forwardRef and memo\n * components automatically, preserving their behavior while adding reactivity.\n *\n * @param baseComponent - The React functional component to make reactive to signal changes\n * @returns A memoized component that re-renders when props or tracked signals change\n *\n * @example\n * ```ts\n * import { atom } from '@tldraw/state'\n * import { track, useAtom } from '@tldraw/state-react'\n *\n * const Counter = track(function Counter(props: CounterProps) {\n * const count = useAtom('count', 0)\n * const increment = useCallback(() => count.set(count.get() + 1), [count])\n * return <button onClick={increment}>{count.get()}</button>\n * })\n *\n * // Component automatically re-renders when count signal changes\n * ```\n *\n * @example\n * ```ts\n * // Works with forwardRef components\n * const TrackedInput = track(React.forwardRef<HTMLInputElement, InputProps>(\n * function TrackedInput(props, ref) {\n * const theme = useValue(themeSignal)\n * return <input ref={ref} style={{ color: theme.textColor }} {...props} />\n * }\n * ))\n * ```\n *\n * @public\n */\nexport function track<T extends FunctionComponent<any>>(\n\tbaseComponent: T\n): React.NamedExoticComponent<React.ComponentProps<T>> {\n\tlet compare = null\n\tconst $$typeof = baseComponent['$$typeof' as keyof typeof baseComponent]\n\tif ($$typeof === ReactMemoSymbol) {\n\t\tbaseComponent = (baseComponent as any).type\n\t\tcompare = (baseComponent as any).compare\n\t}\n\tif ($$typeof === ReactForwardRefSymbol) {\n\t\treturn memo(forwardRef(new Proxy((baseComponent as any).render, ProxyHandlers) as any)) as any\n\t}\n\n\treturn memo(new Proxy(baseComponent, ProxyHandlers) as any, compare) as any\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA2D;AAC3D,8BAAiC;AAmB1B,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY5B,MAAM,WAA8B,SAAc,eAAoB;AAErE,eAAO;AAAA,MAAiB,UAAU,eAAe,UAAU,QAAQ;AAAA,MAAgB,MAClF,UAAU,MAAM,SAAS,aAAa;AAAA,IACvC;AAAA,EACD;AACD;AAaO,MAAM,kBAAkB,OAAO,IAAI,YAAY;AAa/C,MAAM,wBAAwB,OAAO,IAAI,mBAAmB;AA4C5D,SAAS,MACf,eACsD;AACtD,MAAI,UAAU;AACd,QAAM,WAAW,cAAc,UAAwC;AACvE,MAAI,aAAa,iBAAiB;AACjC,oBAAiB,cAAsB;AACvC,cAAW,cAAsB;AAAA,EAClC;AACA,MAAI,aAAa,uBAAuB;AACvC,eAAO,uBAAK,yBAAW,IAAI,MAAO,cAAsB,QAAQ,aAAa,CAAQ,CAAC;AAAA,EACvF;AAEA,aAAO,mBAAK,IAAI,MAAM,eAAe,aAAa,GAAU,OAAO;AACpE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/useComputed.ts"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { Computed, ComputedOptions, computed } from '@tldraw/state'\nimport { useMemo } from 'react'\n\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mBAAoD;AACpD,mBAAwB;
|
|
4
|
+
"sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { Computed, ComputedOptions, computed } from '@tldraw/state'\nimport { useMemo } from 'react'\n\n/**\n * Creates a new computed signal that automatically tracks its dependencies and recalculates when they change.\n * This overload is for basic computed values without custom options.\n *\n * @param name - A descriptive name for the computed signal, used for debugging and identification\n * @param compute - A function that computes the value, automatically tracking any signal dependencies\n * @param deps - React dependency array that controls when the computed signal is recreated\n * @returns A computed signal containing the calculated value\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * function UserProfile() {\n * const fullName = useComputed(\n * 'fullName',\n * () => `${firstName.get()} ${lastName.get()}`,\n * [firstName, lastName]\n * )\n *\n * return <div>Welcome, {fullName.get()}!</div>\n * }\n * ```\n *\n * @public\n */\nexport function useComputed<Value>(name: string, compute: () => Value, deps: any[]): Computed<Value>\n\n/**\n * Creates a new computed signal with custom options for advanced behavior like custom equality checking,\n * diff computation, and history tracking. The computed signal will be created only once.\n *\n * @param name - A descriptive name for the computed signal, used for debugging and identification\n * @param compute - A function that computes the value, automatically tracking any signal dependencies\n * @param opts - Configuration options for the computed signal\n * - isEqual - Custom equality function to determine if the computed value has changed\n * - computeDiff - Function to compute diffs between old and new values for history tracking\n * - historyLength - Maximum number of diffs to keep in history buffer for time-travel functionality\n * @param deps - React dependency array that controls when the computed signal is recreated\n * @returns A computed signal containing the calculated value with the specified options\n *\n * @example\n * ```ts\n * function ShoppingCart() {\n * const items = useAtom('items', [])\n *\n * // Computed with custom equality to avoid recalculation for equivalent arrays\n * const sortedItems = useComputed(\n * 'sortedItems',\n * () => items.get().sort((a, b) => a.name.localeCompare(b.name)),\n * {\n * isEqual: (a, b) => a.length === b.length && a.every((item, i) => item.id === b[i].id)\n * },\n * [items]\n * )\n *\n * return <ItemList items={sortedItems.get()} />\n * }\n * ```\n *\n * @public\n */\nexport function useComputed<Value, Diff = unknown>(\n\tname: string,\n\tcompute: () => Value,\n\topts: ComputedOptions<Value, Diff>,\n\tdeps: any[]\n): Computed<Value>\n/**\n * Implementation function that handles both overloaded signatures of useComputed.\n * Uses the arguments object to dynamically determine which signature was called.\n *\n * This function creates a memoized computed signal that automatically tracks dependencies\n * and only recreates when the dependency array changes, providing optimal performance\n * in React components.\n *\n * @public\n */\nexport function useComputed() {\n\tconst name = arguments[0]\n\tconst compute = arguments[1]\n\tconst opts = arguments.length === 3 ? undefined : arguments[2]\n\tconst deps = arguments.length === 3 ? arguments[2] : arguments[3]\n\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\treturn useMemo(() => computed(`useComputed(${name})`, compute, opts), deps)\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mBAAoD;AACpD,mBAAwB;AAiFjB,SAAS,cAAc;AAC7B,QAAM,OAAO,UAAU,CAAC;AACxB,QAAM,UAAU,UAAU,CAAC;AAC3B,QAAM,OAAO,UAAU,WAAW,IAAI,SAAY,UAAU,CAAC;AAC7D,QAAM,OAAO,UAAU,WAAW,IAAI,UAAU,CAAC,IAAI,UAAU,CAAC;AAEhE,aAAO,sBAAQ,UAAM,uBAAS,eAAe,IAAI,KAAK,SAAS,IAAI,GAAG,IAAI;AAC3E;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/useQuickReactor.ts"],
|
|
4
|
-
"sourcesContent": ["import { EMPTY_ARRAY, EffectScheduler } from '@tldraw/state'\nimport { useEffect } from 'react'\n\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6C;AAC7C,mBAA0B;
|
|
4
|
+
"sourcesContent": ["import { EMPTY_ARRAY, EffectScheduler } from '@tldraw/state'\nimport { useEffect } from 'react'\n\n/**\n * A React hook that runs side effects immediately in response to signal changes, without throttling.\n * Unlike useReactor which batches updates to animation frames, useQuickReactor executes the effect\n * function immediately when dependencies change, making it ideal for critical updates that cannot wait.\n *\n * The effect runs immediately when the component mounts and whenever tracked signals change.\n * Updates are not throttled, so the effect executes synchronously on every change.\n *\n * @example\n * ```ts\n * function DataSynchronizer() {\n * const criticalData = useAtom('criticalData', null)\n *\n * useQuickReactor('sync-data', () => {\n * const data = criticalData.get()\n * if (data) {\n * // Send immediately - don't wait for next frame\n * sendToServer(data)\n * }\n * }, [criticalData])\n *\n * return <div>Sync status updated</div>\n * }\n * ```\n *\n * @example\n * ```ts\n * function CursorUpdater({ editor }) {\n * useQuickReactor('update-cursor', () => {\n * const cursor = editor.getInstanceState().cursor\n * document.body.style.cursor = cursor.type\n * }, [])\n * }\n * ```\n *\n * @param name - A descriptive name for the reactor, used for debugging and performance profiling\n * @param reactFn - The effect function to execute when signals change. Should not return a value.\n * @param deps - Optional dependency array that controls when the reactor is recreated. Works like useEffect deps.\n * @public\n */\nexport function useQuickReactor(name: string, reactFn: () => void, deps: any[] = EMPTY_ARRAY) {\n\tuseEffect(() => {\n\t\tconst scheduler = new EffectScheduler(name, reactFn)\n\t\tscheduler.attach()\n\t\tscheduler.execute()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t}\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6C;AAC7C,mBAA0B;AA0CnB,SAAS,gBAAgB,MAAc,SAAqB,OAAc,0BAAa;AAC7F,8BAAU,MAAM;AACf,UAAM,YAAY,IAAI,6BAAgB,MAAM,OAAO;AACnD,cAAU,OAAO;AACjB,cAAU,QAAQ;AAClB,WAAO,MAAM;AACZ,gBAAU,OAAO;AAAA,IAClB;AAAA,EAED,GAAG,IAAI;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAgC;AAChC,mBAAoC;AACpC,mBAA0B;
|
|
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
|
+
"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
|
}
|
|
@@ -32,7 +32,7 @@ __export(useStateTracking_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(useStateTracking_exports);
|
|
34
34
|
var import_state = require("@tldraw/state");
|
|
35
|
-
var import_react = __toESM(require("react"));
|
|
35
|
+
var import_react = __toESM(require("react"), 1);
|
|
36
36
|
function useStateTracking(name, render, deps = []) {
|
|
37
37
|
const renderRef = import_react.default.useRef(render);
|
|
38
38
|
renderRef.current = render;
|
|
@@ -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 * @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;
|
|
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;",
|
|
6
6
|
"names": ["React", "subscribe", "scheduler", "getSnapshot"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/useValue.ts"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { Signal, computed, react } from '@tldraw/state'\nimport { useMemo, useSyncExternalStore } from 'react'\n\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mBAAwC;AACxC,mBAA8C;
|
|
4
|
+
"sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { Signal, computed, react } from '@tldraw/state'\nimport { useMemo, useSyncExternalStore } from 'react'\n\n/**\n * Extracts the current value from a signal and subscribes the component to changes.\n *\n * This is the most straightforward way to read signal values in React components.\n * When the signal changes, the component will automatically re-render with the new value.\n *\n * Note: You do not need to use this hook if you are wrapping the component with {@link track},\n * as tracked components automatically subscribe to any signals accessed with `.get()`.\n *\n * @param value - The signal to read the value from\n * @returns The current value of the signal\n *\n * @example\n * ```ts\n * import { atom } from '@tldraw/state'\n * import { useValue } from '@tldraw/state-react'\n *\n * const count = atom('count', 0)\n *\n * function Counter() {\n * const currentCount = useValue(count)\n * return (\n * <button onClick={() => count.set(currentCount + 1)}>\n * Count: {currentCount}\n * </button>\n * )\n * }\n * ```\n *\n * @public\n */\nexport function useValue<Value>(value: Signal<Value>): Value\n\n/**\n * Creates a computed value with automatic dependency tracking and subscribes to changes.\n *\n * This overload allows you to compute a value from one or more signals with automatic\n * memoization. The computed function will only re-execute when its dependencies change,\n * and the component will only re-render when the computed result changes.\n *\n * @param name - A descriptive name for debugging purposes\n * @param fn - Function that computes the value, should call `.get()` on any signals it depends on\n * @param deps - Array of signals that the computed function depends on\n * @returns The computed value\n *\n * @example\n * ```ts\n * import { atom } from '@tldraw/state'\n * import { useValue } from '@tldraw/state-react'\n *\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * function UserGreeting() {\n * const fullName = useValue('fullName', () => {\n * return `${firstName.get()} ${lastName.get()}`\n * }, [firstName, lastName])\n *\n * return <div>Hello {fullName}!</div>\n * }\n * ```\n *\n * @public\n */\nexport function useValue<Value>(name: string, fn: () => Value, deps: unknown[]): Value\n\n/**\n * Implementation function for useValue hook overloads.\n *\n * Handles both single signal subscription and computed value creation with dependency tracking.\n * Uses React's useSyncExternalStore for efficient subscription management and automatic cleanup.\n *\n * @internal\n */\nexport function useValue() {\n\tconst args = arguments\n\t// deps will be either the computed or the deps array\n\tconst deps = args.length === 3 ? args[2] : [args[0]]\n\tconst name = args.length === 3 ? args[0] : `useValue(${args[0].name})`\n\n\tconst { $val, subscribe, getSnapshot } = useMemo(() => {\n\t\tconst $val =\n\t\t\targs.length === 1 ? (args[0] as Signal<any>) : (computed(name, args[1]) as Signal<any>)\n\n\t\treturn {\n\t\t\t$val,\n\t\t\tsubscribe: (notify: () => void) => {\n\t\t\t\treturn react(`useValue(${name})`, () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t$val.get()\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Will be rethrown during render if the component doesn't unmount first.\n\t\t\t\t\t}\n\t\t\t\t\tnotify()\n\t\t\t\t})\n\t\t\t},\n\t\t\tgetSnapshot: () => $val.lastChangedEpoch,\n\t\t}\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n\n\tuseSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\treturn $val.__unsafe__getWithoutCapture()\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mBAAwC;AACxC,mBAA8C;AA4EvC,SAAS,WAAW;AAC1B,QAAM,OAAO;AAEb,QAAM,OAAO,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnD,QAAM,OAAO,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,EAAE,IAAI;AAEnE,QAAM,EAAE,MAAM,WAAW,YAAY,QAAI,sBAAQ,MAAM;AACtD,UAAMA,QACL,KAAK,WAAW,IAAK,KAAK,CAAC,QAAqB,uBAAS,MAAM,KAAK,CAAC,CAAC;AAEvE,WAAO;AAAA,MACN,MAAAA;AAAA,MACA,WAAW,CAAC,WAAuB;AAClC,mBAAO,oBAAM,YAAY,IAAI,KAAK,MAAM;AACvC,cAAI;AACH,YAAAA,MAAK,IAAI;AAAA,UACV,QAAQ;AAAA,UAER;AACA,iBAAO;AAAA,QACR,CAAC;AAAA,MACF;AAAA,MACA,aAAa,MAAMA,MAAK;AAAA,IACzB;AAAA,EAED,GAAG,IAAI;AAEP,yCAAqB,WAAW,aAAa,WAAW;AACxD,SAAO,KAAK,4BAA4B;AACzC;",
|
|
6
6
|
"names": ["$val"]
|
|
7
7
|
}
|