@republicroad/seal-appshell 1.1.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.
- package/LICENSE +21 -0
- package/README.md +74 -0
- package/dist/assets/custom-node-schema.json.d.ts +549 -0
- package/dist/components/__tests__/skinned-decision-graph.test.d.ts +1 -0
- package/dist/components/__tests__/skinned-header.test.d.ts +1 -0
- package/dist/components/__tests__/skinned-panels.test.d.ts +1 -0
- package/dist/components/__tests__/skinned-simulator-panel.test.d.ts +1 -0
- package/dist/components/__tests__/skinned-simulator.test.d.ts +1 -0
- package/dist/components/custom-node/__tests__/custom-node-button.test.d.ts +1 -0
- package/dist/components/custom-node/__tests__/schema-container-tab.test.d.ts +1 -0
- package/dist/components/custom-node/crypto-node.d.ts +5 -0
- package/dist/components/custom-node/current-date-node.d.ts +8 -0
- package/dist/components/custom-node/http-request-node.d.ts +5 -0
- package/dist/components/custom-node/key-value-editor.d.ts +16 -0
- package/dist/components/custom-node/locked-corner-badge.d.ts +2 -0
- package/dist/components/custom-node/query-list-node.d.ts +5 -0
- package/dist/components/custom-node/schema-container-tab.d.ts +13 -0
- package/dist/components/icons/code.d.ts +3 -0
- package/dist/components/icons/flash-circle.d.ts +3 -0
- package/dist/components/reui/alert.d.ts +9 -0
- package/dist/components/reui/autocomplete.d.ts +40 -0
- package/dist/components/reui/badge.d.ts +14 -0
- package/dist/components/reui/cascader/cascader-async.d.ts +86 -0
- package/dist/components/reui/cascader/cascader-columns.d.ts +26 -0
- package/dist/components/reui/cascader/cascader-context.d.ts +190 -0
- package/dist/components/reui/cascader/cascader-footer.d.ts +83 -0
- package/dist/components/reui/cascader/cascader-i18n.d.ts +13 -0
- package/dist/components/reui/cascader/cascader-item.d.ts +112 -0
- package/dist/components/reui/cascader/cascader-lib.d.ts +137 -0
- package/dist/components/reui/cascader/cascader-nav.d.ts +56 -0
- package/dist/components/reui/cascader/cascader-types.d.ts +179 -0
- package/dist/components/reui/cascader/cascader-virtual.d.ts +62 -0
- package/dist/components/reui/cascader/cascader.d.ts +325 -0
- package/dist/components/shell-header.d.ts +20 -0
- package/dist/components/skinned-decision-graph.d.ts +19 -0
- package/dist/components/ui/alert-dialog.d.ts +20 -0
- package/dist/components/ui/avatar.d.ts +5 -0
- package/dist/components/ui/badge.d.ts +9 -0
- package/dist/components/ui/button.d.ts +11 -0
- package/dist/components/ui/dropdown-menu.d.ts +27 -0
- package/dist/components/ui/field.d.ts +24 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/label.d.ts +4 -0
- package/dist/components/ui/scroll-area.d.ts +4 -0
- package/dist/components/ui/select.d.ts +13 -0
- package/dist/components/ui/separator.d.ts +3 -0
- package/dist/components/ui/sheet.d.ts +23 -0
- package/dist/components/ui/sonner.d.ts +5 -0
- package/dist/components/ui/spinner.d.ts +2 -0
- package/dist/components/ui/switch.d.ts +3 -0
- package/dist/components/ui/tabs.d.ts +6 -0
- package/dist/components/ui/toggle-group.d.ts +13 -0
- package/dist/components/ui/toggle.d.ts +12 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/components/version-history/__tests__/version-history-panel.test.d.ts +1 -0
- package/dist/components/version-history/version-history-panel.d.ts +40 -0
- package/dist/context/theme.provider.d.ts +29 -0
- package/dist/hooks/useCustomNodes.d.ts +16 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +121959 -0
- package/dist/integration/decision-graph-appshell.stories.d.ts +10 -0
- package/dist/lib/__tests__/crypto-protocol.test.d.ts +1 -0
- package/dist/lib/__tests__/custom-node-registry.test.d.ts +1 -0
- package/dist/lib/__tests__/http-request-protocol.test.d.ts +1 -0
- package/dist/lib/__tests__/json-path-protocol.test.d.ts +1 -0
- package/dist/lib/__tests__/storage-key.test.d.ts +1 -0
- package/dist/lib/auth/__tests__/adapter.test.d.ts +1 -0
- package/dist/lib/auth/adapter.d.ts +7 -0
- package/dist/lib/auth-client.d.ts +3 -0
- package/dist/lib/crypto-protocol.d.ts +28 -0
- package/dist/lib/custom-node-plans.d.ts +24 -0
- package/dist/lib/custom-node-registry.d.ts +13 -0
- package/dist/lib/custom-node-schema-source.d.ts +6 -0
- package/dist/lib/custom-node-types.d.ts +73 -0
- package/dist/lib/http-request-protocol.d.ts +47 -0
- package/dist/lib/json-path-protocol.d.ts +11 -0
- package/dist/lib/roster-source.d.ts +12 -0
- package/dist/lib/storage-key.d.ts +5 -0
- package/dist/lib/user-resolver.d.ts +6 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/reui/icons/animated/duotone/lock.d.ts +3 -0
- package/dist/reui/icons/animated/outline/play-circle.d.ts +3 -0
- package/dist/reui/icons/default/outline/plus-circle.d.ts +3 -0
- package/dist/reui/icons/default/outline/shield-search.d.ts +3 -0
- package/dist/reui/icons/default/outline/trash-square.d.ts +3 -0
- package/dist/setupTests.d.ts +0 -0
- package/dist/shell/__tests__/default-simulate.test.d.ts +1 -0
- package/dist/shell/__tests__/graphs-http-adapter.test.d.ts +1 -0
- package/dist/shell/__tests__/indexed-db-adapter.test.d.ts +0 -0
- package/dist/shell/__tests__/restore.test.d.ts +0 -0
- package/dist/shell/default-simulate.d.ts +3 -0
- package/dist/shell/editor-shell.context.d.ts +22 -0
- package/dist/shell/execute-simulate.d.ts +8 -0
- package/dist/shell/graphs-http-adapter.d.ts +7 -0
- package/dist/shell/index.d.ts +10 -0
- package/dist/shell/indexed-db-adapter.d.ts +16 -0
- package/dist/shell/persistence.d.ts +89 -0
- package/dist/shell/restore.d.ts +21 -0
- package/dist/shell/types.d.ts +20 -0
- package/dist/skin/__tests__/apply.test.d.ts +1 -0
- package/dist/skin/apply.d.ts +7 -0
- package/dist/skin/layout.d.ts +17 -0
- package/dist/skin/types.d.ts +73 -0
- package/dist/style.css +2 -0
- package/package.json +73 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { CascaderCollapse, CascaderFlatNode, CascaderIndex, CascaderNode, CascaderPathSegment, CascaderSelectable } from '../../../components/reui/cascader/cascader-types';
|
|
2
|
+
/** Root level key in `childrenOf`. NUL-prefixed so no real value collides. */
|
|
3
|
+
export declare const CASCADER_ROOT_KEY = "\0root";
|
|
4
|
+
/**
|
|
5
|
+
* Paging pseudo-node prefix. Written as an escape sequence, not a raw 0x00
|
|
6
|
+
* byte: a literal NUL makes this module read as binary and `grep -I` skips it.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CASCADER_MORE_PREFIX = "\0more:";
|
|
9
|
+
/**
|
|
10
|
+
* The four scroll classes. In the lib because `cascader-columns.tsx` needs them
|
|
11
|
+
* too and must not import `cascader.tsx`. Shell > bound > scrollport > rows.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The bound. `min()` over an undefined custom property is an INVALID
|
|
15
|
+
* declaration that drops the whole max-height, hence the `100vh` fallback: an
|
|
16
|
+
* inline panel has no positioner, so no `--available-height`. `24rem` is the
|
|
17
|
+
* default cap, so the common case sets no variable at all.
|
|
18
|
+
*/
|
|
19
|
+
export declare const CASCADER_LIST_HEIGHT_CLASS = "max-h-[min(var(--available-height,100vh),var(--cascader-max-height,24rem))]";
|
|
20
|
+
/**
|
|
21
|
+
* Each style's list padding: rows take it as `padding`, the scrollport as
|
|
22
|
+
* `scroll-padding` floored at 4px so lyra's `0` does not strand a row.
|
|
23
|
+
* Mirrored per style from `registry/styles/style-*.css`; keep them in step.
|
|
24
|
+
*/
|
|
25
|
+
export declare const CASCADER_LIST_PAD_CLASS = "[--cascader-list-pad:4px]";
|
|
26
|
+
/** The scrollport: a `max-h-*` on the ScrollArea ROOT bounds nothing. */
|
|
27
|
+
export declare const CASCADER_SCROLL_CLASS = "size-full min-h-0 **:data-[slot=scroll-area-viewport]:h-full **:data-[slot=scroll-area-viewport]:overscroll-contain **:data-[slot=scroll-area-viewport]:scroll-py-[max(var(--cascader-list-pad,4px),4px)]";
|
|
28
|
+
/** The rows' box; `data-empty:p-0` keeps an empty state from double inset. */
|
|
29
|
+
export declare const CASCADER_ROWS_CLASS = "p-(--cascader-list-pad,4px) data-empty:p-0";
|
|
30
|
+
/** The footer's own stops; an `aria-disabled` command still counts as one. */
|
|
31
|
+
export declare function getCascaderFooterStops(scope: HTMLElement): HTMLElement[];
|
|
32
|
+
/** Where Tab lands, or `null` to let the browser out: focus is never trapped. */
|
|
33
|
+
export declare function getCascaderTabTarget(panel: HTMLElement, from: Element | null, backwards: boolean): HTMLElement | null;
|
|
34
|
+
/**
|
|
35
|
+
* The writing direction at `element`. `DirectionProvider` may only vote yes:
|
|
36
|
+
* unmounted it answers `"ltr"`, which would override a real `<html dir="rtl">`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isCascaderRtl(element: Element, provided: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The paging row as a REAL node, not a DOM-only row after the list: every index
|
|
41
|
+
* Base UI hands out indexes the RENDERED array, so a DOM-only row shifts them.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createCascaderMoreNode<T = unknown>(parentKey: string, loadedCount?: number): CascaderNode<T>;
|
|
44
|
+
export declare function isCascaderMoreNode<T>(node: CascaderNode<T>): boolean;
|
|
45
|
+
/** The level key a paging pseudo-node belongs to, or `null` for a real node. */
|
|
46
|
+
export declare function getCascaderMoreParent<T>(node: CascaderNode<T>): string | null;
|
|
47
|
+
/** Either input shape into one index. Cycle-guarded depths; first wins. */
|
|
48
|
+
export declare function buildCascaderIndex<T = unknown>(items: readonly CascaderNode<T>[] | undefined, getParent?: (node: CascaderNode<T>) => string | null | undefined): CascaderIndex<T>;
|
|
49
|
+
/**
|
|
50
|
+
* Folds loaded pages into an index built from `items`, so a re-render with a
|
|
51
|
+
* new `items` array keeps what the user drilled into. Static `items` wins;
|
|
52
|
+
* `detached` is `byValue` ONLY, or deep search doubles it and `childrenOf` lies.
|
|
53
|
+
*/
|
|
54
|
+
export declare function mergeCascaderIndex<T = unknown>(base: CascaderIndex<T>, pages: ReadonlyMap<string, readonly CascaderNode<T>[]>, detached?: ReadonlyMap<string, CascaderNode<T>>): CascaderIndex<T>;
|
|
55
|
+
/** Children of `parent`, or the root level when `parent` is nullish. */
|
|
56
|
+
export declare function getCascaderChildren<T>(index: CascaderIndex<T>, parent?: string | null): CascaderNode<T>[];
|
|
57
|
+
/** A branch has known children, or `hasChildren` for an unfetched level. */
|
|
58
|
+
export declare function isCascaderBranch<T>(index: CascaderIndex<T>, node: CascaderNode<T>): boolean;
|
|
59
|
+
/** Trailing count for the default row. Explicit `count` wins over the tree. */
|
|
60
|
+
export declare function getCascaderCount<T>(index: CascaderIndex<T>, node: CascaderNode<T>): number;
|
|
61
|
+
/** Whether a node may be committed as a selection. */
|
|
62
|
+
export declare function isCascaderSelectable<T>(index: CascaderIndex<T>, node: CascaderNode<T>, selectable: CascaderSelectable<T>): boolean;
|
|
63
|
+
/** Ancestor chain for `value`, root first. Empty while async data loads. */
|
|
64
|
+
export declare function getCascaderPath<T>(index: CascaderIndex<T>, value: string | null | undefined): CascaderNode<T>[];
|
|
65
|
+
/**
|
|
66
|
+
* `toLocaleLowerCase`, not `toLowerCase`: the invariant mapping turns Turkish
|
|
67
|
+
* "I" into "i" not "ı", so "ışık" would never match "IŞIK". Both sides fold here.
|
|
68
|
+
*/
|
|
69
|
+
export declare function foldCascaderText(text: string): string;
|
|
70
|
+
/** Folds once so callers can hoist the cost out of a per-node loop. */
|
|
71
|
+
export declare function normalizeCascaderQuery(query: string): string;
|
|
72
|
+
/** Case-insensitive substring over label and keywords; pre-fold the query. */
|
|
73
|
+
export declare function matchesCascaderQuery<T>(node: CascaderNode<T>, normalized: string): boolean;
|
|
74
|
+
/** Filters one level in place-order. Returns the input when the query is empty. */
|
|
75
|
+
export declare function filterCascaderLevel<T>(nodes: readonly CascaderNode<T>[], query: string, matches?: (node: CascaderNode<T>, normalized: string) => boolean): CascaderNode<T>[];
|
|
76
|
+
/** Searches every node, optionally under `within`, in one pass over `all`. */
|
|
77
|
+
export declare function searchCascaderDeep<T>(index: CascaderIndex<T>, query: string, options?: {
|
|
78
|
+
within?: string | null;
|
|
79
|
+
limit?: number;
|
|
80
|
+
matches?: (node: CascaderNode<T>, normalized: string) => boolean;
|
|
81
|
+
}): CascaderNode<T>[];
|
|
82
|
+
/**
|
|
83
|
+
* Flattens the tree into tree mode's visible rows. A `sentinels` paging row
|
|
84
|
+
* COUNTS as a sibling in `aria-setsize`: it holds an index of its own.
|
|
85
|
+
*/
|
|
86
|
+
export declare function flattenCascaderTree<T>(index: CascaderIndex<T>, expanded: ReadonlySet<string>, sentinels?: ReadonlySet<string>): CascaderFlatNode<T>[];
|
|
87
|
+
/** Shortens a path; the ellipsis segment still carries the hidden nodes. */
|
|
88
|
+
export declare function collapseCascaderPath<T>(path: readonly CascaderNode<T>[], options?: {
|
|
89
|
+
maxSegments?: number;
|
|
90
|
+
collapse?: CascaderCollapse;
|
|
91
|
+
}): CascaderPathSegment<T>[];
|
|
92
|
+
/**
|
|
93
|
+
* Every LOADED node under `value`, root first. Reaching an unfetched descendant
|
|
94
|
+
* would mean a network request per level of the subtree on every selection.
|
|
95
|
+
*/
|
|
96
|
+
export declare function collectCascaderSubtree<T>(index: CascaderIndex<T>, value: string): CascaderNode<T>[];
|
|
97
|
+
/**
|
|
98
|
+
* Toggles `value` with its loaded subtree, then reconciles ancestors. One flat
|
|
99
|
+
* array, no hidden "checked" state: a branch is in the selection exactly when
|
|
100
|
+
* every selectable loaded child of it is, which keeps a row's checked state an
|
|
101
|
+
* O(1) lookup. Nodes `isSelectable` rejects are skipped down AND ignored up,
|
|
102
|
+
* the pressed node excepted.
|
|
103
|
+
*/
|
|
104
|
+
export declare function applyCascadeSelection<T>(index: CascaderIndex<T>, selected: readonly string[], value: string, select: boolean, isSelectable?: (node: CascaderNode<T>) => boolean): string[];
|
|
105
|
+
/**
|
|
106
|
+
* How many selected nodes each value has BELOW it, itself excluded. Walks UP
|
|
107
|
+
* from each selection: a per-row subtree scan would be quadratic and re-paid.
|
|
108
|
+
*/
|
|
109
|
+
export declare function getCascaderSelectedDescendants<T>(index: CascaderIndex<T>, selected: readonly string[]): Map<string, number>;
|
|
110
|
+
/** The PARTIALLY selected values, read off counts the caller already holds. */
|
|
111
|
+
export declare function getCascaderIndeterminateFrom(counts: ReadonlyMap<string, number>, selected: readonly string[]): Set<string>;
|
|
112
|
+
/** The one-call form; the root uses the two halves above and its own counts. */
|
|
113
|
+
export declare function getCascaderIndeterminate<T>(index: CascaderIndex<T>, selected: readonly string[]): Set<string>;
|
|
114
|
+
/** How `getCascaderCheckedValues` condenses a full-closure selection. */
|
|
115
|
+
export type CascaderCheckedStrategy = 'all' | 'parent' | 'child';
|
|
116
|
+
/**
|
|
117
|
+
* The cascade selection under a reporting strategy. DERIVED OUTPUT ONLY: the
|
|
118
|
+
* STORED value stays the full closure, which keeps a checked state an O(1)
|
|
119
|
+
* lookup. `"all"` is the closure; `"parent"` drops a value whose parent is
|
|
120
|
+
* selected; `"child"` drops one with a selected child.
|
|
121
|
+
*/
|
|
122
|
+
export declare function getCascaderCheckedValues<T>(index: CascaderIndex<T>, selected: readonly string[], strategy: CascaderCheckedStrategy): readonly string[];
|
|
123
|
+
/** Values in `nodes` whose label collides, so the chip must show its path. */
|
|
124
|
+
export declare function findAmbiguousCascaderLabels<T>(nodes: readonly CascaderNode<T>[]): Set<string>;
|
|
125
|
+
/** Warns once per `key`, never in production, never by throwing. */
|
|
126
|
+
export declare function warnCascaderOnce(key: string, message: string): void;
|
|
127
|
+
/** Empties the ledger. Tests only; a warning is meant to be seen once. */
|
|
128
|
+
export declare function resetCascaderWarnings(): void;
|
|
129
|
+
/** What a dev-time scan of the consumer's `items` found wrong with it. */
|
|
130
|
+
export interface CascaderDataIssues {
|
|
131
|
+
/** Values appearing more than once. First wins, so a duplicate drops a row. */
|
|
132
|
+
duplicates: string[];
|
|
133
|
+
/** Values on a `getParent` cycle. Depth is clamped, so the tree reads wrong. */
|
|
134
|
+
cycles: string[];
|
|
135
|
+
}
|
|
136
|
+
/** Dev-time scan, kept out of `buildCascaderIndex` so the build stays hot. */
|
|
137
|
+
export declare function findCascaderDataIssues<T>(items: readonly CascaderNode<T>[] | undefined, getParent?: (node: CascaderNode<T>) => string | null | undefined): CascaderDataIssues;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Combobox as ComboboxPrimitive } from '@base-ui/react';
|
|
2
|
+
import { useRender } from '@base-ui/react/use-render';
|
|
3
|
+
import { CascaderCollapse, CascaderNode, CascaderValueDisplay } from '../../../components/reui/cascader/cascader-types';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
export type CascaderNavProps = useRender.ComponentProps<'div'>;
|
|
6
|
+
/**
|
|
7
|
+
* Header: back control, search input, and the separator under them. The
|
|
8
|
+
* breadcrumb belongs with the list below: it describes the rows, not the field.
|
|
9
|
+
*/
|
|
10
|
+
declare function CascaderNav({ className, ...props }: CascaderNavProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
|
11
|
+
export interface CascaderBackProps extends Omit<useRender.ComponentProps<'button'>, 'children'> {
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/** Pops one level. Renders nothing at the root, so no dead header space. */
|
|
15
|
+
declare function CascaderBack({ className, children, ...props }: CascaderBackProps): React.ReactElement<any, string | React.JSXElementConstructor<any>> | null;
|
|
16
|
+
export interface CascaderBreadcrumbProps extends Omit<useRender.ComponentProps<'nav'>, 'children'> {
|
|
17
|
+
/** Maximum visible node segments before the middle collapses. */
|
|
18
|
+
maxSegments?: number;
|
|
19
|
+
collapse?: CascaderCollapse;
|
|
20
|
+
/** Clicking a segment navigates back to that level. Defaults to true. */
|
|
21
|
+
interactive?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compact trail of the current level AND its ancestors, collapsed by the same
|
|
25
|
+
* helper as `CascaderValue` so panel and trigger cannot disagree. The current
|
|
26
|
+
* level IS the last crumb: dropping it read as a rendering bug, a trail naming
|
|
27
|
+
* only the places you are not.
|
|
28
|
+
*/
|
|
29
|
+
declare function CascaderBreadcrumb({ className, maxSegments, collapse, interactive, ...props }: CascaderBreadcrumbProps): React.ReactElement<any, string | React.JSXElementConstructor<any>> | null;
|
|
30
|
+
export interface CascaderInputProps extends ComboboxPrimitive.Input.Props {
|
|
31
|
+
/** Renders the back control inline, before the field. Defaults to true. */
|
|
32
|
+
showBack?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Search field. MUST render inside the positioner: only there does Base UI skip
|
|
36
|
+
* the refill from the committed selection that would fight every level swap.
|
|
37
|
+
*/
|
|
38
|
+
declare function CascaderInput({ className, showBack, placeholder, onKeyDown, 'aria-describedby': ariaDescribedBy, ...props }: CascaderInputProps): React.JSX.Element;
|
|
39
|
+
export interface CascaderValueProps extends Omit<useRender.ComponentProps<'span'>, 'children'> {
|
|
40
|
+
/** `path` shows the full trail, `leaf` only the node, `count` only a total. */
|
|
41
|
+
display?: CascaderValueDisplay;
|
|
42
|
+
maxSegments?: number;
|
|
43
|
+
collapse?: CascaderCollapse;
|
|
44
|
+
separator?: React.ReactNode;
|
|
45
|
+
/** Renders the selected node's icon before the trail. */
|
|
46
|
+
showIcon?: boolean;
|
|
47
|
+
placeholder?: React.ReactNode;
|
|
48
|
+
/** Replaces the whole rendering. Receives the resolved selection and path. */
|
|
49
|
+
children?: (selected: CascaderNode[], path: CascaderNode[]) => React.ReactNode;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Trigger display. Defaults to the collapsed selection path rather than a bare
|
|
53
|
+
* leaf label, which in a nested picker is frequently ambiguous.
|
|
54
|
+
*/
|
|
55
|
+
declare function CascaderValue({ className, display, maxSegments, collapse, separator, showIcon, placeholder, children, ...props }: CascaderValueProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
|
56
|
+
export { CascaderNav, CascaderBack, CascaderBreadcrumb, CascaderInput, CascaderValue };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* A node in the cascader tree. Two input shapes normalize to one internal
|
|
4
|
+
* index: nested (nodes carry `children`), or flat adjacency plus the root's
|
|
5
|
+
* `getParent`, which skips the re-nesting step for large datasets.
|
|
6
|
+
*/
|
|
7
|
+
export interface CascaderNode<T = unknown> {
|
|
8
|
+
/** Stable id. Also the committed selection value. */
|
|
9
|
+
value: string;
|
|
10
|
+
label: string;
|
|
11
|
+
icon?: React.ReactNode;
|
|
12
|
+
description?: string;
|
|
13
|
+
children?: CascaderNode<T>[];
|
|
14
|
+
/** Declares a branch before load; async nodes read as leaves without it. */
|
|
15
|
+
hasChildren?: boolean;
|
|
16
|
+
/** Trailing count. Defaults to known children; set it for async nodes. */
|
|
17
|
+
count?: number;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
keywords?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Only read on a node nested inside a `getChildren` result. For the level a
|
|
22
|
+
* node OWNS the authoritative signal is that level's `CascaderLoadResult`.
|
|
23
|
+
*/
|
|
24
|
+
hasMore?: boolean;
|
|
25
|
+
data?: T;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* One footer ACTION. Deliberately not a `CascaderNode`, so a command can never
|
|
29
|
+
* be passed where an option is expected and join the ring, filter or selection.
|
|
30
|
+
*/
|
|
31
|
+
export interface CascaderActionItem {
|
|
32
|
+
/** Stable key. Falls back to the label when it is a string, then the index. */
|
|
33
|
+
value?: string;
|
|
34
|
+
label: React.ReactNode;
|
|
35
|
+
icon?: React.ReactNode;
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
/** Ignored when `items` is present - a flyout opens instead. */
|
|
38
|
+
onSelect?: () => void;
|
|
39
|
+
/** Turns the row into a submenu trigger. One level deep on purpose. */
|
|
40
|
+
items?: CascaderActionItem[];
|
|
41
|
+
/** Consecutive entries sharing a heading are drawn under one. */
|
|
42
|
+
group?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Panel layout. See the docs for the keyboard map of each. */
|
|
45
|
+
export type CascaderMode = 'drill' | 'columns' | 'tree';
|
|
46
|
+
export type CascaderSearchScope = 'level' | 'deep';
|
|
47
|
+
/**
|
|
48
|
+
* Which nodes may be committed. The predicate arm is generic over the payload
|
|
49
|
+
* rather than quantified per call: a per-call `<T>` would force every predicate
|
|
50
|
+
* to accept EVERY payload, so `(node: CascaderNode<Member>) => boolean` could
|
|
51
|
+
* never satisfy it.
|
|
52
|
+
*/
|
|
53
|
+
export type CascaderSelectable<T = unknown> = 'leaf' | 'any' | ((node: CascaderNode<T>) => boolean);
|
|
54
|
+
export type CascaderCollapse = 'middle' | 'start' | 'none';
|
|
55
|
+
export type CascaderValueDisplay = 'path' | 'leaf' | 'count';
|
|
56
|
+
export type CascaderChangeReason = 'select' | 'deselect' | 'clear';
|
|
57
|
+
/** Second argument to `onValueChange`: resolved nodes, so no re-lookup. */
|
|
58
|
+
export interface CascaderChangeDetails<T = unknown> {
|
|
59
|
+
/** The node committed or toggled. Null when the selection was cleared. */
|
|
60
|
+
node: CascaderNode<T> | null;
|
|
61
|
+
/** Ancestor chain of `node`, root first, node last. */
|
|
62
|
+
path: CascaderNode<T>[];
|
|
63
|
+
nodes: CascaderNode<T>[];
|
|
64
|
+
reason: CascaderChangeReason;
|
|
65
|
+
}
|
|
66
|
+
/** Normalized tree, built once per `items` identity, shared by every mode. */
|
|
67
|
+
export interface CascaderIndex<T = unknown> {
|
|
68
|
+
byValue: Map<string, CascaderNode<T>>;
|
|
69
|
+
/** Children by parent value. Root children are keyed by `ROOT_KEY`. */
|
|
70
|
+
childrenOf: Map<string, CascaderNode<T>[]>;
|
|
71
|
+
parentOf: Map<string, string | null>;
|
|
72
|
+
/** Zero-based depth by node value. */
|
|
73
|
+
depthOf: Map<string, number>;
|
|
74
|
+
/** Top level nodes, in input order. */
|
|
75
|
+
roots: CascaderNode<T>[];
|
|
76
|
+
/** Every node in a stable, depth-first order. Used by deep search. */
|
|
77
|
+
all: CascaderNode<T>[];
|
|
78
|
+
}
|
|
79
|
+
export interface CascaderFlatNode<T = unknown> {
|
|
80
|
+
node: CascaderNode<T>;
|
|
81
|
+
depth: number;
|
|
82
|
+
/** Known children, or a declared `hasChildren`. */
|
|
83
|
+
branch: boolean;
|
|
84
|
+
expanded: boolean;
|
|
85
|
+
/** Sibling count, plus one when the level has a paging row. */
|
|
86
|
+
setSize: number;
|
|
87
|
+
/** One-based index among siblings. */
|
|
88
|
+
posInSet: number;
|
|
89
|
+
}
|
|
90
|
+
/** One segment of a rendered path, after collapsing. */
|
|
91
|
+
export type CascaderPathSegment<T = unknown> = {
|
|
92
|
+
type: 'node';
|
|
93
|
+
node: CascaderNode<T>;
|
|
94
|
+
} | {
|
|
95
|
+
type: 'ellipsis';
|
|
96
|
+
hidden: CascaderNode<T>[];
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Async load state for one node's children. Deliberately WITHOUT a `status`
|
|
100
|
+
* field: "declared a branch, never fetched" and "fetched and genuinely empty"
|
|
101
|
+
* are told apart by MAP MEMBERSHIP, and a second source of that truth would
|
|
102
|
+
* eventually disagree with the first.
|
|
103
|
+
*/
|
|
104
|
+
export interface CascaderLoadState {
|
|
105
|
+
loading: boolean;
|
|
106
|
+
error: boolean;
|
|
107
|
+
hasMore: boolean;
|
|
108
|
+
/** Opaque cursor handed back to `getChildren` for the next page. */
|
|
109
|
+
cursor?: string;
|
|
110
|
+
}
|
|
111
|
+
/** Why a level was fetched. `resolve`: `resolveValue` hit an unloaded node. */
|
|
112
|
+
export type CascaderLoadReason = 'level' | 'more' | 'prefetch' | 'retry' | 'resolve';
|
|
113
|
+
/** Argument handed to `getChildren`. */
|
|
114
|
+
export interface CascaderLoadContext {
|
|
115
|
+
/** Aborted when the request is superseded or the popup closes. */
|
|
116
|
+
signal: AbortSignal;
|
|
117
|
+
cursor?: string;
|
|
118
|
+
reason?: CascaderLoadReason;
|
|
119
|
+
}
|
|
120
|
+
/** Value returned by `getChildren`. A bare array is also accepted. */
|
|
121
|
+
export interface CascaderLoadResult<T = unknown> {
|
|
122
|
+
items: CascaderNode<T>[];
|
|
123
|
+
nextCursor?: string;
|
|
124
|
+
/** Defaults to whether `nextCursor` was supplied. */
|
|
125
|
+
hasMore?: boolean;
|
|
126
|
+
}
|
|
127
|
+
/** Argument handed to `onSearch`. */
|
|
128
|
+
export interface CascaderSearchContext {
|
|
129
|
+
signal: AbortSignal;
|
|
130
|
+
/** The path the user is searching within, deepest last. */
|
|
131
|
+
path: string[];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Every user facing string, so the primitive ships no hardcoded copy. The
|
|
135
|
+
* callbacks take plain labels, not nodes: a `CascaderNode<T>` parameter would
|
|
136
|
+
* force this object to carry the item generic. `*Announcement` is live-region.
|
|
137
|
+
*/
|
|
138
|
+
export interface CascaderLabels {
|
|
139
|
+
search: string | ((parentLabel?: string) => string);
|
|
140
|
+
back: string;
|
|
141
|
+
/** A level's FIRST page. `loadingMore` is the next, `loadMore` its idle row. */
|
|
142
|
+
loading: string;
|
|
143
|
+
loadingMore: string;
|
|
144
|
+
loadMore: string;
|
|
145
|
+
error: string;
|
|
146
|
+
retry: string;
|
|
147
|
+
empty: string;
|
|
148
|
+
/** Rendered by `CascaderValue` when `display="count"`. */
|
|
149
|
+
selectedCount: (count: number) => string;
|
|
150
|
+
breadcrumbLabel: string;
|
|
151
|
+
chipsLabel: string;
|
|
152
|
+
removeChip: (label: string) => string;
|
|
153
|
+
/** Trail separator. Not every locale writes one with a slash. */
|
|
154
|
+
pathSeparator: string;
|
|
155
|
+
/** Names the root level wherever there is no parent node to name it. */
|
|
156
|
+
rootLevel: string;
|
|
157
|
+
itemCount: (count: number) => string;
|
|
158
|
+
/** Appended to a branch row's name outside tree mode: it opens another list. */
|
|
159
|
+
branchAffordance: string;
|
|
160
|
+
/** Columns-trail rows are plain buttons, so they carry no `aria-selected`. */
|
|
161
|
+
selectedState: string;
|
|
162
|
+
/** Same for the mixed state, which a `role="button"` row may not carry. */
|
|
163
|
+
partiallySelectedState: string;
|
|
164
|
+
columnsLabel: string;
|
|
165
|
+
actionsLabel: string;
|
|
166
|
+
submenuAffordance: string;
|
|
167
|
+
panelLabel: string;
|
|
168
|
+
/** Read per mode AND per direction: the level keys mirror in RTL. */
|
|
169
|
+
keyboardHint: (mode: CascaderMode, dir: 'ltr' | 'rtl') => string;
|
|
170
|
+
rootAnnouncement: (count: number) => string;
|
|
171
|
+
expandedAnnouncement: (label: string, count: number) => string;
|
|
172
|
+
collapsedAnnouncement: (label: string) => string;
|
|
173
|
+
levelAnnouncement: (parentLabel: string, depth: number, count: number) => string;
|
|
174
|
+
resultsAnnouncement: (count: number) => string;
|
|
175
|
+
maxReachedAnnouncement: (max: number) => string;
|
|
176
|
+
/** `count` is the descendants swept along; `selecting` is the direction. */
|
|
177
|
+
cascadeAnnouncement: (label: string, count: number, selecting: boolean) => string;
|
|
178
|
+
searchingAnnouncement: string;
|
|
179
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Virtualizer } from '@tanstack/react-virtual';
|
|
2
|
+
import { CascaderColumn } from '../../../components/reui/cascader/cascader-context';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Windowing for the cascader, in its own file so the primitive's own install
|
|
6
|
+
* never pulls `@tanstack/react-virtual` in. Base UI forces three rules:
|
|
7
|
+
*
|
|
8
|
+
* 1. A windowed row MUST carry an explicit `index`; the fallback
|
|
9
|
+
* `findItemIndex` is O(n) per row and returns `-1` on the frame a level swap
|
|
10
|
+
* renders empty, dangling `aria-activedescendant`.
|
|
11
|
+
* 2. That index is DESTRUCTIVE while unvirtualized (`useCompositeListItem`
|
|
12
|
+
* skips registration, `CompositeList` truncates `elementsRef` to zero and
|
|
13
|
+
* the first arrow key highlights nothing), so rows stay plain until the root
|
|
14
|
+
* flips `virtualized`, gated inside `CascaderItem`.
|
|
15
|
+
* 3. No `Combobox.Collection` and no FUNCTION CHILD on `Combobox.List`, which
|
|
16
|
+
* implicitly wraps one in a Collection: a Collection renders every filtered
|
|
17
|
+
* item.
|
|
18
|
+
*/
|
|
19
|
+
export interface UseCascaderVirtualizerOptions {
|
|
20
|
+
count: number;
|
|
21
|
+
getScrollElement: () => HTMLElement | null;
|
|
22
|
+
estimateSize?: number;
|
|
23
|
+
overscan?: number;
|
|
24
|
+
/** Key rows by node value, never by index: a tree expand shifts every index. */
|
|
25
|
+
getItemKey: (index: number) => string | number;
|
|
26
|
+
/** Render index of the highlighted row, or `-1`. Pinned into the window. */
|
|
27
|
+
activeIndex?: number;
|
|
28
|
+
}
|
|
29
|
+
export type CascaderVirtualizer = Virtualizer<HTMLElement, HTMLElement>;
|
|
30
|
+
/**
|
|
31
|
+
* The cascader's virtualizer: TanStack plus the highlight pinning and
|
|
32
|
+
* scroll-into-view a combobox list needs. `activeIndex` is clamped here, not by
|
|
33
|
+
* the caller: emitted from a layout effect, it can be one commit ahead of a
|
|
34
|
+
* level that just shrank and would otherwise scroll to a row that is gone.
|
|
35
|
+
*/
|
|
36
|
+
export declare function useCascaderVirtualizer({ count, getScrollElement, estimateSize, overscan, getItemKey, activeIndex, }: UseCascaderVirtualizerOptions): CascaderVirtualizer;
|
|
37
|
+
export interface CascaderVirtualItemsProps {
|
|
38
|
+
/** Row height before measurement. Defaults to the root `estimateRowSize`. */
|
|
39
|
+
estimateSize?: number;
|
|
40
|
+
/** Rows rendered beyond each edge. Defaults to the root `overscan`. */
|
|
41
|
+
overscan?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Windowed replacement for `CascaderItems`, dropped inside `CascaderList`. The
|
|
45
|
+
* registration is a LAYOUT effect because the root derives `virtualized` from
|
|
46
|
+
* it and the flip has to land before the browser paints. Until the root has
|
|
47
|
+
* seen it, this renders exactly what `CascaderItems` does, so no frame carries
|
|
48
|
+
* indexed rows while Base UI still owns the composite list.
|
|
49
|
+
*/
|
|
50
|
+
declare function CascaderVirtualItems(props: CascaderVirtualItemsProps): React.JSX.Element;
|
|
51
|
+
export interface CascaderVirtualColumnProps extends CascaderVirtualItemsProps {
|
|
52
|
+
column: CascaderColumn;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Windowed replacement for one Miller column, passed through the
|
|
56
|
+
* `CascaderColumns` render slot. One virtualizer PER COLUMN, since columns
|
|
57
|
+
* scroll independently, and only the deepest is Base UI's listbox: the trail
|
|
58
|
+
* behind is `as="button"` rows outside Base UI, so it windows on its own row
|
|
59
|
+
* count rather than waiting for the root to flip.
|
|
60
|
+
*/
|
|
61
|
+
declare function CascaderVirtualColumn({ column, estimateSize, overscan }: CascaderVirtualColumnProps): React.JSX.Element;
|
|
62
|
+
export { CascaderVirtualColumn, CascaderVirtualItems };
|