@ngrok/mantle 0.70.2 → 0.71.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +4 -0
  2. package/dist/agent.json +76 -0
  3. package/dist/alert-dialog.d.ts +337 -90
  4. package/dist/alert-dialog.js.map +1 -1
  5. package/dist/alert.js.map +1 -1
  6. package/dist/anchor-2stEauOz.js.map +1 -1
  7. package/dist/anchor.d.ts +45 -4
  8. package/dist/badge.d.ts +32 -3
  9. package/dist/badge.js.map +1 -1
  10. package/dist/{button-BMgAxAwM.d.ts → button-Bq0x5Pv4.d.ts} +4 -4
  11. package/dist/button.d.ts +1 -1
  12. package/dist/checkbox.d.ts +1 -1
  13. package/dist/code-block.d.ts +1 -1
  14. package/dist/code-block_highlight-utils.d.ts +1 -1
  15. package/dist/code.d.ts +22 -1
  16. package/dist/code.js.map +1 -1
  17. package/dist/combobox.d.ts +10 -0
  18. package/dist/combobox.js.map +1 -1
  19. package/dist/command.d.ts +7 -7
  20. package/dist/command.js.map +1 -1
  21. package/dist/copy-to-clipboard-DjOD_Mwb.js.map +1 -1
  22. package/dist/data-table.d.ts +303 -22
  23. package/dist/data-table.js.map +1 -1
  24. package/dist/dialog-BHzl9eye.js.map +1 -1
  25. package/dist/dialog.d.ts +6 -1
  26. package/dist/flag.d.ts +33 -4
  27. package/dist/flag.js.map +1 -1
  28. package/dist/hooks.d.ts +297 -72
  29. package/dist/hooks.js.map +1 -1
  30. package/dist/hover-card.d.ts +15 -10
  31. package/dist/hover-card.js.map +1 -1
  32. package/dist/icons.js.map +1 -1
  33. package/dist/{index-DMAkXvFI.d.ts → index-C91lxoX9.d.ts} +55 -12
  34. package/dist/input.d.ts +1 -1
  35. package/dist/input.js.map +1 -1
  36. package/dist/kbd-CAVUiqBT.js.map +1 -1
  37. package/dist/kbd.d.ts +37 -8
  38. package/dist/label.d.ts +40 -9
  39. package/dist/label.js.map +1 -1
  40. package/dist/llms.txt +77 -0
  41. package/dist/media-object.d.ts +26 -10
  42. package/dist/media-object.js.map +1 -1
  43. package/dist/multi-select.d.ts +185 -34
  44. package/dist/multi-select.js.map +1 -1
  45. package/dist/otp-input.d.ts +167 -0
  46. package/dist/otp-input.js +2 -0
  47. package/dist/otp-input.js.map +1 -0
  48. package/dist/pagination.d.ts +1 -1
  49. package/dist/pagination.js.map +1 -1
  50. package/dist/popover.d.ts +7 -5
  51. package/dist/popover.js.map +1 -1
  52. package/dist/primitive-tXm_8n_t.js.map +1 -1
  53. package/dist/progress.js.map +1 -1
  54. package/dist/resolve-pre-rendered-props-C-kiaLHj.js.map +1 -1
  55. package/dist/{resolve-pre-rendered-props-x-52gvQ1.d.ts → resolve-pre-rendered-props-CNUnH1fU.d.ts} +2 -2
  56. package/dist/select-DOgdZO0Q.js.map +1 -1
  57. package/dist/{select-BjpP51vO.d.ts → select-DZutJxyr.d.ts} +9 -1
  58. package/dist/select.d.ts +1 -1
  59. package/dist/separator-DSOIrnhj.js.map +1 -1
  60. package/dist/sheet.d.ts +5 -1
  61. package/dist/sheet.js.map +1 -1
  62. package/dist/skeleton.d.ts +32 -5
  63. package/dist/skeleton.js.map +1 -1
  64. package/dist/sort-DzCsa6Qj.js.map +1 -1
  65. package/dist/split-button.d.ts +1 -1
  66. package/dist/{table--DsTqaWO.d.ts → table-BsNJBKiq.d.ts} +7 -3
  67. package/dist/table-Cl4nlRMR.js.map +1 -1
  68. package/dist/table.d.ts +1 -1
  69. package/dist/theme-provider-BFcnjeME.js.map +1 -1
  70. package/dist/theme.d.ts +1 -1
  71. package/dist/theme.js.map +1 -1
  72. package/dist/tooltip.d.ts +31 -14
  73. package/dist/tooltip.js.map +1 -1
  74. package/dist/use-copy-to-clipboard-C7vsjJe-.js.map +1 -1
  75. package/dist/use-matches-media-query-CojcYxlA.js.map +1 -1
  76. package/dist/use-prefers-reduced-motion-aXfsyo-k.js.map +1 -1
  77. package/package.json +14 -7
