@enonic/ui 0.49.0 → 0.50.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.
@@ -21,66 +21,8 @@ export type UseScrollActiveIntoViewConfig = {
21
21
  buildElementId?: (activeId: string) => string;
22
22
  };
23
23
  /**
24
- * Automatically scrolls the active item into view within a scrollable container.
25
- *
26
- * This hook is commonly used in keyboard-navigable lists, menus, and navigation
27
- * components to ensure the currently active item is visible when it changes.
28
- *
29
- * Features:
30
- * - Smooth scrolling behavior with 'nearest' algorithm (minimal scroll distance)
31
- * - Supports both vertical and horizontal scrolling
32
- * - Supports custom ID construction for prefixed/namespaced IDs
33
- * - Only scrolls when activeId changes
34
- *
35
- * @param config - Configuration object for scroll behavior
36
- *
37
- * @example
38
- * ```tsx
39
- * // Vertical list (Menu)
40
- * function MenuContent() {
41
- * const contentRef = useRef<HTMLDivElement>(null);
42
- * const { active } = useMenu();
43
- *
44
- * useScrollActiveIntoView({
45
- * containerRef: contentRef,
46
- * activeId: active,
47
- * orientation: 'vertical',
48
- * });
49
- *
50
- * return <div ref={contentRef}>...</div>;
51
- * }
52
- *
53
- * // Horizontal navigation (Menubar)
54
- * function Menubar() {
55
- * const menubarRef = useRef<HTMLDivElement>(null);
56
- * const { active } = useMenubar();
57
- *
58
- * useScrollActiveIntoView({
59
- * containerRef: menubarRef,
60
- * activeId: active,
61
- * orientation: 'horizontal',
62
- * });
63
- *
64
- * return <div ref={menubarRef}>...</div>;
65
- * }
66
- *
67
- * // With ID prefix (Listbox)
68
- * function ListboxContent({ baseId }: { baseId: string }) {
69
- * const contentRef = useRef<HTMLDivElement>(null);
70
- * const { active } = useListbox();
71
- *
72
- * // Memoize to keep the effect dependency stable across re-renders
73
- * const buildElementId = useCallback((id: string) => `${baseId}-listbox-option-${id}`, [baseId]);
74
- *
75
- * useScrollActiveIntoView({
76
- * containerRef: contentRef,
77
- * activeId: active,
78
- * orientation: 'vertical',
79
- * buildElementId,
80
- * });
81
- *
82
- * return <div ref={contentRef}>...</div>;
83
- * }
84
- * ```
24
+ * Reactive scroll-into-view for keyboard navigation. Uses `block: 'nearest'`
25
+ * for minimal scroll travel. For the open-transition "center on selected"
26
+ * behavior, see `useActiveOnOpen` which performs an imperative center-scroll.
85
27
  */
86
28
  export declare function useScrollActiveIntoView({ containerRef, activeId, orientation, buildElementId, }: UseScrollActiveIntoViewConfig): void;
