@rivetjs/cdk 0.1.0-alpha.0 → 0.1.0-alpha.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 (61) hide show
  1. package/_adapters/selection.d.ts +13 -2
  2. package/_adapters/selection.js +39 -15
  3. package/button/index.jsx +5 -3
  4. package/collections/components.d.ts +36 -1
  5. package/collections/components.jsx +224 -16
  6. package/collections/nodes.d.ts +9 -1
  7. package/collections/nodes.js +26 -0
  8. package/collections/row-stamps.d.ts +17 -0
  9. package/collections/row-stamps.js +108 -0
  10. package/collections/server-dev.d.ts +1 -0
  11. package/collections/server-dev.js +2 -0
  12. package/collections/server-off.d.ts +1 -0
  13. package/collections/server-off.js +2 -0
  14. package/collections/server-on.d.ts +1 -0
  15. package/collections/server-on.jsx +10 -0
  16. package/drag-drop/directives/drag.d.ts +16 -0
  17. package/drag-drop/directives/drag.js +28 -0
  18. package/drag-drop/drag.jsx +6 -4
  19. package/drag-drop/lazy-refs.js +15 -4
  20. package/grid-list/behaviour.d.ts +6 -0
  21. package/grid-list/behaviour.js +25 -7
  22. package/grid-list/index.d.ts +3 -1
  23. package/grid-list/index.jsx +60 -24
  24. package/interactions/press.jsx +2 -1
  25. package/interactions/utils.d.ts +2 -3
  26. package/interactions/utils.js +8 -7
  27. package/listbox/behaviour.jsx +37 -14
  28. package/listbox/index.jsx +65 -29
  29. package/menu/index.jsx +38 -7
  30. package/package.json +4 -4
  31. package/portal/index.d.ts +8 -0
  32. package/portal/index.jsx +8 -1
  33. package/radio-group/index.jsx +4 -1
  34. package/selection/keyed.js +6 -16
  35. package/selection/selectable-collection.js +42 -17
  36. package/sidebar/index.jsx +5 -4
  37. package/state/menu.d.ts +3 -0
  38. package/state/menu.js +1 -0
  39. package/table/index.jsx +512 -168
  40. package/tree/index.jsx +26 -4
  41. package/utils/data-attrs.d.ts +4 -2
  42. package/utils/data-attrs.jsx +27 -3
  43. package/utils/index.d.ts +1 -15
  44. package/utils/index.js +5 -102
  45. package/utils/link-state.d.ts +6 -0
  46. package/utils/link-state.js +6 -0
  47. package/utils/links.d.ts +1 -6
  48. package/utils/links.jsx +2 -4
  49. package/utils/transitions.d.ts +15 -0
  50. package/utils/transitions.js +104 -0
  51. package/virtualizer/behaviour.d.ts +35 -4
  52. package/virtualizer/behaviour.js +131 -9
  53. package/virtualizer/collection-renderer.d.ts +62 -0
  54. package/virtualizer/collection-renderer.jsx +190 -0
  55. package/virtualizer/engine.d.ts +1 -1
  56. package/virtualizer/index.d.ts +1 -0
  57. package/virtualizer/index.jsx +1 -0
  58. package/virtualizer/list-layout.js +4 -2
  59. package/virtualizer/state.js +12 -2
  60. package/virtualizer/virtualizer.d.ts +8 -6
  61. package/virtualizer/virtualizer.jsx +4 -1
@@ -301,6 +301,9 @@ const memoOf = (owner) => {
301
301
  };
302
302
  /** scroll on the scrollable body: remember the position (restored when focus re-enters). */