package/dist/hooks.d.ts CHANGED
@@ -106,42 +106,119 @@ declare function useIsBelowBreakpoint(breakpoint: TailwindBreakpoint): boolean;
106
106
  //#endregion
107
107
  //#region src/hooks/use-callback-ref.d.ts
108
108
  /**
109
- * Returns a memoized callback that will always refer to the latest callback
110
- * passed to the hook.
109
+ * Returns a memoized callback that always invokes the latest version of the
110
+ * provided callback, while preserving a stable function identity across
111
+ * renders.
112
+ *
113
+ * Use this when you need to pass a callback to a child component, an event
114
+ * handler, or a hook dependency array, but the consumer should not re-run /
115
+ * re-render simply because the callback's identity changed. The returned
116
+ * function never changes reference, but internally always calls through to
117
+ * the latest `callback` passed in.
118
+ *
119
+ * Most commonly used as an internal building block for other hooks (for
120
+ * example, {@link useDebouncedCallback}). It is also re-exported publicly
121
+ * for consumers that need the same pattern.
122
+ *
123
+ * @param callback - The callback to wrap. May be `undefined`, in which case
124
+ * invoking the returned function is a no-op until a callback is provided
125
+ * on a subsequent render.
126
+ * @returns A stable function with the same signature as `callback` that
127
+ * forwards its arguments to the most recent `callback` value.
111
128
  *
112
- * This is useful when you want to pass a callback which may or may not be
113
- * memoized (have a stable identity) to a child component that will be updated
114
- * without causing the child component to re-render.
129
+ * @example
130
+ * // Pass a stable handler to a memoized child without re-rendering it
131
+ * const onSelect = useCallbackRef((id: string) => {
132
+ * // reads the latest `props.items` without being in deps
133
+ * props.onSelectItem(id, props.items);
134
+ * });
135
+ *
136
+ * return <MemoizedList onSelect={onSelect} />;
115
137
  */
116
138
  declare function useCallbackRef<T extends (...args: unknown[]) => unknown>(callback: T | undefined): T;
117
139
  //#endregion
118
140
  //#region src/hooks/use-copy-to-clipboard.d.ts
119
141
  /**
120
- * A hook that returns an async function to copy a string to the clipboard.
142
+ * React hook that returns a stable async function for copying a string to
143
+ * the system clipboard.
121
144
  *
122
- * `await` the returned function to know whether the write succeeded. It
123
- * throws when both the Clipboard API and the `execCommand` polyfill fail.
145
+ * The returned function uses the Clipboard API when available and falls back
146
+ * to a `document.execCommand("copy")` polyfill for older browsers. `await`
147
+ * the call (or attach a `.then()` / `.catch()`) to observe whether the copy
148
+ * succeeded — the function throws when both the Clipboard API and the
149
+ * polyfill fail, or when called outside of a DOM environment.
124
150
  *
125
151
  * Inspired by: https://usehooks.com/usecopytoclipboard
152
+ *
153
+ * @returns An async function `(value: string) => Promise<void>` that writes
154
+ * `value` to the clipboard and rejects on failure.
155
+ *
156
+ * @example
157
+ * // Copy a token on click and surface a toast on success/failure
158
+ * const copy = useCopyToClipboard();
159
+ *
160
+ * async function handleCopy() {
161
+ * try {
162
+ * await copy(token);
163
+ * toast.success("Copied!");
164
+ * } catch {
165
+ * toast.error("Could not copy to clipboard");
166
+ * }
167
+ * }
168
+ *
169
+ * return <button onClick={handleCopy}>Copy token</button>;
126
170
  */
