@box/metadata-taxonomy-picker 2.18.0

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 (170) hide show
  1. package/LICENSE +379 -0
  2. package/README.md +57 -0
  3. package/dist/chunks/empty-state.js +22 -0
  4. package/dist/chunks/error-state.js +41 -0
  5. package/dist/chunks/load-more-error-row.js +53 -0
  6. package/dist/chunks/load-more-loading-row.js +17 -0
  7. package/dist/chunks/loading-state.js +17 -0
  8. package/dist/chunks/metadata-taxonomy-picker-shell.js +278 -0
  9. package/dist/chunks/taxonomy-ancestor-breadcrumb.js +115 -0
  10. package/dist/chunks/taxonomy-items-list.js +114 -0
  11. package/dist/chunks/taxonomy-level-filter.js +47 -0
  12. package/dist/chunks/taxonomy-menu-item.js +99 -0
  13. package/dist/esm/index.js +3 -0
  14. package/dist/esm/lib/components/metadata-taxonomy-picker-shell/index.js +2 -0
  15. package/dist/esm/lib/components/metadata-taxonomy-picker-shell/messages.js +24 -0
  16. package/dist/esm/lib/components/metadata-taxonomy-picker-shell/metadata-taxonomy-picker-shell.js +2 -0
  17. package/dist/esm/lib/components/taxonomy-ancestor-breadcrumb/index.js +2 -0
  18. package/dist/esm/lib/components/taxonomy-ancestor-breadcrumb/taxonomy-ancestor-breadcrumb.js +2 -0
  19. package/dist/esm/lib/components/taxonomy-items-list/empty-state/empty-state.js +2 -0
  20. package/dist/esm/lib/components/taxonomy-items-list/empty-state/index.js +2 -0
  21. package/dist/esm/lib/components/taxonomy-items-list/error-state/error-state.js +2 -0
  22. package/dist/esm/lib/components/taxonomy-items-list/error-state/index.js +2 -0
  23. package/dist/esm/lib/components/taxonomy-items-list/index.js +6 -0
  24. package/dist/esm/lib/components/taxonomy-items-list/load-more-feedback/index.js +3 -0
  25. package/dist/esm/lib/components/taxonomy-items-list/load-more-feedback/load-more-error-row.js +2 -0
  26. package/dist/esm/lib/components/taxonomy-items-list/load-more-feedback/load-more-loading-row.js +2 -0
  27. package/dist/esm/lib/components/taxonomy-items-list/load-more-feedback/messages.js +20 -0
  28. package/dist/esm/lib/components/taxonomy-items-list/loading-state/index.js +2 -0
  29. package/dist/esm/lib/components/taxonomy-items-list/loading-state/loading-state.js +2 -0
  30. package/dist/esm/lib/components/taxonomy-items-list/messages.js +36 -0
  31. package/dist/esm/lib/components/taxonomy-items-list/taxonomy-items-list.js +2 -0
  32. package/dist/esm/lib/components/taxonomy-level-filter/index.js +2 -0
  33. package/dist/esm/lib/components/taxonomy-level-filter/messages.js +6 -0
  34. package/dist/esm/lib/components/taxonomy-level-filter/taxonomy-level-filter.js +2 -0
  35. package/dist/esm/lib/components/taxonomy-menu-item/index.js +2 -0
  36. package/dist/esm/lib/components/taxonomy-menu-item/messages.js +6 -0
  37. package/dist/esm/lib/components/taxonomy-menu-item/taxonomy-menu-item.js +2 -0
  38. package/dist/esm/lib/components/taxonomy-menu-item/use-taxonomy-menu-item-keyboard.js +100 -0
  39. package/dist/esm/lib/constants.js +2 -0
  40. package/dist/esm/lib/hooks/messages.js +16 -0
  41. package/dist/esm/lib/hooks/use-taxonomy-picker-controller.js +261 -0
  42. package/dist/esm/lib/metadata-taxonomy-picker.js +10 -0
  43. package/dist/esm/lib/state/reducer.js +147 -0
  44. package/dist/esm/lib/state/transitions.js +22 -0
  45. package/dist/i18n/bn-IN.js +24 -0
  46. package/dist/i18n/bn-IN.properties +44 -0
  47. package/dist/i18n/da-DK.js +24 -0
  48. package/dist/i18n/da-DK.properties +44 -0
  49. package/dist/i18n/de-DE.js +24 -0
  50. package/dist/i18n/de-DE.properties +44 -0
  51. package/dist/i18n/en-AU.js +24 -0
  52. package/dist/i18n/en-AU.properties +44 -0
  53. package/dist/i18n/en-CA.js +24 -0
  54. package/dist/i18n/en-CA.properties +44 -0
  55. package/dist/i18n/en-GB.js +24 -0
  56. package/dist/i18n/en-GB.properties +44 -0
  57. package/dist/i18n/en-US.js +24 -0
  58. package/dist/i18n/en-US.properties +44 -0
  59. package/dist/i18n/en-x-pseudo.js +24 -0
  60. package/dist/i18n/en-x-pseudo.properties +44 -0
  61. package/dist/i18n/es-419.js +24 -0
  62. package/dist/i18n/es-419.properties +44 -0
  63. package/dist/i18n/es-ES.js +24 -0
  64. package/dist/i18n/es-ES.properties +44 -0
  65. package/dist/i18n/fi-FI.js +24 -0
  66. package/dist/i18n/fi-FI.properties +44 -0
  67. package/dist/i18n/fr-CA.js +24 -0
  68. package/dist/i18n/fr-CA.properties +44 -0
  69. package/dist/i18n/fr-FR.js +24 -0
  70. package/dist/i18n/fr-FR.properties +44 -0
  71. package/dist/i18n/hi-IN.js +24 -0
  72. package/dist/i18n/hi-IN.properties +44 -0
  73. package/dist/i18n/it-IT.js +24 -0
  74. package/dist/i18n/it-IT.properties +44 -0
  75. package/dist/i18n/ja-JP.js +24 -0
  76. package/dist/i18n/ja-JP.properties +44 -0
  77. package/dist/i18n/json/src/lib/components/metadata-taxonomy-picker-shell/messages.json +1 -0
  78. package/dist/i18n/json/src/lib/components/taxonomy-items-list/load-more-feedback/messages.json +1 -0
  79. package/dist/i18n/json/src/lib/components/taxonomy-items-list/messages.json +1 -0
  80. package/dist/i18n/json/src/lib/components/taxonomy-level-filter/messages.json +1 -0
  81. package/dist/i18n/json/src/lib/components/taxonomy-menu-item/messages.json +1 -0
  82. package/dist/i18n/json/src/lib/hooks/messages.json +1 -0
  83. package/dist/i18n/ko-KR.js +24 -0
  84. package/dist/i18n/ko-KR.properties +44 -0
  85. package/dist/i18n/nb-NO.js +24 -0
  86. package/dist/i18n/nb-NO.properties +44 -0
  87. package/dist/i18n/nl-NL.js +24 -0
  88. package/dist/i18n/nl-NL.properties +44 -0
  89. package/dist/i18n/pl-PL.js +24 -0
  90. package/dist/i18n/pl-PL.properties +44 -0
  91. package/dist/i18n/pt-BR.js +24 -0
  92. package/dist/i18n/pt-BR.properties +44 -0
  93. package/dist/i18n/ru-RU.js +24 -0
  94. package/dist/i18n/ru-RU.properties +44 -0
  95. package/dist/i18n/sv-SE.js +24 -0
  96. package/dist/i18n/sv-SE.properties +44 -0
  97. package/dist/i18n/tr-TR.js +24 -0
  98. package/dist/i18n/tr-TR.properties +44 -0
  99. package/dist/i18n/zh-CN.js +24 -0
  100. package/dist/i18n/zh-CN.properties +44 -0
  101. package/dist/i18n/zh-TW.js +24 -0
  102. package/dist/i18n/zh-TW.properties +44 -0
  103. package/dist/styles/empty-state.css +1 -0
  104. package/dist/styles/error-state.css +1 -0
  105. package/dist/styles/load-more-error-row.css +1 -0
  106. package/dist/styles/load-more-loading-row.css +1 -0
  107. package/dist/styles/loading-state.css +1 -0
  108. package/dist/styles/metadata-taxonomy-picker-shell.css +1 -0
  109. package/dist/styles/taxonomy-ancestor-breadcrumb.css +1 -0
  110. package/dist/styles/taxonomy-items-list.css +1 -0
  111. package/dist/styles/taxonomy-level-filter.css +1 -0
  112. package/dist/styles/taxonomy-menu-item.css +1 -0
  113. package/dist/types/index.d.ts +4 -0
  114. package/dist/types/lib/components/metadata-taxonomy-picker-shell/index.d.ts +1 -0
  115. package/dist/types/lib/components/metadata-taxonomy-picker-shell/messages.d.ts +27 -0
  116. package/dist/types/lib/components/metadata-taxonomy-picker-shell/metadata-taxonomy-picker-shell.d.ts +2 -0
  117. package/dist/types/lib/components/taxonomy-ancestor-breadcrumb/index.d.ts +1 -0
  118. package/dist/types/lib/components/taxonomy-ancestor-breadcrumb/taxonomy-ancestor-breadcrumb.d.ts +16 -0
  119. package/dist/types/lib/components/taxonomy-items-list/empty-state/empty-state.d.ts +10 -0
  120. package/dist/types/lib/components/taxonomy-items-list/empty-state/index.d.ts +1 -0
  121. package/dist/types/lib/components/taxonomy-items-list/error-state/error-state.d.ts +9 -0
  122. package/dist/types/lib/components/taxonomy-items-list/error-state/index.d.ts +1 -0
  123. package/dist/types/lib/components/taxonomy-items-list/index.d.ts +5 -0
  124. package/dist/types/lib/components/taxonomy-items-list/load-more-feedback/index.d.ts +2 -0
  125. package/dist/types/lib/components/taxonomy-items-list/load-more-feedback/load-more-error-row.d.ts +10 -0
  126. package/dist/types/lib/components/taxonomy-items-list/load-more-feedback/load-more-loading-row.d.ts +7 -0
  127. package/dist/types/lib/components/taxonomy-items-list/load-more-feedback/messages.d.ts +22 -0
  128. package/dist/types/lib/components/taxonomy-items-list/loading-state/index.d.ts +1 -0
  129. package/dist/types/lib/components/taxonomy-items-list/loading-state/loading-state.d.ts +6 -0
  130. package/dist/types/lib/components/taxonomy-items-list/messages.d.ts +42 -0
  131. package/dist/types/lib/components/taxonomy-items-list/taxonomy-items-list.d.ts +82 -0
  132. package/dist/types/lib/components/taxonomy-level-filter/index.d.ts +1 -0
  133. package/dist/types/lib/components/taxonomy-level-filter/messages.d.ts +7 -0
  134. package/dist/types/lib/components/taxonomy-level-filter/taxonomy-level-filter.d.ts +11 -0
  135. package/dist/types/lib/components/taxonomy-menu-item/index.d.ts +1 -0
  136. package/dist/types/lib/components/taxonomy-menu-item/messages.d.ts +7 -0
  137. package/dist/types/lib/components/taxonomy-menu-item/taxonomy-menu-item.d.ts +20 -0
  138. package/dist/types/lib/components/taxonomy-menu-item/use-taxonomy-menu-item-keyboard.d.ts +47 -0
  139. package/dist/types/lib/constants.d.ts +49 -0
  140. package/dist/types/lib/hooks/messages.d.ts +17 -0
  141. package/dist/types/lib/hooks/use-taxonomy-picker-controller.d.ts +22 -0
  142. package/dist/types/lib/metadata-taxonomy-picker.d.ts +8 -0
  143. package/dist/types/lib/state/reducer.d.ts +180 -0
  144. package/dist/types/lib/state/transitions.d.ts +4 -0
  145. package/dist/types/lib/stories/metadata-taxonomy-picker.stories.d.ts +136 -0
  146. package/dist/types/lib/stories/shared/build-taxonomy-items.d.ts +10 -0
  147. package/dist/types/lib/stories/shared/create-mock-items-service.d.ts +35 -0
  148. package/dist/types/lib/stories/shared/generate-deep-nodes.d.ts +39 -0
  149. package/dist/types/lib/stories/shared/generate-hierarchical-nodes.d.ts +3 -0
  150. package/dist/types/lib/stories/shared/generate-nodes.d.ts +2 -0
  151. package/dist/types/lib/stories/shared/generate-search-results.d.ts +10 -0
  152. package/dist/types/lib/stories/shared/story-container.d.ts +5 -0
  153. package/dist/types/lib/stories/shared/taxonomy-items-list-fixtures.d.ts +7 -0
  154. package/dist/types/lib/stories/taxonomy-ancestor-breadcrumb.stories.d.ts +28 -0
  155. package/dist/types/lib/stories/taxonomy-items-list.stories.d.ts +77 -0
  156. package/dist/types/lib/stories/taxonomy-menu-item.stories.d.ts +67 -0
  157. package/dist/types/lib/stories/tests/breadcrumb-navigation.interaction-tests.stories.d.ts +78 -0
  158. package/dist/types/lib/stories/tests/error-handling.interaction-tests.stories.d.ts +50 -0
  159. package/dist/types/lib/stories/tests/interaction-tests.stories.d.ts +47 -0
  160. package/dist/types/lib/stories/tests/level-filter-interaction-tests.stories.d.ts +99 -0
  161. package/dist/types/lib/stories/tests/level-transitions.interaction-tests.stories.d.ts +60 -0
  162. package/dist/types/lib/stories/tests/pagination.interaction-tests.stories.d.ts +90 -0
  163. package/dist/types/lib/stories/tests/portal-and-ref.interaction-tests.stories.d.ts +39 -0
  164. package/dist/types/lib/stories/tests/search-interaction-tests.stories.d.ts +124 -0
  165. package/dist/types/lib/stories/tests/shell-selection.interaction-tests.stories.d.ts +56 -0
  166. package/dist/types/lib/stories/tests/single-level-mode.interaction-tests.stories.d.ts +50 -0
  167. package/dist/types/lib/stories/tests/taxonomy-menu-item.interaction-tests.stories.d.ts +70 -0
  168. package/dist/types/lib/stories/tests/visual-regression-tests.stories.d.ts +38 -0
  169. package/dist/types/lib/types.d.ts +105 -0
  170. package/package.json +51 -0
