@vectara/vectara-ui 16.3.1 → 16.4.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.
@@ -79,6 +79,8 @@ import { VuiStepsVertical, StepsVertical, StepVerticalStatus } from "./stepsVert
79
79
  import { SKELETON_COLOR, VuiSkeleton } from "./skeleton/Skeleton";
80
80
  import { VuiSummary, VuiSummaryCitation } from "./summary";
81
81
  import { VuiTable } from "./table/Table";
82
+ import { VuiSpans } from "./spans/Spans";
83
+ import { SpansRow } from "./spans/types";
82
84
  import { VuiTab } from "./tabs/Tab";
83
85
  import { VuiTabbedRoutes } from "./tabs/TabbedRoutes";
84
86
  import { VuiTabs } from "./tabs/Tabs";
@@ -95,5 +97,5 @@ import { VuiInfoTooltip } from "./tooltip/InfoTooltip";
95
97
  import { VuiTopicButton } from "./topicButton/TopicButton";
96
98
  import { copyToClipboard } from "../utils/copyToClipboard";
97
99
  import { toRgb, toRgba } from "./context/Theme";
98
- export type { AnchorSide, AppContentPadding, ButtonColor, CalloutColor, ChatLanguage, ChatStyle, ChatTurn, CheckboxConfig, CodeEditorColorConfig, CodeEditorError, CodeLanguage, InfoListItemType, InfoListType, InfoTableColumnAlign, InfoTableRow, InfoTableRowType, LinkProps, MenuItem, OptionListItem, Pagination, RadioButtonConfig, SearchResult, SearchSuggestion, Sections, SectionItem, Stat, StepStatus, StepSize, Steps, StepsVertical, StepVerticalStatus, TabSize, Tree, TreeItem };
99
- export { BADGE_COLOR, BUTTON_COLOR, BUTTON_SIZE, CALLOUT_COLOR, CALLOUT_SIZE, ICON_COLOR, ICON_SIZE, ICON_TYPE, PROGRESS_BAR_COLOR, SPACER_SIZE, SPINNER_COLOR, SKELETON_COLOR, SPINNER_SIZE, TAB_SIZE, TEXT_COLOR, TEXT_SIZE, TITLE_SIZE, addNotification, copyToClipboard, generateTokensProvider, toRgb, toRgba, VuiAccordion, VuiAccountButton, VuiAppContent, VuiAppHeader, VuiAppLayout, VuiAppSideNav, VuiAppSideNavLink, VuiAppSideNavGroup, VuiBadge, VuiButtonPrimary, VuiButtonSecondary, VuiButtonTertiary, VuiIconButton, VuiCallout, VuiCard, VuiChat, VuiCheckbox, VuiCode, VuiCodeEditor, VuiComplexConfigurationButton, VuiContextProvider, VuiCopyButton, VuiDatePicker, VuiDateRangePicker, VuiDrawer, VuiErrorBoundary, VuiFlexContainer, VuiFlexItem, VuiFormGroup, VuiGrid, VuiGridItem, VuiHorizontalRule, VuiIcon, VuiImage, VuiImagePreview, VuiInfoList, VuiInfoListItem, VuiInfoMenu, VuiInfoTable, VuiInfoTooltip, VuiInProgress, VuiItemsInput, VuiLabel, VuiLink, VuiLinkInternal, VuiList, VuiMenu, VuiMenuItem, VuiModal, VuiNotifications, VuiNumberInput, VuiOptionsButton, VuiOptionsList, VuiOptionsListItem, VuiPagination, VuiPanel, VuiPasswordInput, VuiPopover, VuiPortal, VuiProgressBar, VuiPrompt, VuiRadioButton, VuiScreenBlock, VuiSearchInput, VuiSearchResult, VuiSearchSelect, VuiSelect, VuiSetting, VuiSimpleCard, VuiSimpleGrid, VuiSpacer, VuiSpinner, VuiStat, VuiStatList, VuiStatus, VuiSteps, VuiStepsVertical, VuiSummary, VuiSkeleton, VuiSummaryCitation, VuiSuperCheckboxGroup, VuiSuperRadioGroup, VuiTable, VuiTab, VuiTabbedRoutes, VuiTabs, VuiTabsNavigator, VuiText, VuiTextArea, VuiTextColor, VuiTextInput, VuiTimeline, VuiTimelineItem, VuiTitle, VuiToggle, VuiTooltip, VuiTopicButton };
100
+ export type { AnchorSide, AppContentPadding, ButtonColor, CalloutColor, ChatLanguage, ChatStyle, ChatTurn, CheckboxConfig, CodeEditorColorConfig, CodeEditorError, CodeLanguage, InfoListItemType, InfoListType, InfoTableColumnAlign, InfoTableRow, InfoTableRowType, LinkProps, MenuItem, OptionListItem, Pagination, RadioButtonConfig, SearchResult, SearchSuggestion, Sections, SectionItem, SpansRow, Stat, StepStatus, StepSize, Steps, StepsVertical, StepVerticalStatus, TabSize, Tree, TreeItem };
101
+ export { BADGE_COLOR, BUTTON_COLOR, BUTTON_SIZE, CALLOUT_COLOR, CALLOUT_SIZE, ICON_COLOR, ICON_SIZE, ICON_TYPE, PROGRESS_BAR_COLOR, SPACER_SIZE, SPINNER_COLOR, SKELETON_COLOR, SPINNER_SIZE, TAB_SIZE, TEXT_COLOR, TEXT_SIZE, TITLE_SIZE, addNotification, copyToClipboard, generateTokensProvider, toRgb, toRgba, VuiAccordion, VuiAccountButton, VuiAppContent, VuiAppHeader, VuiAppLayout, VuiAppSideNav, VuiAppSideNavLink, VuiAppSideNavGroup, VuiBadge, VuiButtonPrimary, VuiButtonSecondary, VuiButtonTertiary, VuiIconButton, VuiCallout, VuiCard, VuiChat, VuiCheckbox, VuiCode, VuiCodeEditor, VuiComplexConfigurationButton, VuiContextProvider, VuiCopyButton, VuiDatePicker, VuiDateRangePicker, VuiDrawer, VuiErrorBoundary, VuiFlexContainer, VuiFlexItem, VuiFormGroup, VuiGrid, VuiGridItem, VuiHorizontalRule, VuiIcon, VuiImage, VuiImagePreview, VuiInfoList, VuiInfoListItem, VuiInfoMenu, VuiInfoTable, VuiInfoTooltip, VuiInProgress, VuiItemsInput, VuiLabel, VuiLink, VuiLinkInternal, VuiList, VuiMenu, VuiMenuItem, VuiModal, VuiNotifications, VuiNumberInput, VuiOptionsButton, VuiOptionsList, VuiOptionsListItem, VuiPagination, VuiPanel, VuiPasswordInput, VuiPopover, VuiPortal, VuiProgressBar, VuiPrompt, VuiRadioButton, VuiScreenBlock, VuiSearchInput, VuiSearchResult, VuiSearchSelect, VuiSelect, VuiSetting, VuiSimpleCard, VuiSimpleGrid, VuiSpacer, VuiSpans, VuiSpinner, VuiStat, VuiStatList, VuiStatus, VuiSteps, VuiStepsVertical, VuiSummary, VuiSkeleton, VuiSummaryCitation, VuiSuperCheckboxGroup, VuiSuperRadioGroup, VuiTable, VuiTab, VuiTabbedRoutes, VuiTabs, VuiTabsNavigator, VuiText, VuiTextArea, VuiTextColor, VuiTextInput, VuiTimeline, VuiTimelineItem, VuiTitle, VuiToggle, VuiTooltip, VuiTopicButton };
@@ -72,6 +72,7 @@ import { VuiStepsVertical } from "./stepsVertical/StepsVertical";
72
72
  import { SKELETON_COLOR, VuiSkeleton } from "./skeleton/Skeleton";