127
171
  declare function useCopyToClipboard(): typeof copyToClipboard;
128
172
  //#endregion
129
173
  //#region src/hooks/use-debounced-callback.d.ts
174
+ /**
175
+ * Options for {@link useDebouncedCallback}.
176
+ */
130
177
  type Options = {
131
178
  /**
132
- * The delay in milliseconds to wait before calling the callback.
179
+ * The delay in milliseconds to wait between the last invocation and
180
+ * actually running the callback.
133
181
  */
134
182
  waitMs: number;
135
183
  };
136
184
  /**
137
- * Create a debounced version of a callback function.
185
+ * Returns a debounced version of the provided callback. Each call resets a
186
+ * timer; the underlying callback only runs after `options.waitMs` of
187
+ * inactivity has elapsed.
188
+ *
189
+ * Useful for limiting rapid invocations such as search-as-you-type inputs,
190
+ * window resize handlers, or expensive button-press handlers. The pending
191
+ * timer is automatically cleared on unmount.
192
+ *
193
+ * The debounced function always invokes the latest version of `callbackFn`,
194
+ * so callers do not need to memoize it. The returned function's identity
195
+ * only changes when `options.waitMs` changes, so it is safe to include in
196
+ * dependency arrays.
197
+ *
198
+ * @param callbackFn - The function to debounce. The latest reference passed
199
+ * on each render is always used when the timer fires.
200
+ * @param options - Debounce options.
201
+ * @param options.waitMs - Milliseconds of inactivity to wait before calling
202
+ * `callbackFn`.
203
+ * @returns A function with the same parameter list as `callbackFn` that
204
+ * schedules (or reschedules) the underlying call.
138
205
  *
139
- * It allows you to delay the execution of a function until a certain period of
140
- * inactivity has passed (the `options.waitMs`), which can be useful for limiting rapid
141
- * invocations of a function (like in search inputs or button clicks)
206
+ * @example
207
+ * // Debounce a search input by 300ms
208
+ * const [query, setQuery] = useState("");
209
+ * const search = useDebouncedCallback((value: string) => {
210
+ * fetchResults(value);
211
+ * }, { waitMs: 300 });
142
212
  *
143
- * Note: The debounced callback will always refer to the latest callback passed
144
- * even without memoization, so it's stable and safe to use in dependency arrays.
213
+ * return (
214
+ * <input
215
+ * value={query}
216
+ * onChange={(event) => {
217
+ * setQuery(event.target.value);
218
+ * search(event.target.value);
219
+ * }}
220
+ * />
221
+ * );
145
222
  */
146
223
  declare function useDebouncedCallback<T extends (...args: unknown[]) => unknown>(callbackFn: T, options: Options): (...args: Parameters<T>) => void;
147
224
  //#endregion
@@ -174,71 +251,171 @@ declare function useIsHydrated(): boolean;
174
251
  //#endregion
175
252
  //#region src/hooks/use-isomorphic-layout-effect.d.ts