303
303
  export function selectableCollectionScroll(options, e) {
304
+ // RA: not for a virtualized collection (the virtualizer owns its scroll position).
305
+ if (options.isVirtualized)
306
+ return;
304
307
  const r = resolve(options, e);
305
308
  const m = memoOf(r.ref ?? options);
306
309
  m.scrollTop = r.scrollRef?.scrollTop ?? 0;
@@ -347,7 +350,7 @@ export function selectableCollectionFocus(options, e) {
347
350
  else
348
351
  navigate(manager.firstSelectedKey ?? delegate.getFirstKey?.());
349
352
  }
350
- else if (r.scrollRef) {
353
+ else if (!options.isVirtualized && r.scrollRef) {
351
354
  // Restore the scroll position to what it was before.
352
355
  r.scrollRef.scrollTop = m.scrollTop;
353
356
  r.scrollRef.scrollLeft = m.scrollLeft;
@@ -466,6 +469,37 @@ export function selectableCollectionAutoFocus(options) {
466
469
  syncFocus(options, null);
467
470
  return true;
468
471
  }
472
+ /** useSelectableItem's focus + useSelectableCollection's scroll-into-view for the focused item element. */
473
+ function focusItem(r, element, didAutoFocus) {
474
+ if (!r.shouldUseVirtualFocus) {
475
+ if (element.ownerDocument.activeElement !== element)
476
+ focusSafely(element);
477
+ }
478
+ else
479
+ moveVirtualFocus(element);
480
+ const modality = getInteractionModality();
481
+ if ((modality === "keyboard" || didAutoFocus) && r.scrollRef) {
482
+ scrollIntoView(r.scrollRef, element);
483
+ // Avoid scroll in iOS VO, since it may cause overlay to close.
484
+ if (modality !== "virtual")
485
+ scrollIntoViewport(element, { containingElement: r.ref });
486
+ }
487
+ }
488
+ /** Virtualized collections: focus the item of `key` after the render that adds it (a microtask, then a few frames). */
489
+ function whenRendered(r, key, didAutoFocus) {
490
+ let frames = 0;
491
+ const attempt = () => {
492
+ const manager = r.manager;
493
+ if (untrack(() => manager.focusedKey) !== key || !untrack(() => manager.isFocused) || !r.ref)
494
+ return;
495
+ const element = getItemElement(r.ref, key);
496
+ if (element)
497
+ focusItem(r, element, didAutoFocus);
498
+ else if (frames++ < 3)
499
+ requestAnimationFrame(attempt);
500
+ };
501
+ queueMicrotask(attempt);
502
+ }
469
503
  /**
470
504
  * react-aria's post-render effects, run after a handler changed state: move DOM (or virtual) focus
471
505
  * to the focused item, scroll it into view (keyboard modality / after auto focus), and focus the
@@ -480,22 +514,13 @@ export function syncFocus(options, previousKey, event) {
480
514
  const changed = focusedKey !== previousKey;
481
515
  if (isFocused && focusedKey != null && (changed || m.didAutoFocus) && r.ref) {
482
516
  const element = getItemElement(r.ref, focusedKey);
483
- if (element) {
484
- // useSelectableItem: focus the item that became the focused key.
485
- if (!r.shouldUseVirtualFocus) {
486
- if (element.ownerDocument.activeElement !== element)
487
- focusSafely(element);
488
- }
489
- else
490
- moveVirtualFocus(element);
491
- const modality = getInteractionModality();
492
- if ((modality === "keyboard" || m.didAutoFocus) && r.scrollRef) {
493
- scrollIntoView(r.scrollRef, element);
494
- // Avoid scroll in iOS VO, since it may cause overlay to close.
495
- if (modality !== "virtual")
496
- scrollIntoViewport(element, { containingElement: r.ref });
497
- }
498
- }
517
+ const didAutoFocus = m.didAutoFocus;
518
+ if (element)
519
+ focusItem(r, element, didAutoFocus);
520
+ // Virtualized: the item renders once the virtualizer persists the new focused key (RA: useSelectableItem's
521
+ // effect after that render) — focus and scroll it in when it appears.
522
+ else if (options.isVirtualized)
523
+ whenRendered(r, focusedKey, didAutoFocus);
499
524
  }
500
525
  // If the focused key becomes null (e.g. the last item is deleted), focus the whole collection.
501
526
  if (!r.shouldUseVirtualFocus && isFocused && focusedKey == null && previousKey != null && r.ref)
package/sidebar/index.jsx CHANGED
@@ -36,7 +36,7 @@
36
36
  // Deviations: `data-active` / `data-show-on-hover` are `"true"` or absent (React writes `"false"` too);
37
37
  // `SidebarMenuAction` writes `data-show-on-hover` and `SidebarMenuButton` `data-variant` (upstream: Tailwind
38
38
  // classes) so CSS can key on them; the desktop root stays in the DOM on mobile (empty, CSS-hidden) so `SidebarInset`'s sibling selectors keep working.
39
- import { createContext, provide, useContext } from "@rivetjs/reactivity";
39
+ import { createContext, provide, signal, useContext } from "@rivetjs/reactivity";
40
40
  import { Show, clientEffect, clientEvent, ref as createRef } from "@rivetjs/runtime/dom";
41
41
  import { resumableContext } from "@rivetjs/runtime/resume-format";
42
42
  import { visuallyHidden } from "../a11y/index.js";
@@ -548,12 +548,13 @@ export function SidebarMenuBadge(props) {
548
548
  /** A loading placeholder for a menu item. */
549
549
  export function SidebarMenuSkeleton(props) {
550
550
  const { showIcon = false, iconClass, textClass, class: cls, style, render, ref, children: _children, ...rest } = props;
551
- // Random width between 50 to 90%.
552
- const width = `${Math.floor(Math.random() * 40) + 50}%`;
551
+ // Random width between 50 to 90%. Rivet: state (upstream `useState`), so the server's pick is the page's: a resumed or
552
+ // adopted skeleton keeps the width the markup shows.
553
+ const width = signal(`${Math.floor(Math.random() * 40) + 50}%`);
553
554
  const rp = renderProps({ class: cls, values: NONE, defaultClassName: "rv-SidebarMenuSkeleton" });
554
555
  const parts = () => (<>
555
556
  {showIcon ? <div class={iconClass ?? "rv-SidebarMenuSkeleton-icon"} data-sidebar="menu-skeleton-icon"/> : null}
556
- <div class={textClass ?? "rv-SidebarMenuSkeleton-text"} data-sidebar="menu-skeleton-text" style={{ "--skeleton-width": width }}/>
557
+ <div class={textClass ?? "rv-SidebarMenuSkeleton-text"} data-sidebar="menu-skeleton-text" style={{ "--skeleton-width": width() }}/>
557
558
  </>);
558
559
  if (render)
559
560
  return render({ ...rest, ref, "data-slot": "sidebar-menu-skeleton", "data-sidebar": "menu-skeleton", class: () => rp.class(), style, children: parts }, undefined);
package/state/menu.d.ts CHANGED
@@ -2,6 +2,7 @@ import { type Signal } from "@rivetjs/reactivity";
2
2
  import { type FocusStrategy, type MultipleSelectionState } from "../_adapters/collections.js";
3
3
  import type { PressEvent } from "../interactions/press.jsx";
4
4
  import type { HoverEvent } from "../interactions/hover.jsx";
5
+ import type { VirtualCollection } from "../_adapters/selection.js";
5
6
  import { type OverlayTriggerProps, type OverlayTriggerState } from "./overlays.js";
6
7
  import { type Key, type ListProps, type ListState, type Selection, type SelectionMode } from "./list.js";
7
8
  export type { Key, Selection, SelectionMode, FocusStrategy };
@@ -134,6 +135,8 @@ export declare function createMenuState(props: MenuProps, context?: {
134
135
  submenuTrigger?: SubmenuTriggerState;
135
136
  parentId?: string;
136
137
  id?: string;
138
+ /** Rivet: a Menu inside a `CollectionVirtualizer` (its built collection + the layout delegate). */
139
+ virtual?: VirtualCollection;
137
140
  }): MenuState;
138
141
  /** Register a rendered MenuItem's callbacks / disabled flag (RAC: the collection node's props). */
139
142
  export declare function registerMenuItem(state: MenuState, key: Key, item: MenuItemCallbacks & {
package/state/menu.js CHANGED
@@ -132,6 +132,7 @@ export function createMenuState(props, context = {}) {
132
132
  delays: {},
133
133
  groups: {},
134
134
  itemCount: signal(0),
135
+ ...(context.virtual ? { virtual: context.virtual } : {}),
135
136
  };
136
137
  });
137
138
  }