73
73
  import { VuiSummary, VuiSummaryCitation } from "./summary";
74
74
  import { VuiTable } from "./table/Table";
75
+ import { VuiSpans } from "./spans/Spans";
75
76
  import { VuiTab } from "./tabs/Tab";
76
77
  import { VuiTabbedRoutes } from "./tabs/TabbedRoutes";
77
78
  import { VuiTabs } from "./tabs/Tabs";
@@ -88,4 +89,4 @@ import { VuiInfoTooltip } from "./tooltip/InfoTooltip";
88
89
  import { VuiTopicButton } from "./topicButton/TopicButton";
89
90
  import { copyToClipboard } from "../utils/copyToClipboard";
90
91
  import { toRgb, toRgba } from "./context/Theme";
91
- export { BADGE_COLOR, BUTTON_COLOR, BUTTON_SIZE, CALLOUT_COLOR, CALLOUT_SIZE, ICON_COLOR, ICON_SIZE, ICON_TYPE, PROGRESS_BAR_COLOR, SPACER_SIZE, SPINNER_COLOR, SKELETON_COLOR, SPINNER_SIZE, TAB_SIZE, TEXT_COLOR, TEXT_SIZE, TITLE_SIZE, addNotification, copyToClipboard, generateTokensProvider, toRgb, toRgba, VuiAccordion, VuiAccountButton, VuiAppContent, VuiAppHeader, VuiAppLayout, VuiAppSideNav, VuiAppSideNavLink, VuiAppSideNavGroup, VuiBadge, VuiButtonPrimary, VuiButtonSecondary, VuiButtonTertiary, VuiIconButton, VuiCallout, VuiCard, VuiChat, VuiCheckbox, VuiCode, VuiCodeEditor, VuiComplexConfigurationButton, VuiContextProvider, VuiCopyButton, VuiDatePicker, VuiDateRangePicker, VuiDrawer, VuiErrorBoundary, VuiFlexContainer, VuiFlexItem, VuiFormGroup, VuiGrid, VuiGridItem, VuiHorizontalRule, VuiIcon, VuiImage, VuiImagePreview, VuiInfoList, VuiInfoListItem, VuiInfoMenu, VuiInfoTable, VuiInfoTooltip, VuiInProgress, VuiItemsInput, VuiLabel, VuiLink, VuiLinkInternal, VuiList, VuiMenu, VuiMenuItem, VuiModal, VuiNotifications, VuiNumberInput, VuiOptionsButton, VuiOptionsList, VuiOptionsListItem, VuiPagination, VuiPanel, VuiPasswordInput, VuiPopover, VuiPortal, VuiProgressBar, VuiPrompt, VuiRadioButton, VuiScreenBlock, VuiSearchInput, VuiSearchResult, VuiSearchSelect, VuiSelect, VuiSetting, VuiSimpleCard, VuiSimpleGrid, VuiSpacer, VuiSpinner, VuiStat, VuiStatList, VuiStatus, VuiSteps, VuiStepsVertical, VuiSummary, VuiSkeleton, VuiSummaryCitation, VuiSuperCheckboxGroup, VuiSuperRadioGroup, VuiTable, VuiTab, VuiTabbedRoutes, VuiTabs, VuiTabsNavigator, VuiText, VuiTextArea, VuiTextColor, VuiTextInput, VuiTimeline, VuiTimelineItem, VuiTitle, VuiToggle, VuiTooltip, VuiTopicButton };
92
+ export { BADGE_COLOR, BUTTON_COLOR, BUTTON_SIZE, CALLOUT_COLOR, CALLOUT_SIZE, ICON_COLOR, ICON_SIZE, ICON_TYPE, PROGRESS_BAR_COLOR, SPACER_SIZE, SPINNER_COLOR, SKELETON_COLOR, SPINNER_SIZE, TAB_SIZE, TEXT_COLOR, TEXT_SIZE, TITLE_SIZE, addNotification, copyToClipboard, generateTokensProvider, toRgb, toRgba, VuiAccordion, VuiAccountButton, VuiAppContent, VuiAppHeader, VuiAppLayout, VuiAppSideNav, VuiAppSideNavLink, VuiAppSideNavGroup, VuiBadge, VuiButtonPrimary, VuiButtonSecondary, VuiButtonTertiary, VuiIconButton, VuiCallout, VuiCard, VuiChat, VuiCheckbox, VuiCode, VuiCodeEditor, VuiComplexConfigurationButton, VuiContextProvider, VuiCopyButton, VuiDatePicker, VuiDateRangePicker, VuiDrawer, VuiErrorBoundary, VuiFlexContainer, VuiFlexItem, VuiFormGroup, VuiGrid, VuiGridItem, VuiHorizontalRule, VuiIcon, VuiImage, VuiImagePreview, VuiInfoList, VuiInfoListItem, VuiInfoMenu, VuiInfoTable, VuiInfoTooltip, VuiInProgress, VuiItemsInput, VuiLabel, VuiLink, VuiLinkInternal, VuiList, VuiMenu, VuiMenuItem, VuiModal, VuiNotifications, VuiNumberInput, VuiOptionsButton, VuiOptionsList, VuiOptionsListItem, VuiPagination, VuiPanel, VuiPasswordInput, VuiPopover, VuiPortal, VuiProgressBar, VuiPrompt, VuiRadioButton, VuiScreenBlock, VuiSearchInput, VuiSearchResult, VuiSearchSelect, VuiSelect, VuiSetting, VuiSimpleCard, VuiSimpleGrid, VuiSpacer, VuiSpans, VuiSpinner, VuiStat, VuiStatList, VuiStatus, VuiSteps, VuiStepsVertical, VuiSummary, VuiSkeleton, VuiSummaryCitation, VuiSuperCheckboxGroup, VuiSuperRadioGroup, VuiTable, VuiTab, VuiTabbedRoutes, VuiTabs, VuiTabsNavigator, VuiText, VuiTextArea, VuiTextColor, VuiTextInput, VuiTimeline, VuiTimelineItem, VuiTitle, VuiToggle, VuiTooltip, VuiTopicButton };
@@ -59,11 +59,20 @@ export const VuiModal = (_a) => {
59
59
  onClose === null || onClose === void 0 ? void 0 : onClose();
60
60
  }, 0);