176
253
  /**
177
- * useIsomorphicLayoutEffect is a hook that uses useLayoutEffect on the client and useEffect on the server.
254
+ * A drop-in replacement for `useLayoutEffect` that does not warn during
255
+ * server-side rendering.
256
+ *
257
+ * Resolves to `useLayoutEffect` in the browser (where it can read layout and
258
+ * synchronously re-render before paint) and to `useEffect` on the server
259
+ * (where layout effects are a no-op and React would otherwise log a
260
+ * "useLayoutEffect does nothing on the server" warning).
261
+ *
262
+ * Use this whenever you need the timing semantics of `useLayoutEffect` in
263
+ * code that may also execute during SSR. It is most often used internally
264
+ * by other Mantle hooks and components.
265
+ *
266
+ * @param effect - The imperative function that may return a cleanup
267
+ * function — same signature as React's `useLayoutEffect` / `useEffect`.
268
+ * @param deps - Optional dependency list, same semantics as
269
+ * `useLayoutEffect`.
270
+ * @returns Nothing.
271
+ *
272
+ * @example
273
+ * // Measure an element synchronously after layout
274
+ * const ref = useRef<HTMLDivElement>(null);
275
+ * const [width, setWidth] = useState(0);
276
+ *
277
+ * useIsomorphicLayoutEffect(() => {
278
+ * if (ref.current) {
279
+ * setWidth(ref.current.getBoundingClientRect().width);
280
+ * }
281
+ * }, []);
282
+ *
283
+ * return <div ref={ref}>Width: {width}</div>;
178
284
  */
179
285
  declare const useIsomorphicLayoutEffect: typeof useEffect;
180
286
  //#endregion
181
287
  //#region src/hooks/use-matches-media-query.d.ts
182
288
  /**
183
- * React hook that subscribes to and returns the result of a CSS media query.
289
+ * React hook that subscribes to a CSS media query and returns whether it
290
+ * currently matches, re-rendering whenever the result changes.
184
291
  *
185
- * This hook uses `window.matchMedia` under the hood and leverages
186
- * `useSyncExternalStore` to stay compliant with React's concurrent rendering model.
292
+ * Uses `window.matchMedia` under the hood and `useSyncExternalStore` for
293
+ * compatibility with React's concurrent rendering model. Returns `false`
294
+ * on the server; during hydration React uses that server snapshot before
295
+ * updating to the client media-query value.
187
296
  *
188
- * @param {string} query - A valid CSS media query string (e.g., `(max-width: 768px)`).
297
+ * For common viewport breakpoint checks, prefer the more specific
298
+ * {@link useBreakpoint} or {@link useIsBelowBreakpoint} hooks.
189
299
  *
190
- * @returns {boolean} `true` if the media query currently matches, otherwise `false`.
300
+ * @param query - A valid CSS media query string
301
+ * (e.g. `"(max-width: 768px)"`, `"(prefers-color-scheme: dark)"`).
302
+ * @returns `true` if the media query currently matches, otherwise `false`.
191
303
  *
192
304
  * @example
193
- * // Detect if the user prefers a dark color scheme:
194
- * const isDarkMode = useMatchesMediaQuery("(prefers-color-scheme: dark)");
305
+ * // Detect if the user prefers a dark color scheme
306
+ * const prefersDark = useMatchesMediaQuery("(prefers-color-scheme: dark)");
195
307
  *
196
- * if (isDarkMode) {
197
- * document.body.classList.add("dark");
198
- * } else {
199
- * document.body.classList.remove("dark");
200
- * }
308
+ * return <div className={prefersDark ? "dark" : "light"}>Hello</div>;
309
+ *
310
+ * @example
311
+ * // Show a different layout on portrait orientation
312
+ * const isPortrait = useMatchesMediaQuery("(orientation: portrait)");
313
+ *
314
+ * return isPortrait ? <PortraitLayout /> : <LandscapeLayout />;
201
315
  */
202
316
  declare function useMatchesMediaQuery(query: string): boolean;
203
317
  //#endregion
204
318
  //#region src/hooks/use-prefers-reduced-motion.d.ts