@@ -0,0 +1,42 @@
1
+ import { RefObject } from 'react';
2
+ type VirtuosoLikeHandle = {
3
+ scrollToIndex: (location: number | {
4
+ index: number;
5
+ align?: 'start' | 'center' | 'end';
6
+ behavior?: 'auto' | 'smooth';
7
+ }) => void;
8
+ };
9
+ export type UseScrollSelectedToVirtuosoConfig<TItem extends {
10
+ id: string;
11
+ }> = {
12
+ /** Whether the dropdown is open. The hook fires on the rising edge. */
13
+ open: boolean;
14
+ /** The single selected ID to center on open, or undefined to skip. */
15
+ selectedId: string | undefined;
16
+ /**
17
+ * Setter called with `selectedId` on the rising edge so navigation
18
+ * resumes from there. Pass the active setter from the wrapped list
19
+ * (e.g., `VirtualizedTreeList`'s `onActiveChange` consumer).
20
+ */
21
+ setActive: (id: string) => void;
22
+ /** Flat items in render order. Used to look up the index for `scrollToIndex`. */
23
+ items: readonly TItem[];
24
+ /** Ref to the Virtuoso instance for scroll operations. */
25
+ virtuosoRef: RefObject<VirtuosoLikeHandle | null> | undefined;
26
+ };
27
+ /**
28
+ * Centers the selected item in a Virtuoso-backed virtualized list when its
29
+ * containing dropdown opens, and sets the consumer's active state to match.
30
+ *
31
+ * Retries on `requestAnimationFrame` while the Virtuoso ref is still null
32
+ * (Virtuoso typically attaches within a frame of mount). Bails after a small
33
+ * cap to avoid infinite loops.
34
+ *
35
+ * Use inside a consumer that wraps `VirtualizedTreeList` in `Combobox.TreeContent`
36
+ * (or any other open/close container) when the same UX as `Combobox.ListContent`
37
+ * is desired: open with selection → centered, arrow keys → nearest.
38
+ */
39
+ export declare function useScrollSelectedToVirtuoso<TItem extends {
40
+ id: string;
41
+ }>({ open, selectedId, setActive, items, virtuosoRef, }: UseScrollSelectedToVirtuosoConfig<TItem>): void;
42
+ export {};
@@ -29,6 +29,12 @@ export type ComboboxContextValue = {
29
29
  isItemDisabled: (id: string) => boolean;
30
30
  hasValue: boolean;
31
31
  setHasValue: (has: boolean) => void;
32
+ /**
33
+ * Whether the auto-active-on-open behavior should run inside Listbox-backed
34
+ * content. Set to `false` when active state is consumer-controlled. Tree
35
+ * content doesn't use this — see `useScrollSelectedToVirtuoso` instead.
36
+ */
37
+ autoActiveOnOpen: boolean;
32
38
  };
33
39
  export type ComboboxProviderProps = {
34
40
  value: ComboboxContextValue;
@@ -26,6 +26,11 @@ export type ListboxContextValue = {
26
26
  unregisterItem: (id: string) => void;
27
27
  getItems: () => string[];
28
28
  isItemDisabled: (id: string) => boolean;
29
+ /**
30
+ * When `false`, `useActiveOnOpen` in `Listbox.Content` is skipped. Set
31
+ * by hosts (e.g., Combobox) when the consumer controls active state.
32
+ */
33
+ autoActiveOnOpen: boolean;
29
34
  };
30
35
  export type ListboxProviderProps = {
31
36
  value: ListboxContextValue;
@@ -9,6 +9,7 @@ export type VirtualizedTreeListContextValue = {
9
9
  toggleSelection: (id: string, index: number) => void;
10
10
  selectionMode: 'single' | 'multiple' | 'none';
11
11
  isFocused: boolean;
12
+ keyboardActive: boolean;
12
13
  items: readonly FlatNodeBase[];
13
14
  getItemIndex: (id: string) => number;
14
15
  scrollToIndex: (index: number) => void;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Whether the browser supports CSS `field-sizing: content` for content-based form control sizing.
3
+ * Evaluated once at module load. `false` in non-browser environments.
4
+ * @see https://caniuse.com/mdn-css_properties_field-sizing
5
+ */
6
+ export declare const SUPPORTS_FIELD_SIZING: boolean;
@@ -2,4 +2,5 @@ export * from './aria';
2
2
  export * from './cn';
3
3
  export * from './mobile';
4
4
  export * from './ref';
5
+ export * from './scroll';
5
6
  export * from './unwrap';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Scrolls an element to the center of its nearest scrollable ancestor without
3
+ * disturbing outer scroll containers (page, dialog, etc.).
4
+ *
5
+ * - Chromium 134+ uses `scrollIntoView({ block: 'center', container: 'nearest' })`
6
+ * and centers the element within the closest scroll container only.
7
+ * - Other browsers fall back to `scrollIntoView({ block: 'nearest' })` — the
8
+ * element lands at the closest edge instead of the center, but ancestors
9
+ * that already show it stay put.
10
+ *
11
+ * Useful for dropdowns: bring the selected option into view on open without
12
+ * scrolling the page underneath.
13
+ */
14
+ export declare function scrollIntoCenter(element: HTMLElement): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enonic/ui",
3
- "version": "0.49.0",
3
+ "version": "0.50.1",
4
4
  "description": "Enonic UI Component Library",
5
5
  "author": "Enonic",
6
6
  "license": "MIT",