61
61
  };
62
+ // Handle outside clicks, but ignore clicks on notifications.
63
+ const handleClickOutside = (event) => {
64
+ const target = event.target;
65
+ // Check if the click is within a notification.
66
+ if (target.closest("[data-awareness='notification']")) {
67
+ return;
68
+ }
69
+ onCloseDelayed();
70
+ };
62
71
  const containerClasses = classNames("vuiModalContainer", {
63
72
  "vuiModalContainer-isLoaded": showTransition
64
73
  });
65
74
  const classes = classNames("vuiModal", `vuiModal--${color}`, `vuiModal--${size}`, className);
66
- return (_jsx(VuiPortal, { children: (isOpen || isContentVisible || showTransition) && (_jsx(VuiScreenBlock, Object.assign({ type: "modal", isHidden: !isOpen }, { children: _jsx(FocusOn, Object.assign({ onEscapeKey: onCloseDelayed, onClickOutside: canClickOutsideToClose ? onCloseDelayed : undefined,
75
+ return (_jsx(VuiPortal, { children: (isOpen || isContentVisible || showTransition) && (_jsx(VuiScreenBlock, Object.assign({ type: "modal", isHidden: !isOpen }, { children: _jsx(FocusOn, Object.assign({ onEscapeKey: onCloseDelayed, onClickOutside: canClickOutsideToClose ? handleClickOutside : undefined,
67
76
  // Enable manual focus return to work.
68
77
  returnFocus: false,
69
78
  // Enable focus on contents when it's open,
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import { Column } from "../table/types";
3
+ import { SpansRow } from "./types";
4
+ type Props<T extends SpansRow> = {
5
+ idField: keyof T | ((row: T) => string);
6
+ parentField: keyof T | ((row: T) => string | null);
7
+ columns: Column<T>[];
8
+ rows: T[];
9
+ expandedIds: Set<string>;
10
+ onExpandedIdsChange: (ids: Set<string>) => void;
11
+ onExpand?: (row: T) => void | Promise<void>;
12
+ isLoadingChildren?: (row: T) => boolean;
13
+ isLoading?: boolean;
14
+ content?: React.ReactNode;
15
+ className?: string;
16
+ rowDecorator?: (row: T, depth: number) => React.HTMLAttributes<HTMLTableRowElement>;
17
+ indentSize?: number;
18
+ isHeaderSticky?: boolean;
19
+ fluid?: boolean;
20
+ "data-testid"?: string;
21
+ };
22
+ export declare const VuiSpans: <T extends SpansRow>({ idField, parentField, columns, rows, expandedIds, onExpandedIdsChange, onExpand, isLoadingChildren, isLoading, content, className, rowDecorator, indentSize, isHeaderSticky, fluid, "data-testid": dataTestId }: Props<T>) => import("react/jsx-runtime").JSX.Element;
23
+ export {};
@@ -0,0 +1,94 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useCallback, useMemo, useRef, useState } from "react";
3
+ import classNames from "classnames";
4
+ import { VuiFlexItem } from "../flex/FlexItem";
5
+ import { VuiSpinner } from "../spinner/Spinner";
6
+ import { VuiText } from "../typography/Text";
7
+ import { VuiTableContent } from "../table/TableContent";
8
+ import { buildAndFlattenSpans } from "./buildAndFlattenSpans";
9
+ import { VuiSpansRow } from "./SpansRow";
10
+ import { VuiSpansHeaderCell } from "./SpansHeaderCell";
11
+ import { VuiSpansLoadingRow } from "./SpansLoadingRow";
12
+ const DEFAULT_INDENT_SIZE = 16;
13
+ const extractId = (row, idField) => {
14
+ return typeof idField === "function" ? idField(row) : row[idField];
15
+ };
16
+ const extractParentId = (row, parentField) => {
17
+ if (typeof parentField === "function")
18
+ return parentField(row);
19
+ const value = row[parentField];
20
+ return value === undefined || value === null ? null : value;
21
+ };
22
+ export const VuiSpans = ({ idField, parentField, columns, rows, expandedIds, onExpandedIdsChange, onExpand, isLoadingChildren, isLoading, content, className, rowDecorator, indentSize = DEFAULT_INDENT_SIZE, isHeaderSticky, fluid, "data-testid": dataTestId }) => {
23
+ const [internalLoadingIds, setInternalLoadingIds] = useState(new Set());
24
+ // Tracks pending fetches so we ignore stale resolutions (e.g. user collapses
25
+ // mid-fetch, then re-expands — the original promise should not clear loading
26
+ // state for the new attempt).
27
+ const fetchTokensRef = useRef(new Map());
28
+ const getId = useCallback((row) => extractId(row, idField), [idField]);
29
+ const getParentId = useCallback((row) => extractParentId(row, parentField), [parentField]);
30
+ const flatSpans = useMemo(() => buildAndFlattenSpans(rows, expandedIds, getId, getParentId), [rows, expandedIds, getId, getParentId]);
31
+ const columnCount = columns.length;
32
+ const handleToggle = useCallback((row, id, hasChildren, hasLoadedChildren) => {
33
+ var _a;
34
+ const isCurrentlyExpanded = expandedIds.has(id);
35
+ const nextExpandedIds = new Set(expandedIds);
36
+ if (isCurrentlyExpanded) {
37
+ nextExpandedIds.delete(id);
38
+ onExpandedIdsChange(nextExpandedIds);
39
+ return;
40
+ }
41
+ nextExpandedIds.add(id);
42
+ onExpandedIdsChange(nextExpandedIds);
43
+ // Only fire the consumer fetch when the row claims to have children
44
+ // (`hasChildren`) but none are present in the rows list yet.
45
+ const needsFetch = hasChildren && !hasLoadedChildren && Boolean(onExpand);
46
+ if (!needsFetch)
47
+ return;
48
+ const pendingFetchTokens = fetchTokensRef.current;
49
+ const currentFetchToken = ((_a = pendingFetchTokens.get(id)) !== null && _a !== void 0 ? _a : 0) + 1;
50
+ pendingFetchTokens.set(id, currentFetchToken);
51
+ setInternalLoadingIds((prev) => {
52
+ const nextLoadingIds = new Set(prev);
53
+ nextLoadingIds.add(id);
54
+ return nextLoadingIds;
55
+ });
56
+ const clearLoading = () => {
57
+ // Only clear if our token is still the latest — otherwise a newer
58
+ // fetch is in flight and owns the loading state.
59
+ if (pendingFetchTokens.get(id) !== currentFetchToken)
60
+ return;
61
+ setInternalLoadingIds((prev) => {
62
+ const nextLoadingIds = new Set(prev);
63
+ nextLoadingIds.delete(id);
64
+ return nextLoadingIds;
65
+ });
66
+ };
67
+ Promise.resolve(onExpand(row)).finally(clearLoading);
68
+ }, [expandedIds, onExpandedIdsChange, onExpand]);
69
+ const classes = classNames("vuiSpans", className, {
70
+ "vuiSpans--fluid": fluid
71
+ });
72
+ let tbodyContent;
73
+ if (content) {
74
+ tbodyContent = _jsx(VuiTableContent, Object.assign({ colSpan: columnCount }, { children: content }));
75
+ }
76
+ else if (isLoading) {
77
+ tbodyContent = (_jsxs(VuiTableContent, Object.assign({ colSpan: columnCount }, { children: [_jsx(VuiFlexItem, Object.assign({ grow: false }, { children: _jsx(VuiSpinner, { size: "xs" }) })), _jsx(VuiFlexItem, Object.assign({ grow: false }, { children: _jsx(VuiText, { children: _jsx("p", { children: "Loading" }) }) }))] })));
78
+ }
79
+ else {
80
+ tbodyContent = flatSpans.map((flat, rowIndex) => {
81
+ const { row, id, depth, hasChildren, hasLoadedChildren, posInSet, setSize } = flat;
82
+ const isExpanded = expandedIds.has(id);
83
+ const externalLoading = isLoadingChildren ? isLoadingChildren(row) : false;
84
+ const isLoadingRow = internalLoadingIds.has(id) || externalLoading;
85
+ const showLoadingRow = isExpanded && hasChildren && !hasLoadedChildren && isLoadingRow;
86
+ return (_jsxs(React.Fragment, { children: [_jsx(VuiSpansRow, { row: row, rowIndex: rowIndex, columns: columns, id: id, depth: depth, indentSize: indentSize, posInSet: posInSet, setSize: setSize, hasChildren: hasChildren, isExpanded: isExpanded, onToggle: () => handleToggle(row, id, hasChildren, hasLoadedChildren), rowDecorator: rowDecorator }), showLoadingRow && (_jsx(VuiSpansLoadingRow, { colSpan: columnCount, depth: depth + 1, indentSize: indentSize }, `${id}__loading`))] }, id));
87
+ });
88
+ }
89
+ return (_jsx("div", Object.assign({ className: "vuiSpansWrapper", "data-testid": dataTestId }, { children: _jsxs("table", Object.assign({ className: classes, role: "treegrid" }, { children: [_jsx("thead", Object.assign({ className: isHeaderSticky ? "vuiSpansStickyHeader" : undefined }, { children: _jsx("tr", Object.assign({ role: "row" }, { children: columns.map((column) => {
90
+ const { name, width } = column;
91
+ const styles = width ? { width } : undefined;
92
+ return (_jsx("th", Object.assign({ role: "columnheader", style: styles }, { children: _jsx(VuiSpansHeaderCell, { column: column }) }), name));
93
+ }) })) })), _jsx("tbody", { children: tbodyContent })] })) })));
94
+ };
@@ -0,0 +1,5 @@
1
+ type Props = {
2
+ children: React.ReactNode;
3
+ };
4
+ export declare const VuiSpansCell: ({ children }: Props) => import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export const VuiSpansCell = ({ children }) => {
3
+ return _jsx("div", Object.assign({ className: "vuiSpansCell" }, { children: children }));
4
+ };
@@ -0,0 +1,6 @@
1
+ import { Column } from "../table/types";
2
+ type Props<T> = {
3
+ column: Column<T>;
4
+ };
5
+ export declare const VuiSpansHeaderCell: <T>({ column }: Props<T>) => import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export const VuiSpansHeaderCell = ({ column }) => {
3
+ const { name, header } = column;
4
+ return _jsx("div", Object.assign({ className: "vuiSpansHeaderCell" }, { children: header.render ? header.render() : name }));
5
+ };
@@ -0,0 +1,8 @@
1
+ type Props = {
2
+ colSpan: number;
3
+ depth: number;
4
+ indentSize: number;
5
+ message?: React.ReactNode;
6
+ };
7
+ export declare const VuiSpansLoadingRow: ({ colSpan, depth, indentSize, message }: Props) => import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { VuiFlexContainer } from "../flex/FlexContainer";
3
+ import { VuiFlexItem } from "../flex/FlexItem";
4
+ import { VuiSpinner } from "../spinner/Spinner";
5
+ import { VuiText } from "../typography/Text";
6
+ import { VuiTextColor } from "../typography/TextColor";
7
+ export const VuiSpansLoadingRow = ({ colSpan, depth, indentSize, message = "Loading…" }) => {
8
+ return (_jsx("tr", Object.assign({ className: "vuiSpansLoadingRow vuiSpansRow--inert" }, { children: _jsx("td", Object.assign({ colSpan: colSpan, className: "vuiSpansLoadingRow__cell" }, { children: _jsx("div", Object.assign({ className: "vuiSpansLoadingRow__inner", style: { paddingLeft: depth * indentSize } }, { children: _jsxs(VuiFlexContainer, Object.assign({ alignItems: "center", spacing: "xs" }, { children: [_jsx(VuiFlexItem, Object.assign({ grow: false }, { children: _jsx(VuiSpinner, { size: "xs" }) })), _jsx(VuiFlexItem, Object.assign({ grow: false }, { children: _jsx(VuiText, Object.assign({ size: "xs" }, { children: _jsx("p", { children: _jsx(VuiTextColor, Object.assign({ color: "subdued" }, { children: message })) }) })) }))] })) })) })) })));
9
+ };
@@ -0,0 +1,17 @@
1
+ import { Column, Row } from "../table/types";
2
+ type Props<T> = {
3
+ row: T;
4
+ rowIndex: number;
5
+ columns: Column<T>[];
6
+ id: string;
7
+ depth: number;
8
+ indentSize: number;
9
+ posInSet: number;
10
+ setSize: number;
11
+ hasChildren: boolean;
12
+ isExpanded: boolean;
13
+ onToggle: () => void;
14
+ rowDecorator?: (row: T, depth: number) => React.HTMLAttributes<HTMLTableRowElement>;
15
+ };
16
+ export declare const VuiSpansRow: <T extends Row>({ row, rowIndex, columns, id, depth, indentSize, posInSet, setSize, hasChildren, isExpanded, onToggle, rowDecorator }: Props<T>) => import("react/jsx-runtime").JSX.Element;
17
+ export {};
@@ -0,0 +1,43 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import classNames from "classnames";
14
+ import { BiChevronDown, BiChevronRight } from "react-icons/bi";
15
+ import { VuiIcon } from "../icon/Icon";
16
+ import { VuiIconButton } from "../button/IconButton";
17
+ import { VuiSpansCell } from "./SpansCell";
18
+ // Cap visual indentation at this depth so the first column doesn't overflow
19
+ // for very deep traces. `aria-level` continues to reflect the true depth.
20
+ const MAX_VISUAL_DEPTH = 12;
21
+ export const VuiSpansRow = ({ row, rowIndex, columns, id, depth, indentSize, posInSet, setSize, hasChildren, isExpanded, onToggle, rowDecorator }) => {
22
+ var _a;
23
+ const decoratorAttrs = (_a = rowDecorator === null || rowDecorator === void 0 ? void 0 : rowDecorator(row, depth)) !== null && _a !== void 0 ? _a : {};
24
+ const { className: decoratorClassName } = decoratorAttrs, restDecoratorAttrs = __rest(decoratorAttrs, ["className"]);
25
+ const rowClassName = classNames("vuiSpansRow", decoratorClassName, {
26
+ "vuiSpansRow-isExpanded": hasChildren && isExpanded,
27
+ "vuiSpansRow--leaf": !hasChildren
28
+ });
29
+ const visualDepth = Math.min(depth, MAX_VISUAL_DEPTH);
30
+ const indentStyle = { paddingLeft: visualDepth * indentSize };
31
+ return (_jsx("tr", Object.assign({ role: "row", "aria-level": depth + 1, "aria-posinset": posInSet, "aria-setsize": setSize, "aria-expanded": hasChildren ? isExpanded : undefined, "data-row-id": id, className: rowClassName }, restDecoratorAttrs, { children: columns.map((column, columnIndex) => {
32
+ const { name, render, className: columnClassName, testId } = column;
33
+ const cellClasses = classNames("vuiSpansCellWrapper", columnClassName, {
34
+ "vuiSpansCellWrapper--first": columnIndex === 0,
35
+ "vuiSpansCellWrapper--truncate": column.truncate
36
+ });
37
+ const cellContent = render ? render(row, rowIndex) : row[name];
38
+ if (columnIndex === 0) {
39
+ return (_jsx("td", Object.assign({ role: "gridcell", className: cellClasses, "data-testid": typeof testId === "function" ? testId(row) : testId }, { children: _jsxs("div", Object.assign({ className: "vuiSpansCell__indent", style: indentStyle }, { children: [_jsx("div", Object.assign({ className: "vuiSpansCell__chevron" }, { children: hasChildren ? (_jsx(VuiIconButton, { icon: _jsx(VuiIcon, { children: isExpanded ? _jsx(BiChevronDown, {}) : _jsx(BiChevronRight, {}) }), size: "xs", color: "neutral", "aria-label": isExpanded ? "Collapse span" : "Expand span", onClick: onToggle, "data-testid": `spanExpandToggle-${id}` })) : (_jsx("span", { className: "vuiSpansCell__chevronPlaceholder", "aria-hidden": "true" })) })), _jsx(VuiSpansCell, { children: cellContent })] })) }), name));
40
+ }
41
+ return (_jsx("td", Object.assign({ role: "gridcell", className: cellClasses, "data-testid": typeof testId === "function" ? testId(row) : testId }, { children: _jsx(VuiSpansCell, { children: cellContent }) }), name));
42
+ }) })));
43
+ };
@@ -0,0 +1,110 @@
1
+ $spansChevronWidth: 24px;
2
+
3
+ .vuiSpansWrapper {
4
+ width: 100%;
5
+ position: relative;
6
+ }
7
+
8
+ .vuiSpans {
9
+ width: 100%;
10
+ table-layout: fixed;
11
+
12
+ thead {
13
+ border-bottom: 1px solid var(--vui-color-border-medium);
14
+ }
15
+
16
+ tbody tr {
17
+ border-bottom: 1px solid var(--vui-color-border-light);
18
+
19
+ &:not(.vuiSpansRow--inert):hover {
20
+ background-color: rgba(var(--vui-color-light-shade-rgb), 0.25);
21
+ }
22
+
23
+ &:last-child {
24
+ border-bottom: 1px solid var(--vui-color-border-medium);
25
+ }
26
+ }
27
+
28
+ th {
29
+ font-size: $fontSizeStandard;
30
+ font-weight: $fontWeightBold;
31
+ padding: $sizeXxs;
32
+ text-align: left;
33
+ }
34
+
35
+ td {
36
+ font-size: $fontSizeStandard;
37
+ padding: $sizeXxs;
38
+ vertical-align: middle;
39
+ word-break: break-word;
40
+ }
41
+ }
42
+
43
+ .vuiSpans--fluid {
44
+ table-layout: auto;
45
+ }
46
+
47
+ // First column: depth-indent + chevron + content laid out in a row.
48
+ .vuiSpansCell__indent {
49
+ display: flex;
50
+ align-items: center;
51
+ gap: $sizeXxs;
52
+ // padding-left set inline based on depth.
53
+ }
54
+
55
+ .vuiSpansCell__chevron {
56
+ flex: 0 0 auto;
57
+ width: $spansChevronWidth;
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ }
62
+
63
+ .vuiSpansCell__chevronPlaceholder {
64
+ display: inline-block;
65
+ width: $spansChevronWidth;
66
+ height: 1px; // Reserves horizontal space; height irrelevant.
67
+ }
68
+
69
+ .vuiSpansCell {
70
+ display: flex;
71
+ align-items: center;
72
+ min-width: 0;
73
+ flex: 1 1 auto;
74
+ }
75
+
76
+ .vuiSpansCellWrapper--truncate {
77
+ min-width: 0;
78
+ overflow: hidden;
79
+ white-space: nowrap;
80
+ text-overflow: ellipsis;
81
+ }
82
+
83
+ .vuiSpansHeaderCell {
84
+ display: flex;
85
+ align-items: center;
86
+ padding: $sizeXxs;
87
+ }
88
+
89
+ // Sticky header — same treatment as VuiTable.
90
+ .vuiSpansStickyHeader {
91
+ position: sticky;
92
+ top: 0;
93
+ background-color: var(--vui-color-empty-shade);
94
+ z-index: 1;
95
+ }
96
+
97
+ // Loading row injected under a parent during lazy fetch.
98
+ .vuiSpansLoadingRow {
99
+ background-color: rgba(var(--vui-color-light-shade-rgb), 0.15);
100
+ }
101
+
102
+ .vuiSpansLoadingRow__cell {
103
+ padding: $sizeXxs !important;
104
+ }
105
+
106
+ .vuiSpansLoadingRow__inner {
107
+ display: flex;
108
+ align-items: center;
109
+ // padding-left set inline based on depth.
110
+ }
@@ -0,0 +1,14 @@
1
+ import { SpansRow } from "./types";
2
+ export type FlatSpan<T> = {
3
+ row: T;
4
+ id: string;
5
+ parentId: string | null;
6
+ depth: number;
7
+ hasChildren: boolean;
8
+ hasLoadedChildren: boolean;
9
+ posInSet: number;
10
+ setSize: number;
11
+ };
12
+ export type IdAccessor<T> = (row: T) => string;
13
+ export type ParentAccessor<T> = (row: T) => string | null;
14
+ export declare const buildAndFlattenSpans: <T extends SpansRow>(rows: T[], expandedIds: Set<string>, getId: IdAccessor<T>, getParentId: ParentAccessor<T>) => FlatSpan<T>[];
@@ -0,0 +1,69 @@
1
+ // Flattens a list of rows (linked by `parentId`) into a render-ordered, depth-tagged array,
2
+ // showing only visible nodes based on `expandedIds`. The render layer maps this 1:1 to <tr>s.
3
+ // Orphans (rows with missing parents) are dropped. Cycles are avoided using an ancestor set.
4
+ export const buildAndFlattenSpans = (rows, expandedIds, getId, getParentId) => {
5
+ // First pass: collect every known id so we can detect orphans below.
6
+ const allIds = new Set();
7
+ for (const row of rows) {
8
+ allIds.add(getId(row));
9
+ }
10
+ // Group rows by their parent id, with top-level rows under a sentinel key.
11
+ // The sentinel can't collide with a real id because it contains spaces.
12
+ const ROOT_KEY = " __VuiSpansRoot__ ";
13
+ const childrenByParent = new Map();
14
+ for (const row of rows) {
15
+ const parentId = getParentId(row);
16
+ // Drop orphans: parent id set but not present in the rows list.
17
+ if (parentId !== null && !allIds.has(parentId))
18
+ continue;
19
+ const key = parentId === null ? ROOT_KEY : parentId;
20
+ const list = childrenByParent.get(key);
21
+ if (list) {
22
+ list.push(row);
23
+ }
24
+ else {
25
+ childrenByParent.set(key, [row]);
26
+ }
27
+ }
28
+ const result = [];
29
+ // Recursive depth-first walk. `ancestors` carries the chain of ids from the
30
+ // root down to (but not including) the current row, used to detect cycles.
31
+ const walk = (parentKey, parentId, depth, ancestors) => {
32
+ const siblings = childrenByParent.get(parentKey);
33
+ if (!siblings)
34
+ return;
35
+ siblings.forEach((row, index) => {
36
+ const id = getId(row);
37
+ // Break cycle: this id appears earlier in its own ancestor chain.
38
+ if (ancestors.has(id))
39
+ return;
40
+ const ownChildren = childrenByParent.get(id);
41
+ const ownChildCount = ownChildren ? ownChildren.length : 0;
42
+ const hasLoadedChildren = ownChildCount > 0;
43
+ // The chevron should appear if either we already have children to show
44
+ // or the row is hinting that lazily-loadable children exist.
45
+ const hasChildren = row.hasChildren || hasLoadedChildren;
46
+ result.push({
47
+ row,
48
+ id,
49
+ parentId,
50
+ depth,
51
+ hasChildren,
52
+ hasLoadedChildren,
53
+ posInSet: index + 1,
54
+ setSize: siblings.length
55
+ });
56
+ // Only descend if the user has expanded this row AND there's something
57
+ // loaded to descend into. The lazy-only case (`hasChildren` but
58
+ // `!hasLoadedChildren`) is handled by the loading row in the render
59
+ // layer, not here.
60
+ if (hasChildren && expandedIds.has(id) && ownChildCount > 0) {
61
+ const nextAncestors = new Set(ancestors);
62
+ nextAncestors.add(id);
63
+ walk(id, id, depth + 1, nextAncestors);
64
+ }
65
+ });
66
+ };
67
+ walk(ROOT_KEY, null, 0, new Set());
68
+ return result;
69
+ };