205
319
  /**
206
- * Imperatively reads the current `prefers-reduced-motion` preference.
207
- * Useful in event handlers and plain functions where a hook cannot be called.
320
+ * Imperatively reads the current `prefers-reduced-motion` preference once at
321
+ * the time of the call.
322
+ *
323
+ * Useful in event handlers, animation entrypoints, or plain functions where
324
+ * a React hook cannot be called. Prefer {@link usePrefersReducedMotion}
325
+ * inside components — it subscribes to live changes.
208
326
  *
209
- * Returns `true` when the user has opted out of animations.
327
+ * @returns `true` when the user has opted out of animations or when called
328
+ * outside a browser environment (SSR), `false` when motion is allowed.
210
329
  *
211
330
  * @remarks
212
- * Returns `true` (reduce motion) when called outside a browser environment (SSR),
213
- * matching the conservative default of {@link usePrefersReducedMotion}.
331
+ * The conservative SSR default of `true` matches
332
+ * {@link usePrefersReducedMotion}: animations stay off until we can verify
333
+ * the user's preference on the client.
334
+ *
335
+ * @example
336
+ * // Skip a one-off entrance animation in a click handler
337
+ * function onOpen() {
338
+ * if (getPrefersReducedMotion()) {
339
+ * element.style.opacity = "1";
340
+ * return;
341
+ * }
342
+ * element.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 200 });
343
+ * }
214
344
  */
215
345
  declare function getPrefersReducedMotion(): boolean;
216
346
  /**
217
- * Returns `true` when the user has opted out of animations (i.e., prefers reduced motion).
347
+ * React hook that subscribes to the user's `prefers-reduced-motion` media
348
+ * query and re-renders when it changes.
349
+ *
350
+ * Defaults to `true` (reduce motion) on the server and during the first
351
+ * client render to avoid animating before hydration. The initial client
352
+ * effect reads the *real* preference and updates state. The underlying
353
+ * media query used is `(prefers-reduced-motion: no-preference)` inverted —
354
+ * "if the system hasn't opted out, animations are allowed."
218
355
  *
219
- * Implementation notes:
220
- * - Uses the `(prefers-reduced-motion: no-preference)` media query and inverts it.
221
- * This keeps the “default” mental model explicit: if the system hasn’t opted out,
222
- * animations are allowed.
223
- * - Defaults to `true` (reduce motion) on the server/during SSR to avoid animating
224
- * before hydration. The initial client effect reads the *real* preference and updates state.
356
+ * @returns `true` when the user prefers reduced motion (animations should be
357
+ * shortened or skipped), `false` when full motion is acceptable.
358
+ *
359
+ * @remarks
360
+ * If you need to support very old browsers that lack
361
+ * `MediaQueryList.addEventListener`, consider falling back to
362
+ * `addListener` / `removeListener`.
225
363
  *
226
364
  * @example
227
365
  * // Conditionally shorten or skip transitions
228
- * const reduce = usePrefersReducedMotion();
229
- * const duration = reduce ? 0 : 200;
366
+ * const prefersReducedMotion = usePrefersReducedMotion();
367
+ * const duration = prefersReducedMotion ? 0 : 200;
230
368
  *
231
- * @remarks
232
- * If you need to support very old browsers that lack `MediaQueryList.addEventListener`,
233
- * consider falling back to `addListener/removeListener`.
369
+ * return <Modal transitionDuration={duration} />;
370
+ *
371
+ * @example
372
+ * // Disable an autoplaying carousel when motion is reduced
373
+ * const prefersReducedMotion = usePrefersReducedMotion();
374
+ *
375
+ * return <Carousel autoplay={!prefersReducedMotion} />;
234
376
  */
235
377
  declare function usePrefersReducedMotion(): boolean;
236
378
  //#endregion
237
379
  //#region src/hooks/use-random-stable-id.d.ts