@@ -0,0 +1,82 @@
1
+ import { TaxonomyMenuItemProps } from '../taxonomy-menu-item';
2
+ export interface TaxonomyItemsListProps {
3
+ items: TaxonomyMenuItemProps[];
4
+ multiSelect: boolean;
5
+ selectedValue: string;
6
+ estimatedRowHeight?: number;
7
+ /**
8
+ * When `true`, each row is measured at runtime via `ResizeObserver` +
9
+ * `getBoundingClientRect()` so the virtualizer can position rows of
10
+ * varying height (e.g. search/ancestor rows whose breadcrumb may wrap).
11
+ *
12
+ * Browse rows are CSS-locked to a fixed height, so leaving this `false`
13
+ * (the default) avoids per-row layout reads during scroll.
14
+ */
15
+ hasVariableRowHeight?: boolean;
16
+ /** When `true`, the empty state renders "No results found" instead of "No subnodes". */
17
+ isSearchMode?: boolean;
18
+ isFetching: boolean;
19
+ /**
20
+ * Initial-load error for the list. When set, the entire list area is replaced
21
+ * by a full-area `ErrorState` with a retry button. Mutually exclusive with
22
+ * `items` — the controller clears items when this is set.
23
+ */
24
+ error: string | null;
25
+ /**
26
+ * Inline "load more" error for the active list. When set, an inline error row
27
+ * is rendered at the bottom of the existing items (already-loaded items remain
28
+ * visible) and the scroll-near-bottom auto-trigger is suppressed until the
29
+ * user retries.
30
+ */
31
+ loadMoreError?: string | null;
32
+ /**
33
+ * Called when the user activates the retry control on either error surface
34
+ * (full-area initial load error or the inline load-more error row). The
35
+ * controller decides which operation to re-run via `lastFailedOp`.
36
+ */
37
+ onRetry: () => void;
38
+ /**
39
+ * Applied as the `id` of the scroll container (`role="listbox"`), satisfying
40
+ * the `aria-controls` reference set on the combobox input in the shell.
41
+ * Must match the `listboxId` threaded down from `MetadataTaxonomyPickerShell`.
42
+ */
43
+ listboxId?: string;
44
+ /** Called when the user Shift+Tabs from the first row to return focus to the main input. */
45
+ onFocusExit?: () => void;
46
+ /**
47
+ * `true` when another page of items is available to be loaded.
48
+ * Required (alongside no in-flight fetch) for the scroll-near-bottom
49
+ * handler to fire `onLoadMore`.
50
+ */
51
+ hasMore?: boolean;
52
+ /** `true` while a "load more" page request is in flight (drives the inline ghost rows). */
53
+ isFetchingMore?: boolean;
54
+ /** Called when the user has scrolled within `LOAD_MORE_ROOT_MARGIN_PX` of the bottom of the list. */
55
+ onLoadMore?: () => void;
56
+ }
57
+ /** Imperative handle exposed via `ref` for scroll position save/restore. */
58
+ export interface TaxonomyItemsListRef {
59
+ getScrollTop: () => number;
60
+ scrollTo: (top: number) => void;
61
+ }
62
+ /**
63
+ * Virtualized list of taxonomy rows.
64
+ *
65
+ * Renders only the visible window of `TaxonomyMenuItem`s plus an overscan
66
+ * buffer, so very large datasets stay smooth. The list owns the scroll
67
+ * container and renders one of three non-list states (loading, error, empty)
68
+ * when applicable; otherwise it renders the virtualized rows.
69
+ *
70
+ * The scroll container carries `role="listbox"` (+ `id={listboxId}`) to
71
+ * satisfy the ARIA combobox contract — the shell's `aria-controls` points
72
+ * here. In single-select mode the `Radio.Group` lives inside the listbox
73
+ * (not around it) so the hierarchy is `listbox > radiogroup > options`.
74
+ *
75
+ * Browse rows are CSS-locked to a fixed height; search/ancestor rows can
76
+ * vary, in which case `hasVariableRowHeight` opts into per-row measurement.
77
+ *
78
+ * The component exposes a `TaxonomyItemsListRef` imperative handle for
79
+ * saving and restoring the scroll position when toggling between browse
80
+ * and search modes.
81
+ */
82
+ export declare const TaxonomyItemsList: import('react').ForwardRefExoticComponent<TaxonomyItemsListProps & import('react').RefAttributes<TaxonomyItemsListRef>>;
@@ -0,0 +1 @@
1
+ export { TaxonomyLevelFilter, type TaxonomyLevelFilterProps } from './taxonomy-level-filter';
@@ -0,0 +1,7 @@
1
+ export declare const messages: {
2
+ allOption: {
3
+ defaultMessage: string;
4
+ description: string;
5
+ id: string;
6
+ };
7
+ };
@@ -0,0 +1,11 @@
1
+ import { TaxonomyLevel } from '../../types';
2
+ export interface TaxonomyLevelFilterProps {
3
+ availableLevels: readonly TaxonomyLevel[];
4
+ levelFilter: number | null;
5
+ onLevelFilterChange: (level: number | null) => void;
6
+ }
7
+ /**
8
+ * Dropdown above the search results that scopes search to a single taxonomy
9
+ * level. "All levels" clears the filter.
10
+ */
11
+ export declare function TaxonomyLevelFilter({ availableLevels, levelFilter, onLevelFilterChange }: TaxonomyLevelFilterProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export { TaxonomyMenuItem, type TaxonomyMenuItemProps } from './taxonomy-menu-item';
@@ -0,0 +1,7 @@
1
+ export declare const messages: {
2
+ drillDownAriaLabel: {
3
+ defaultMessage: string;
4
+ description: string;
5
+ id: string;
6
+ };
7
+ };
@@ -0,0 +1,20 @@
1
+ import { TaxonomyAncestor } from '../../types';
2
+ export interface TaxonomyMenuItemProps {
3
+ id: string;
4
+ displayName: string;
5
+ isSearchMode?: boolean;
6
+ selectable: boolean;
7
+ multiSelect: boolean;
8
+ isSelected: boolean;
9
+ showDrillDown: boolean;
10
+ ancestors?: TaxonomyAncestor[];
11
+ onSelect: (id: string, closeAfterSelect?: boolean) => void;
12
+ onDrillDown?: (id: string) => void;
13
+ /** Called when Shift+Tab is pressed while this is the first row, to return focus to the main input. */
14
+ onFocusExit?: () => void;
15
+ }
16
+ /**
17
+ * Row component for browse and search lists.
18
+ * Keyboard model is documented in useTaxonomyMenuItemKeyboard.
19
+ */
20
+ export declare const TaxonomyMenuItem: import('react').NamedExoticComponent<TaxonomyMenuItemProps>;
@@ -0,0 +1,47 @@
1
+ import { KeyboardEvent } from 'react';
2
+ export interface UseTaxonomyMenuItemKeyboardOptions {
3
+ id: string;
4
+ multiSelect: boolean;
5
+ onSelect: (id: string, closeAfterSelect?: boolean) => void;
6
+ /** Called when focus exits the top of the list (e.g. Shift+Tab on the first row). */
7
+ onFocusExit?: () => void;
8
+ rowRef: React.RefObject<HTMLButtonElement>;
9
+ selectionControlRef: React.RefObject<HTMLButtonElement>;
10
+ drillDownRef: React.RefObject<HTMLButtonElement>;
11
+ }
12
+ export interface UseTaxonomyMenuItemKeyboardResult {
13
+ /** Redirect focus to the row when the radio receives it without our code asking. */
14
+ handleSelectionControlFocus: () => void;
15
+ /** Bubble-phase handler on the CompositeItem row div. */
16
+ handleRowKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void;
17
+ /**
18
+ * Capture-phase handler on the CompositeItem row div. Intercepts all arrow
19
+ * keys originating from the selection control before Radix's RadioGroup
20
+ * navigation (merged via Slot/asChild into the radio button's own onKeyDown)
21
+ * can process them.
22
+ */
23
+ handleRowKeyDownCapture: (event: KeyboardEvent<HTMLButtonElement>) => void;
24
+ /** Bubble-phase handler on the checkbox / radio button. */
25
+ handleSelectionControlKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void;
26
+ /** Bubble-phase handler on the drill-down IconButton. */
27
+ handleDrillDownKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void;
28
+ }
29
+ /**
30
+ * Encapsulates all keyboard (and radio-focus-redirect) logic for a single
31
+ * TaxonomyMenuItem row.
32
+ *
33
+ * Keyboard model (requires an ancestor CompositeProvider + Composite):
34
+ * - Tab / Shift+Tab anywhere in the row → next / previous row.
35
+ * At a list boundary Tab exits the popup naturally.
36
+ * - ArrowUp / ArrowDown anywhere in the row → next / previous row.
37
+ * - ArrowLeft from row → selection control.
38
+ * - ArrowRight from row → drill-down.
39
+ * - ArrowLeft from selection control → row.
40
+ * - ArrowRight from selection control → drill-down.
41
+ * - ArrowLeft from drill-down → selection control (or row if none).
42
+ * - Enter on radio → select (Radix blocks the native Enter event on buttons).
43
+ * - Enter / Space on drill-down → drill-down action.
44
+ * - Arrow keys from the selection control are intercepted in the capture phase
45
+ * so Radix's RovingFocusGroup never navigates between radios.
46
+ */
47
+ export declare function useTaxonomyMenuItemKeyboard({ id, multiSelect, onSelect, onFocusExit, rowRef, selectionControlRef, drillDownRef, }: UseTaxonomyMenuItemKeyboardOptions): UseTaxonomyMenuItemKeyboardResult;
@@ -0,0 +1,49 @@
1
+ /** Single-line browse rows — matches `--bp-size-090` (36px) in taxonomy-menu-item styles. */
2
+ export declare const ESTIMATED_BROWSE_ROW_HEIGHT = 36;
3
+ /**
4
+ * Two-line search/ancestor rows — display name + breadcrumb line.
5
+ * ~20px body-default-bold + ~16px xsmall breadcrumb + 2 × 4px vertical padding ≈ 44px.
6
+ */
7
+ export declare const ESTIMATED_SEARCH_ROW_HEIGHT = 44;
8
+ /**
9
+ * How many off-screen rows the virtualizer renders above and below the
10
+ * viewport. Higher values reduce the chance of fast scroll outrunning
11
+ * the renderer at the cost of slightly more DOM work; rows are memoized,
12
+ * so the trade-off favors a generous cushion.
13
+ */
14
+ export declare const OVERSCAN_COUNT = 20;
15
+ /** Skeleton rows shown during initial load. Sized to fill the stable 21rem popover area. */
16
+ export declare const SKELETON_ROW_COUNT = 9;
17
+ /** Height (px) of each skeleton row Ghost placeholder. */
18
+ export declare const SKELETON_ROW_HEIGHT = 16;
19
+ /** Number of ghost rows shown at the bottom of the list while a "load more" page is in flight. */
20
+ export declare const LOAD_MORE_GHOST_ROW_COUNT = 3;
21
+ /** Height (px) of each ghost row shown during "load more". */
22
+ export declare const LOAD_MORE_GHOST_ROW_HEIGHT = 16;
23
+ /** Total height (px) of the inline "load more" ghost block: padding(12×2) + 3×16 + 2×gap(20) = 112. */
24
+ export declare const LOAD_MORE_GHOST_BLOCK_HEIGHT = 112;
25
+ /** Distance (px) from the bottom of the list at which the next page is pre-fetched. */
26
+ export declare const LOAD_MORE_ROOT_MARGIN_PX = 200;
27
+ /** Minimum time the load-more loading slot stays visible before yielding to the inline error row. */
28
+ export declare const LOAD_MORE_ERROR_MIN_VISIBLE_MS = 400;
29
+ /** Separator string rendered between breadcrumb segments. */
30
+ export declare const BREADCRUMB_SEPARATOR = " / ";
31
+ /** Maximum number of lines the breadcrumb trail may occupy before collapsing to `first / icon / last`. */
32
+ export declare const BREADCRUMB_MAX_LINES = 2;
33
+ /**
34
+ * Sub-pixel tolerance for breadcrumb overflow detection.
35
+ * Browsers can report ~1px diffs even when content visually fits.
36
+ */
37
+ export declare const BREADCRUMB_OVERFLOW_THRESHOLD_PX = 2;
38
+ /** Unicode ellipsis character used when trimming overflowed breadcrumb segments. */
39
+ export declare const BREADCRUMB_ELLIPSIS_CHAR = "\u2026";
40
+ /** Default page size for taxonomy node fetch requests. */
41
+ export declare const DEFAULT_PAGE_LIMIT = 100;
42
+ /** Stale-after threshold for cached levels; older entries are evicted on read. */
43
+ export declare const CACHE_TTL_MS: number;
44
+ /** Cache key used for the root level (parentId === null). */
45
+ export declare const ROOT_CACHE_KEY = "__root__";
46
+ /** Sentinel `Crumb.id` for the synthetic root entry; mapped back to `null` parentId by the shell. */
47
+ export declare const BREADCRUMB_ROOT_ID = "__breadcrumb_root__";
48
+ /** Debounce time for search input changes. */
49
+ export declare const SEARCH_DEBOUNCE_MS = 500;
@@ -0,0 +1,17 @@
1
+ export declare const messages: {
2
+ initialLoadErrorFallback: {
3
+ defaultMessage: string;
4
+ description: string;
5
+ id: string;
6
+ };
7
+ searchErrorFallback: {
8
+ defaultMessage: string;
9
+ description: string;
10
+ id: string;
11
+ };
12
+ loadMoreErrorFallback: {
13
+ defaultMessage: string;
14
+ description: string;
15
+ id: string;
16
+ };
17
+ };
@@ -0,0 +1,22 @@
1
+ import { PickerState } from '../state/reducer';
2
+ import { DrillDownTarget, TaxonomyItemsService, TaxonomyLevel, TaxonomyPickerEventService } from '../types';
3
+ /** Forces a fixed search scope, bypassing the level-filter and drill-down scoping. */
4
+ export interface SearchScopeOverride {
5
+ parentId: string | null;
6
+ levelFilter: number | null;
7
+ }
8
+ export declare function useTaxonomyPickerController(itemsService: TaxonomyItemsService, levels?: readonly TaxonomyLevel[], eventService?: TaxonomyPickerEventService): {
9
+ state: PickerState;
10
+ isOpen: boolean;
11
+ handleOpenChange: (open: boolean) => void;
12
+ handleInputValueChange: (inputValue: string) => void;
13
+ actions: {
14
+ drillDown: (target: DrillDownTarget) => void;
15
+ navigateBack: (targetParentId: string | null) => void;
16
+ loadMore: () => Promise<void>;
17
+ searchLoadMore: () => Promise<void>;
18
+ setLevelFilter: (level: number | null) => void;
19
+ retry: () => void;
20
+ setSearchScopeOverride: (next: SearchScopeOverride | undefined) => void;
21
+ };
22
+ };
@@ -0,0 +1,8 @@
1
+ import { MetadataTaxonomyPickerProps } from './types';
2
+ export type { MetadataTaxonomyPickerProps } from './types';
3
+ /**
4
+ * Forwards the ref to the picker's trigger input so consumers can manage
5
+ * focus or other imperative interactions on the input element directly.
6
+ */
7
+ export declare const MetadataTaxonomyPicker: import('react').ForwardRefExoticComponent<MetadataTaxonomyPickerProps & import('react').RefAttributes<HTMLInputElement>>;
8
+ export default MetadataTaxonomyPicker;
@@ -0,0 +1,180 @@
1
+ import { BreadcrumbEntry, TaxonomyLevel, TaxonomyNode, TaxonomySearchResult } from '../types';
2
+ export declare enum FailedOp {
3
+ Browse = "browse",
4
+ BrowseMore = "browse-more",
5
+ Search = "search",
6
+ SearchMore = "search-more"
7
+ }
8
+ export declare enum PickerActionType {
9
+ /** Initial fetch for the current level started — shows the skeleton. */
10
+ FetchStart = "FETCH_START",
11
+ /** Initial fetch succeeded; carries `forParentId` so stale responses can be dropped. */
12
+ FetchSuccess = "FETCH_SUCCESS",
13
+ /** Initial fetch failed; sets `lastFailedOp = 'browse'` so retry knows what to re-run. */
14
+ FetchError = "FETCH_ERROR",
15
+ /** User navigated deeper into a node — atomically transitions to a new level. */
16
+ DrillDown = "DRILL_DOWN",
17
+ /** User navigated back to a level whose snapshot is still cached — no skeleton. */
18
+ NavigateBack = "NAVIGATE_BACK",
19
+ /** User navigated back to a level whose cache was evicted — re-fetch with skeleton. */
20
+ NavigateBackRefetch = "NAVIGATE_BACK_REFETCH",
21
+ /** Hard reset to `initialState` (e.g. popover closed). */
22
+ Reset = "RESET",
23
+ /** "Load more" page fetch for the current browse list started. */
24
+ LoadMoreStart = "LOAD_MORE_START",
25
+ /** "Load more" page appended to `items`; advances `itemsNextMarker`. */
26
+ LoadMoreSuccess = "LOAD_MORE_SUCCESS",
27
+ /**
28
+ * "Load more" page failed; sets `loadMoreError` so the inline error row is shown,
29
+ * and `lastFailedOp = 'browse-more'` so retry knows what to re-run from the
30
+ * current marker.
31
+ */
32
+ LoadMoreError = "LOAD_MORE_ERROR",
33
+ /** Search input changed — updates the raw `searchInputValue` on every keystroke. */
34
+ SearchInputChange = "SEARCH_INPUT_CHANGE",
35
+ /** Debounced search committed — promotes `searchInputValue` to `searchQuery` and fires the request. */
36
+ SearchFetchStart = "SEARCH_FETCH_START",
37
+ /** Search results arrived; carries `forQuery` so stale responses can be dropped. */
38
+ SearchFetchSuccess = "SEARCH_FETCH_SUCCESS",
39
+ /** Search request failed; sets `lastFailedOp = 'search'`. */
40
+ SearchFetchError = "SEARCH_FETCH_ERROR",
41
+ /** Search input cleared — wipes the search slice and returns to the browse view. */
42
+ SearchClear = "SEARCH_CLEAR",
43
+ /** "Load more" page fetch for the current search results started. */
44
+ SearchLoadMoreStart = "SEARCH_LOAD_MORE_START",
45
+ /** "Load more" page appended to `searchResults`; advances `searchNextMarker`. */
46
+ SearchLoadMoreSuccess = "SEARCH_LOAD_MORE_SUCCESS",
47
+ /**
48
+ * "Load more" page for search failed; sets `searchLoadMoreError` so the inline
49
+ * error row is shown and `lastFailedOp = 'search-more'` so retry re-runs the
50
+ * load-more from the current marker against the active query.
51
+ */
52
+ SearchLoadMoreError = "SEARCH_LOAD_MORE_ERROR",
53
+ /** User picked a level filter for the global (root-level) search. */
54
+ SetLevelFilter = "SET_LEVEL_FILTER"
55
+ }
56
+ export type PickerAction = {
57
+ type: PickerActionType.FetchStart;
58
+ } | {
59
+ type: PickerActionType.FetchSuccess;
60
+ entries: TaxonomyNode[];
61
+ nextMarker: string | undefined;
62
+ forParentId: string | null;
63
+ } | {
64
+ type: PickerActionType.FetchError;
65
+ error: string;
66
+ forParentId: string | null;
67
+ } | {
68
+ type: PickerActionType.DrillDown;
69
+ targetParentId: string;
70
+ levelStack: BreadcrumbEntry[];
71
+ } | {
72
+ type: PickerActionType.NavigateBack;
73
+ targetParentId: string | null;
74
+ levelStack: BreadcrumbEntry[];
75
+ cached: {
76
+ items: TaxonomyNode[];
77
+ nextMarker: string | undefined;
78
+ };
79
+ } | {
80
+ type: PickerActionType.NavigateBackRefetch;
81
+ targetParentId: string | null;
82
+ levelStack: BreadcrumbEntry[];
83
+ } | {
84
+ type: PickerActionType.Reset;
85
+ } | {
86
+ type: PickerActionType.LoadMoreStart;
87
+ } | {
88
+ type: PickerActionType.LoadMoreSuccess;
89
+ entries: TaxonomyNode[];
90
+ nextMarker: string | undefined;
91
+ } | {
92
+ type: PickerActionType.LoadMoreError;
93
+ error: string;
94
+ } | {
95
+ type: PickerActionType.SearchInputChange;
96
+ value: string;
97
+ availableLevels: readonly TaxonomyLevel[];
98
+ } | {
99
+ type: PickerActionType.SearchFetchStart;
100
+ query: string;
101
+ } | {
102
+ type: PickerActionType.SearchFetchSuccess;
103
+ results: TaxonomySearchResult[];
104
+ nextMarker: string | undefined;
105
+ forQuery: string;
106
+ } | {
107
+ type: PickerActionType.SearchFetchError;
108
+ error: string;
109
+ } | {
110
+ type: PickerActionType.SearchClear;
111
+ } | {
112
+ type: PickerActionType.SearchLoadMoreStart;
113
+ } | {
114
+ type: PickerActionType.SearchLoadMoreSuccess;
115
+ entries: TaxonomySearchResult[];
116
+ nextMarker: string | undefined;
117
+ forQuery: string;
118
+ } | {
119
+ type: PickerActionType.SearchLoadMoreError;
120
+ error: string;
121
+ forQuery: string;
122
+ } | {
123
+ type: PickerActionType.SetLevelFilter;
124
+ level: number | null;
125
+ };
126
+ export interface PickerState {
127
+ /** Breadcrumb trail of ancestors leading to `currentParentId`; empty at root. */
128
+ levelStack: BreadcrumbEntry[];
129
+ /** Parent node currently being browsed; `null` is the root level. */
130
+ currentParentId: string | null;
131
+ /** Children of `currentParentId` rendered in the browse list. */
132
+ items: TaxonomyNode[];
133
+ /** Pagination cursor for the next `items` page; `undefined` when no more pages. */
134
+ itemsNextMarker: string | undefined;
135
+ /** Initial fetch for the current level is in flight — drives the skeleton. */
136
+ isFetching: boolean;
137
+ /** "Load more" page for the current level is in flight — drives the inline spinner. */
138
+ isFetchingMore: boolean;
139
+ /** Initial fetch error for the current level; mutually exclusive with `items`. */
140
+ initialLoadError: string | null;
141
+ /**
142
+ * "Load more" page error for the current level. When set, an inline error row
143
+ * is rendered at the bottom of the existing list (already-loaded items remain
144
+ * visible). Cleared when a retry begins or the level changes.
145
+ */
146
+ loadMoreError: string | null;
147
+ /** Raw search input value — updated on every keystroke (pre-debounce). */
148
+ searchInputValue: string;
149
+ /** Debounced/committed query the active search request was fired against; used to race-guard `SearchFetchSuccess`. */
150
+ searchQuery: string;
151
+ /** Results for the active `searchQuery`. */
152
+ searchResults: TaxonomySearchResult[];
153
+ /** Pagination cursor for the next `searchResults` page. */
154
+ searchNextMarker: string | undefined;
155
+ /** Initial search fetch is in flight. */
156
+ isFetchingSearch: boolean;
157
+ /** "Load more" page for search results is in flight. */
158
+ isFetchingSearchMore: boolean;
159
+ /** Initial search request error; mutually exclusive with `searchResults`. */
160
+ searchError: string | null;
161
+ /**
162
+ * "Load more" page error for the active search query. When set, an inline
163
+ * error row is rendered at the bottom of the search results list. Cleared
164
+ * when a retry begins or the search query changes / clears.
165
+ */
166
+ searchLoadMoreError: string | null;
167
+ /** Optional level filter applied to the global (root-level) search; `null` means "all levels". */
168
+ levelFilter: number | null;
169
+ /** Levels available in the current taxonomy (drives the level-filter dropdown and single-level scoping). */
170
+ availableLevels: TaxonomyLevel[];
171
+ /**
172
+ * Which operation failed most recently, so `retry()` knows which one to re-run
173
+ * without having to infer it from other state. Cleared on success or when a
174
+ * new operation begins. Covers all four failure cases: initial browse, browse
175
+ * load-more, initial search, and search load-more.
176
+ */
177
+ lastFailedOp: FailedOp | null;
178
+ }
179
+ export declare const initialState: PickerState;
180
+ export declare function pickerReducer(state: PickerState, action: PickerAction): PickerState;
@@ -0,0 +1,4 @@
1
+ import { BreadcrumbEntry } from '../types';
2
+ import { PickerState } from './reducer';
3
+ /** Atomic move to a new level: empties items, shows skeleton, wipes search slice. */
4
+ export declare const transitionToLevel: (state: PickerState, targetParentId: string | null, levelStack: BreadcrumbEntry[]) => PickerState;
@@ -0,0 +1,136 @@
1
+ import { StoryObj } from '@storybook/react-vite';
2
+ import { MetadataTaxonomyPicker } from '../../index';
3
+ import { MetadataTaxonomyPickerProps, TaxonomyPickerMode, TaxonomyValue } from '../types';
4
+ declare const _default: {
5
+ title: string;
6
+ component: import('react').ForwardRefExoticComponent<MetadataTaxonomyPickerProps & import('react').RefAttributes<HTMLInputElement>>;
7
+ decorators: ((Story: import('storybook/internal/csf').PartialStoryFn<import('@storybook/react').ReactRenderer, {
8
+ mode: TaxonomyPickerMode;
9
+ multiSelect: boolean;
10
+ value: TaxonomyValue[];
11
+ onValueChange: (values: TaxonomyValue[]) => void;
12
+ itemsService: import('../types').TaxonomyItemsService;
13
+ eventService?: import('../types').TaxonomyPickerEventService | undefined;
14
+ levels?: import('../types').TaxonomyLevel[] | undefined;
15
+ placeholder?: string | undefined;
16
+ disabled?: boolean | undefined;
17
+ label?: string | undefined;
18
+ containerClassName?: string | undefined;
19
+ portalElement?: (HTMLElement | (() => HTMLElement | null) | null) | undefined;
20
+ ref?: import('react').LegacyRef<HTMLInputElement> | undefined;
21
+ key?: import('react').Key | null | undefined;
22
+ }>) => import("react/jsx-runtime").JSX.Element)[];
23
+ parameters: {
24
+ docs: {
25
+ description: {
26
+ component: string;
27
+ };
28
+ page: () => import("react/jsx-runtime").JSX.Element;
29
+ };
30
+ modernizedComponents: boolean;
31
+ };
32
+ };
33
+ export default _default;
34
+ type Story = StoryObj<typeof MetadataTaxonomyPicker>;
35
+ /** The popover shows the initial data when the initial fetch is successful. */
36
+ export declare const InitialData: Story;
37
+ /**
38
+ * The popover shows a skeleton loading state while the initial fetch is in progress.
39
+ */
40
+ export declare const LoadingState: Story;
41
+ /**
42
+ * The popover shows an empty state when there is no data after loading completes.
43
+ */
44
+ export declare const EmptyState: Story;
45
+ /** 1 000 items rendered in a virtualized list — validates performance at scale. */
46
+ export declare const LargeDataset: Story;
47
+ /** 1 000 items in multi-select mode — checkboxes with large dataset. */
48
+ export declare const LargeDatasetMultiSelect: Story;
49
+ /** Error state — the fetch fails and a retry button is shown. */
50
+ export declare const ErrorState: Story;
51
+ /**
52
+ * Disabled state — the trigger cannot be interacted with.
53
+ */
54
+ export declare const Disabled: Story;
55
+ /**
56
+ * No selection — only the placeholder text is visible inside the trigger.
57
+ */
58
+ export declare const NoSelection: Story;
59
+ /**
60
+ * A single chip is shown inside the trigger when one value is selected.
61
+ */
62
+ export declare const SingleChip: Story;
63
+ /**
64
+ * Multiple chips are shown when several values are selected.
65
+ */
66
+ export declare const MultipleChips: Story;
67
+ /**
68
+ * When many values are selected the chips wrap across multiple lines inside the trigger.
69
+ */
70
+ export declare const OverflowChips: Story;
71
+ /** Click a row's chevron (or press Right Arrow on a focused row) to drill in. */
72
+ export declare const DrillDownNavigation: Story;
73
+ /**
74
+ * Eight-level hierarchy (Region → Block) with four siblings per level. Drill all
75
+ * the way down to see the Blueprint Breadcrumb collapse the older ancestors into
76
+ * the folder-tree dropdown while keeping the last few levels and the current
77
+ * page visible.
78
+ */
79
+ export declare const DeepDrillDownNavigation: Story;
80
+ /**
81
+ * Six-level hierarchy with intentionally long display names, rendered in a
82
+ * narrow popover. Forces the folder-tree truncation to engage early so the
83
+ * breadcrumb dropdown trigger is visible even mid-way through the trail.
84
+ */
85
+ export declare const DeepDrillDownNavigationWithLongLabels: Story;
86
+ /**
87
+ * Picker with real search filtering: typing a substring shows all matching
88
+ * nodes with their ancestor breadcrumbs. Try `region`, `country`, `state`, or
89
+ * `01` to see results across all levels. Use the level filter dropdown to
90
+ * restrict results to a specific level.
91
+ */
92
+ export declare const WithSearch: Story;
93
+ /**
94
+ * Disabled state with selected values — chips are visible but not interactive (no dismiss button).
95
+ */
96
+ export declare const DisabledWithChips: Story;
97
+ /**
98
+ * Cursor-paginated browse list (450 items, page size 100). Scroll near the bottom
99
+ * to auto-fetch the next page: ghost rows appear while the request is in flight
100
+ * and new rows append below the already-loaded ones, preserving scroll position.
101
+ */
102
+ export declare const Pagination: Story;
103
+ /**
104
+ * Pagination + drill-down: scroll through several pages at the root, drill into
105
+ * a node, then navigate back. All pages loaded before drilling down are restored
106
+ * from cache, so the user lands back at the same scroll-loaded state without
107
+ * having to re-scroll or re-fetch.
108
+ */
109
+ export declare const PaginationWithDrillDownCacheRestore: Story;
110
+ /** Multi-level × multi-select: drill down at every level, pick many values across levels. */
111
+ export declare const MultiLevelMultiSelect: Story;
112
+ /** Multi-level × single-select: drill down, pick one value; popover closes on selection. */
113
+ export declare const MultiLevelSingleSelect: Story;
114
+ /**
115
+ * Single-level × multi-select with non-selectable parents. Drill through
116
+ * Country → State (no radios) to reach selectable Cities; search is locked
117
+ * to the City level regardless of browse position.
118
+ */
119
+ export declare const SingleLevelMultiSelectWithNonSelectableParents: Story;
120
+ /** Single-level × single-select with non-selectable parents — closes on City pick. */
121
+ export declare const SingleLevelSingleSelectWithNonSelectableParents: Story;
122
+ /**
123
+ * Browse load-more error: the first page loads successfully, but the next page
124
+ * (triggered by scrolling to the bottom) fails. Already-loaded items stay
125
+ * visible and an inline "Loading failed" row with a Reload action appears at
126
+ * the bottom of the list. Clicking Reload re-attempts from the current marker.
127
+ */
128
+ export declare const LoadMoreError: Story;
129
+ /**
130
+ * Search error: the initial search request fails, replacing the result area
131
+ * with the full-area error and retry button. Clicking retry re-runs the last
132
+ * committed query.
133
+ */
134
+ export declare const SearchErrorState: Story;
135
+ export declare const PortalElementInScrollableContainer: Story;
136
+ export declare const ForwardedRefFocus: Story;
@@ -0,0 +1,10 @@
1
+ import { TaxonomyMenuItemProps } from '../../components/taxonomy-menu-item';
2
+ interface BuildOptions extends Partial<TaxonomyMenuItemProps> {
3
+ onSelect: TaxonomyMenuItemProps['onSelect'];
4
+ onDrillDown: TaxonomyMenuItemProps['onDrillDown'];
5
+ }
6
+ /** Browse-mode items: single-line rows, no ancestors, drill-down enabled. */
7
+ export declare function buildBrowseItems(count: number, options: BuildOptions): TaxonomyMenuItemProps[];
8
+ /** Search-mode items: rows with ancestor breadcrumbs of varying depth. */
9
+ export declare function buildSearchItems(count: number, options: BuildOptions): TaxonomyMenuItemProps[];
10
+ export {};
@@ -0,0 +1,35 @@
1
+ import { TaxonomyItemsService, TaxonomyLevel, TaxonomyNode, TaxonomySearchResult } from '../../types';
2
+ declare const ROOT_KEY = "__root__";
3
+ export interface MockServiceConfig {
4
+ /** Flat fixture; ignored when `nodesByParent` is set. */
5
+ nodes?: TaxonomyNode[];
6
+ /** Children indexed by parent id; use `ROOT_KEY` for the root level. */
7
+ nodesByParent?: Record<string, TaxonomyNode[]>;
8
+ /**
9
+ * Pre-defined search results returned verbatim regardless of the query.
10
+ * Ignored when `filterSearchByQuery` is `true`.
11
+ */
12
+ searchResults?: TaxonomySearchResult[];
13
+ /**
14
+ * When `true` and `nodesByParent` is provided, `searchNodes` performs a
15
+ * real case-insensitive substring match against every node's display name
16
+ * and returns results with full ancestor chains — useful for interactive
17
+ * search stories.
18
+ */
19
+ filterSearchByQuery?: boolean;
20
+ /** Optional fixture levels; accepted for parity with the picker's `levels` prop. */
21
+ levels?: TaxonomyLevel[];
22
+ delay?: number;
23
+ /** Override the response delay for `searchNodes` only. Defaults to `delay`. */
24
+ searchDelay?: number;
25
+ shouldError?: boolean;
26
+ errorMessage?: string;
27
+ /**
28
+ * When set, `getNodes` / `searchNodes` honor `params.limit` + `params.marker`
29
+ * for true cursor pagination over the fixture. When unset, the full fixture
30
+ * is returned in one page (legacy behavior used by non-pagination stories).
31
+ */
32
+ paginate?: boolean;
33
+ }
34
+ export declare function createMockItemsService(config?: MockServiceConfig): TaxonomyItemsService;
35
+ export { ROOT_KEY };