238
380
  /**
239
- * Hook to generate a random, stable id.
240
- * This is similar to `useId`, but generates a stable id client side which can also
241
- * be used for css selectors and element ids.
381
+ * React hook that returns a random, stable id (e.g. `"mantle-a3f9k7q"`)
382
+ * suitable for DOM `id` attributes and `aria-*` references.
383
+ *
384
+ * Unlike React's built-in `useId`, the generated suffix does not contain
385
+ * special characters (`:`). The default id is safe to use directly in CSS
386
+ * selectors and `querySelector` calls; if you provide a custom `prefix`,
387
+ * keep it selector-safe or escape the final id with `CSS.escape()` before
388
+ * querying. The id is generated once for the lifetime of the component and
389
+ * is stable across re-renders, but a new value is produced when `prefix`
390
+ * changes.
391
+ *
392
+ * @param prefix - Optional string prepended to the generated suffix.
393
+ * Whitespace-only or empty values fall back to `"mantle"`. Use a
394
+ * selector-safe prefix if you plan to reference the id in CSS selectors
395
+ * without escaping. Defaults to `"mantle"`.
396
+ * @returns A string of the form `"<prefix>-<7-char-random>"`.
397
+ *
398
+ * @example
399
+ * // Associate a label with a custom input
400
+ * const id = useRandomStableId("email-input");
401
+ *
402
+ * return (
403
+ * <>
404
+ * <label htmlFor={id}>Email</label>
405
+ * <input id={id} type="email" />
406
+ * </>
407
+ * );
408
+ *
409
+ * @example
410
+ * // Use as an aria-controls reference
411
+ * const panelId = useRandomStableId("panel");
412
+ *
413
+ * return (
414
+ * <>
415
+ * <button aria-controls={panelId}>Toggle</button>
416
+ * <div id={panelId}>Panel contents</div>
417
+ * </>
418
+ * );
242
419
  */
243
420
  declare const useRandomStableId: (prefix?: string) => string;
244
421
  //#endregion
@@ -253,24 +430,38 @@ declare const useRandomStableId: (prefix?: string) => string;
253
430
  */
254
431
  type ScrollBehavior = "auto" | "smooth";
255
432
  /**
256
- * Returns a `ScrollBehavior` that respects the user's motion preference via `usePrefersReducedMotion`.
433
+ * React hook that returns a {@link ScrollBehavior} value (`"auto"` or
434
+ * `"smooth"`) that respects the user's motion preference.
257
435
  *
258
- * - When `usePrefersReducedMotion()` is `true`, returns `"auto"` (no animated scroll).
259
- * - Otherwise returns `"smooth"`.
436
+ * Internally calls {@link usePrefersReducedMotion}: when reduced motion is
437
+ * preferred, this returns `"auto"` (no animated scroll); otherwise it
438
+ * returns `"smooth"`. Pair this with `window.scrollTo`,
439
+ * `Element.scrollIntoView`, or any other scroll API that accepts a
440
+ * `behavior` option to avoid forcing animations on users who have opted
441
+ * out of motion. The conservative SSR default also prevents "first paint"
442
+ * scroll animations.
260
443
  *
261
- * Use this with `window.scrollTo`, `Element.scrollIntoView`, etc. It prevents
262
- * smooth-scrolling for users who opt out of motion and avoids SSR “first paint”
263
- * animations thanks to the hook’s conservative server default.
444
+ * @returns `"auto"` when the user prefers reduced motion, otherwise
445
+ * `"smooth"`.
264
446
  *
265
447
  * @example
266
- * // Scroll to top
448
+ * // Scroll to the top of the page on a button click
267
449
  * const behavior = useScrollBehavior();
268
- * window.scrollTo({ top: 0, behavior });
450
+ *
451
+ * return (
452
+ * <button onClick={() => window.scrollTo({ top: 0, behavior })}>
453
+ * Back to top
454
+ * </button>
455
+ * );
269
456
  *
270
457
  * @example
271
- * // Bring a section into view
458
+ * // Bring a referenced section into view
272
459
  * const behavior = useScrollBehavior();
273
- * sectionRef.current?.scrollIntoView({ behavior, block: "start" });
460
+ * const sectionRef = useRef<HTMLElement>(null);
461
+ *
462
+ * function focusSection() {
463
+ * sectionRef.current?.scrollIntoView({ behavior, block: "start" });
464
+ * }
274
465
  *
275
466
  * @see {@link usePrefersReducedMotion}
276
467
  * @see CSS `scroll-behavior` property (values: `"auto"`, `"smooth"`).
@@ -361,32 +552,66 @@ type UseUndoRedoReturn<T> = {
361
552
  redo: (current: T) => T | undefined;
362
553
  };
363
554
  /**
364
- * A generic undo/redo hook backed by a reducer.
365
- *
366
- * Maintains two stacks (undo and redo). Call `push` before mutating state
367
- * to snapshot the current value. Call `undo`/`redo` with the current value
368
- * to swap it with the previous/next snapshot.
555
+ * Generic undo/redo hook backed by a reducer that maintains two history
556
+ * stacks (undo and redo).
557
+ *
558
+ * The hook does not own your application state instead it helps you
559
+ * snapshot it. Call `push(snapshot)` *before* mutating state to capture
560
+ * the current value, then call `undo(current)` or `redo(current)` to swap
561
+ * `current` with the previous/next snapshot. Both `undo` and `redo` return
562
+ * the snapshot to apply, or `undefined` if their stack is empty. Pushing a
563
+ * new snapshot clears the redo stack, matching standard editor semantics.
564
+ *
565
+ * @typeParam T - The type of the value being snapshotted (e.g. a list of
566
+ * items, a serialized form value, etc.).
567
+ *
568
+ * @returns An object with the current undo/redo capability flags and
569
+ * actions:
570
+ * - `canUndo`: `true` when there is at least one snapshot on the undo
571
+ * stack.
572
+ * - `canRedo`: `true` when there is at least one snapshot on the redo
573
+ * stack.
574
+ * - `push(snapshot)`: Push a snapshot onto the undo stack and clear the
575
+ * redo stack. Call this *before* mutating state.
576
+ * - `undo(current)`: Pop the latest undo snapshot and return it; returns
577
+ * `undefined` when the undo stack is empty. The supplied `current` is
578
+ * pushed onto the redo stack so you can redo back to it.
579
+ * - `redo(current)`: Pop the latest redo snapshot and return it; returns
580
+ * `undefined` when the redo stack is empty. The supplied `current` is
581
+ * pushed onto the undo stack.
369
582
  *
370
583
  * @example
371
- * ```tsx
584
+ * // Snapshot before mutating, then wire up keyboard shortcuts
585
+ * const [items, setItems] = useState<string[]>([]);
372
586
  * const { push, undo, redo, canUndo, canRedo } = useUndoRedo<string[]>();
373
587
  *
374
588
  * function removeItem(item: string) {
375
- * push([...items]); // snapshot before mutation
376
- * setItems(items.filter((i) => i !== item));
589
+ * push(items); // snapshot before mutation
590
+ * setItems((prev) => prev.filter((entry) => entry !== item));
377
591
  * }
378
592
  *
379
- * function handleKeyDown(event: KeyboardEvent) {
380
- * if ((event.metaKey || event.ctrlKey) && event.key === "z" && !event.shiftKey) {
593
+ * function handleKeyDown(event: React.KeyboardEvent) {
594
+ * const cmd = event.metaKey || event.ctrlKey;
595
+ * if (cmd && event.key === "z" && !event.shiftKey) {
381
596
  * const previous = undo(items);
382
- * if (previous) setItems(previous);
597
+ * if (previous) {
598
+ * setItems(previous);
599
+ * }
383
600
  * }
384
- * if ((event.metaKey || event.ctrlKey) && (event.shiftKey && event.key === "z" || event.key === "y")) {
601
+ * if (cmd && ((event.shiftKey && event.key === "z") || event.key === "y")) {
385
602
  * const next = redo(items);
386
- * if (next) setItems(next);
603
+ * if (next) {
604
+ * setItems(next);
605
+ * }
387
606
  * }
388
607
  * }
389
- * ```
608
+ *
609
+ * return (
610
+ * <div tabIndex={0} onKeyDown={handleKeyDown}>
611
+ * <button disabled={!canUndo} onClick={() => { const previous = undo(items); if (previous) setItems(previous); }}>Undo</button>
612
+ * <button disabled={!canRedo} onClick={() => { const next = redo(items); if (next) setItems(next); }}>Redo</button>
613
+ * </div>
614
+ * );
390
615
  */
391
616
  declare function useUndoRedo<T>(): UseUndoRedoReturn<T>;
392
617
  //#endregion