@dalcvil/steam-green-react 0.2.0 → 1.0.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/README.md +20 -5
- package/dist/components/Avatar/Avatar.css +136 -0
- package/dist/components/Avatar/Avatar.d.ts +22 -0
- package/dist/components/Avatar/Avatar.d.ts.map +1 -0
- package/dist/components/Avatar/Avatar.js +49 -0
- package/dist/components/Avatar/Avatar.js.map +1 -0
- package/dist/components/Avatar/index.d.ts +3 -0
- package/dist/components/Avatar/index.d.ts.map +1 -0
- package/dist/components/Button/Button.css +117 -0
- package/dist/components/Button/Button.d.ts +18 -0
- package/dist/components/Button/Button.d.ts.map +1 -0
- package/dist/components/Button/Button.js +29 -0
- package/dist/components/Button/Button.js.map +1 -0
- package/dist/components/Button/index.d.ts +3 -0
- package/dist/components/Button/index.d.ts.map +1 -0
- package/dist/components/Checkbox/Checkbox.css +116 -0
- package/dist/components/Checkbox/Checkbox.css.d.ts +1 -0
- package/dist/components/Checkbox/Checkbox.d.ts +19 -0
- package/dist/components/Checkbox/Checkbox.d.ts.map +1 -0
- package/dist/components/Checkbox/Checkbox.js +51 -0
- package/dist/components/Checkbox/Checkbox.js.map +1 -0
- package/dist/components/Checkbox/index.d.ts +3 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -0
- package/dist/components/ColorPicker/ColorPicker.css +331 -0
- package/dist/components/ColorPicker/ColorPicker.css.d.ts +1 -0
- package/dist/components/ColorPicker/ColorPicker.d.ts +29 -0
- package/dist/components/ColorPicker/ColorPicker.d.ts.map +1 -0
- package/dist/components/ColorPicker/ColorPicker.js +534 -0
- package/dist/components/ColorPicker/ColorPicker.js.map +1 -0
- package/dist/components/ColorPicker/index.d.ts +3 -0
- package/dist/components/{GreenButton → ColorPicker}/index.d.ts.map +1 -1
- package/dist/components/Console/Console.css +96 -0
- package/dist/components/Console/Console.css.d.ts +1 -0
- package/dist/components/Console/Console.d.ts +26 -0
- package/dist/components/Console/Console.d.ts.map +1 -0
- package/dist/components/Console/Console.js +59 -0
- package/dist/components/Console/Console.js.map +1 -0
- package/dist/components/Console/index.d.ts +3 -0
- package/dist/components/Console/index.d.ts.map +1 -0
- package/dist/components/Dialog/Dialog.css +137 -0
- package/dist/components/Dialog/Dialog.css.d.ts +1 -0
- package/dist/components/Dialog/Dialog.d.ts +28 -0
- package/dist/components/Dialog/Dialog.d.ts.map +1 -0
- package/dist/components/Dialog/Dialog.js +151 -0
- package/dist/components/Dialog/Dialog.js.map +1 -0
- package/dist/components/Dialog/index.d.ts +3 -0
- package/dist/components/Dialog/index.d.ts.map +1 -0
- package/dist/components/Divider/Divider.css +68 -0
- package/dist/components/Divider/Divider.css.d.ts +1 -0
- package/dist/components/Divider/Divider.d.ts +12 -0
- package/dist/components/Divider/Divider.d.ts.map +1 -0
- package/dist/components/Divider/Divider.js +23 -0
- package/dist/components/Divider/Divider.js.map +1 -0
- package/dist/components/Divider/index.d.ts +3 -0
- package/dist/components/Divider/index.d.ts.map +1 -0
- package/dist/components/FieldLabel/FieldLabel.css +98 -0
- package/dist/components/FieldLabel/FieldLabel.css.d.ts +1 -0
- package/dist/components/FieldLabel/FieldLabel.d.ts +29 -0
- package/dist/components/FieldLabel/FieldLabel.d.ts.map +1 -0
- package/dist/components/FieldLabel/FieldLabel.js +42 -0
- package/dist/components/FieldLabel/FieldLabel.js.map +1 -0
- package/dist/components/FieldLabel/index.d.ts +3 -0
- package/dist/components/{GreenPanel → FieldLabel}/index.d.ts.map +1 -1
- package/dist/components/GroupBox/GroupBox.css +101 -0
- package/dist/components/GroupBox/GroupBox.css.d.ts +1 -0
- package/dist/components/GroupBox/GroupBox.d.ts +14 -0
- package/dist/components/GroupBox/GroupBox.d.ts.map +1 -0
- package/dist/components/GroupBox/GroupBox.js +25 -0
- package/dist/components/GroupBox/GroupBox.js.map +1 -0
- package/dist/components/GroupBox/index.d.ts +3 -0
- package/dist/components/GroupBox/index.d.ts.map +1 -0
- package/dist/components/IconButton/IconButton.css +184 -0
- package/dist/components/IconButton/IconButton.css.d.ts +1 -0
- package/dist/components/IconButton/IconButton.d.ts +56 -0
- package/dist/components/IconButton/IconButton.d.ts.map +1 -0
- package/dist/components/IconButton/IconButton.js +77 -0
- package/dist/components/IconButton/IconButton.js.map +1 -0
- package/dist/components/IconButton/index.d.ts +3 -0
- package/dist/components/IconButton/index.d.ts.map +1 -0
- package/dist/components/LevelMeter/LevelMeter.css +117 -0
- package/dist/components/LevelMeter/LevelMeter.css.d.ts +1 -0
- package/dist/components/LevelMeter/LevelMeter.d.ts +20 -0
- package/dist/components/LevelMeter/LevelMeter.d.ts.map +1 -0
- package/dist/components/LevelMeter/LevelMeter.js +44 -0
- package/dist/components/LevelMeter/LevelMeter.js.map +1 -0
- package/dist/components/LevelMeter/index.d.ts +3 -0
- package/dist/components/LevelMeter/index.d.ts.map +1 -0
- package/dist/components/Link/Link.css +127 -0
- package/dist/components/Link/Link.css.d.ts +1 -0
- package/dist/components/Link/Link.d.ts +26 -0
- package/dist/components/Link/Link.d.ts.map +1 -0
- package/dist/components/Link/Link.js +60 -0
- package/dist/components/Link/Link.js.map +1 -0
- package/dist/components/Link/index.d.ts +3 -0
- package/dist/components/Link/index.d.ts.map +1 -0
- package/dist/components/List/List.css +224 -0
- package/dist/components/List/List.css.d.ts +1 -0
- package/dist/components/List/List.d.ts +49 -0
- package/dist/components/List/List.d.ts.map +1 -0
- package/dist/components/List/List.js +300 -0
- package/dist/components/List/List.js.map +1 -0
- package/dist/components/List/index.d.ts +3 -0
- package/dist/components/List/index.d.ts.map +1 -0
- package/dist/components/Menu/Menu.css +122 -0
- package/dist/components/Menu/Menu.css.d.ts +1 -0
- package/dist/components/Menu/Menu.d.ts +46 -0
- package/dist/components/Menu/Menu.d.ts.map +1 -0
- package/dist/components/Menu/Menu.js +246 -0
- package/dist/components/Menu/Menu.js.map +1 -0
- package/dist/components/Menu/index.d.ts +3 -0
- package/dist/components/Menu/index.d.ts.map +1 -0
- package/dist/components/MenuBar/MenuBar.css +94 -0
- package/dist/components/MenuBar/MenuBar.css.d.ts +1 -0
- package/dist/components/MenuBar/MenuBar.d.ts +34 -0
- package/dist/components/MenuBar/MenuBar.d.ts.map +1 -0
- package/dist/components/MenuBar/MenuBar.js +129 -0
- package/dist/components/MenuBar/MenuBar.js.map +1 -0
- package/dist/components/MenuBar/index.d.ts +3 -0
- package/dist/components/MenuBar/index.d.ts.map +1 -0
- package/dist/components/NavBar/NavBar.css +179 -0
- package/dist/components/NavBar/NavBar.css.d.ts +1 -0
- package/dist/components/NavBar/NavBar.d.ts +39 -0
- package/dist/components/NavBar/NavBar.d.ts.map +1 -0
- package/dist/components/NavBar/NavBar.js +198 -0
- package/dist/components/NavBar/NavBar.js.map +1 -0
- package/dist/components/NavBar/index.d.ts +3 -0
- package/dist/components/NavBar/index.d.ts.map +1 -0
- package/dist/components/Notification/Notification.css +176 -0
- package/dist/components/Notification/Notification.css.d.ts +1 -0
- package/dist/components/Notification/Notification.d.ts +26 -0
- package/dist/components/Notification/Notification.d.ts.map +1 -0
- package/dist/components/Notification/Notification.js +181 -0
- package/dist/components/Notification/Notification.js.map +1 -0
- package/dist/components/Notification/index.d.ts +3 -0
- package/dist/components/Notification/index.d.ts.map +1 -0
- package/dist/components/Panel/Panel.css +56 -0
- package/dist/components/Panel/Panel.css.d.ts +1 -0
- package/dist/components/Panel/Panel.d.ts +14 -0
- package/dist/components/Panel/Panel.d.ts.map +1 -0
- package/dist/components/Panel/Panel.js +42 -0
- package/dist/components/Panel/Panel.js.map +1 -0
- package/dist/components/Panel/index.d.ts +3 -0
- package/dist/components/Panel/index.d.ts.map +1 -0
- package/dist/components/ProgressBar/ProgressBar.css +108 -0
- package/dist/components/ProgressBar/ProgressBar.css.d.ts +1 -0
- package/dist/components/ProgressBar/ProgressBar.d.ts +22 -0
- package/dist/components/ProgressBar/ProgressBar.d.ts.map +1 -0
- package/dist/components/ProgressBar/ProgressBar.js +43 -0
- package/dist/components/ProgressBar/ProgressBar.js.map +1 -0
- package/dist/components/ProgressBar/index.d.ts +3 -0
- package/dist/components/ProgressBar/index.d.ts.map +1 -0
- package/dist/components/Radio/Radio.css +125 -0
- package/dist/components/Radio/Radio.css.d.ts +1 -0
- package/dist/components/Radio/Radio.d.ts +40 -0
- package/dist/components/Radio/Radio.d.ts.map +1 -0
- package/dist/components/Radio/Radio.js +91 -0
- package/dist/components/Radio/Radio.js.map +1 -0
- package/dist/components/Radio/index.d.ts +3 -0
- package/dist/components/Radio/index.d.ts.map +1 -0
- package/dist/components/RichText/RichText.css +96 -0
- package/dist/components/RichText/RichText.css.d.ts +1 -0
- package/dist/components/RichText/RichText.d.ts +25 -0
- package/dist/components/RichText/RichText.d.ts.map +1 -0
- package/dist/components/RichText/RichText.js +56 -0
- package/dist/components/RichText/RichText.js.map +1 -0
- package/dist/components/RichText/index.d.ts +3 -0
- package/dist/components/RichText/index.d.ts.map +1 -0
- package/dist/components/ScrollArea/ScrollArea.css +236 -0
- package/dist/components/ScrollArea/ScrollArea.css.d.ts +1 -0
- package/dist/components/ScrollArea/ScrollArea.d.ts +25 -0
- package/dist/components/ScrollArea/ScrollArea.d.ts.map +1 -0
- package/dist/components/ScrollArea/ScrollArea.js +124 -0
- package/dist/components/ScrollArea/ScrollArea.js.map +1 -0
- package/dist/components/ScrollArea/index.d.ts +3 -0
- package/dist/components/ScrollArea/index.d.ts.map +1 -0
- package/dist/components/Scrollbar/Scrollbar.css +260 -0
- package/dist/components/Scrollbar/Scrollbar.css.d.ts +1 -0
- package/dist/components/Scrollbar/Scrollbar.d.ts +31 -0
- package/dist/components/Scrollbar/Scrollbar.d.ts.map +1 -0
- package/dist/components/Scrollbar/Scrollbar.js +277 -0
- package/dist/components/Scrollbar/Scrollbar.js.map +1 -0
- package/dist/components/Scrollbar/index.d.ts +3 -0
- package/dist/components/Scrollbar/index.d.ts.map +1 -0
- package/dist/components/Select/Select.css +299 -0
- package/dist/components/Select/Select.css.d.ts +1 -0
- package/dist/components/Select/Select.d.ts +52 -0
- package/dist/components/Select/Select.d.ts.map +1 -0
- package/dist/components/Select/Select.js +281 -0
- package/dist/components/Select/Select.js.map +1 -0
- package/dist/components/Select/index.d.ts +3 -0
- package/dist/components/Select/index.d.ts.map +1 -0
- package/dist/components/Slider/Slider.css +158 -0
- package/dist/components/Slider/Slider.css.d.ts +1 -0
- package/dist/components/Slider/Slider.d.ts +26 -0
- package/dist/components/Slider/Slider.d.ts.map +1 -0
- package/dist/components/Slider/Slider.js +67 -0
- package/dist/components/Slider/Slider.js.map +1 -0
- package/dist/components/Slider/index.d.ts +3 -0
- package/dist/components/Slider/index.d.ts.map +1 -0
- package/dist/components/SpinBox/SpinBox.css +152 -0
- package/dist/components/SpinBox/SpinBox.css.d.ts +1 -0
- package/dist/components/SpinBox/SpinBox.d.ts +28 -0
- package/dist/components/SpinBox/SpinBox.d.ts.map +1 -0
- package/dist/components/SpinBox/SpinBox.js +207 -0
- package/dist/components/SpinBox/SpinBox.js.map +1 -0
- package/dist/components/SpinBox/index.d.ts +3 -0
- package/dist/components/SpinBox/index.d.ts.map +1 -0
- package/dist/components/Spinner/Spinner.css +137 -0
- package/dist/components/Spinner/Spinner.css.d.ts +1 -0
- package/dist/components/Spinner/Spinner.d.ts +20 -0
- package/dist/components/Spinner/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner/Spinner.js +36 -0
- package/dist/components/Spinner/Spinner.js.map +1 -0
- package/dist/components/Spinner/index.d.ts +3 -0
- package/dist/components/Spinner/index.d.ts.map +1 -0
- package/dist/components/Splitter/Splitter.css +119 -0
- package/dist/components/Splitter/Splitter.css.d.ts +1 -0
- package/dist/components/Splitter/Splitter.d.ts +25 -0
- package/dist/components/Splitter/Splitter.d.ts.map +1 -0
- package/dist/components/Splitter/Splitter.js +212 -0
- package/dist/components/Splitter/Splitter.js.map +1 -0
- package/dist/components/Splitter/index.d.ts +3 -0
- package/dist/components/Splitter/index.d.ts.map +1 -0
- package/dist/components/StatusBar/StatusBar.css +97 -0
- package/dist/components/StatusBar/StatusBar.css.d.ts +1 -0
- package/dist/components/StatusBar/StatusBar.d.ts +22 -0
- package/dist/components/StatusBar/StatusBar.d.ts.map +1 -0
- package/dist/components/StatusBar/StatusBar.js +56 -0
- package/dist/components/StatusBar/StatusBar.js.map +1 -0
- package/dist/components/StatusBar/index.d.ts +3 -0
- package/dist/components/StatusBar/index.d.ts.map +1 -0
- package/dist/components/StatusLabel/StatusLabel.css +93 -0
- package/dist/components/StatusLabel/StatusLabel.css.d.ts +1 -0
- package/dist/components/StatusLabel/StatusLabel.d.ts +20 -0
- package/dist/components/StatusLabel/StatusLabel.d.ts.map +1 -0
- package/dist/components/StatusLabel/StatusLabel.js +42 -0
- package/dist/components/StatusLabel/StatusLabel.js.map +1 -0
- package/dist/components/StatusLabel/index.d.ts +3 -0
- package/dist/components/StatusLabel/index.d.ts.map +1 -0
- package/dist/components/Table/Table.css +253 -0
- package/dist/components/Table/Table.css.d.ts +1 -0
- package/dist/components/Table/Table.d.ts +67 -0
- package/dist/components/Table/Table.d.ts.map +1 -0
- package/dist/components/Table/Table.js +429 -0
- package/dist/components/Table/Table.js.map +1 -0
- package/dist/components/Table/index.d.ts +3 -0
- package/dist/components/Table/index.d.ts.map +1 -0
- package/dist/components/Tabs/Tabs.css +138 -0
- package/dist/components/Tabs/Tabs.css.d.ts +1 -0
- package/dist/components/Tabs/Tabs.d.ts +34 -0
- package/dist/components/Tabs/Tabs.d.ts.map +1 -0
- package/dist/components/Tabs/Tabs.js +140 -0
- package/dist/components/Tabs/Tabs.js.map +1 -0
- package/dist/components/Tabs/index.d.ts +3 -0
- package/dist/components/Tabs/index.d.ts.map +1 -0
- package/dist/components/TextInput/TextInput.css +133 -0
- package/dist/components/TextInput/TextInput.css.d.ts +1 -0
- package/dist/components/TextInput/TextInput.d.ts +16 -0
- package/dist/components/TextInput/TextInput.d.ts.map +1 -0
- package/dist/components/TextInput/TextInput.js +38 -0
- package/dist/components/TextInput/TextInput.js.map +1 -0
- package/dist/components/TextInput/index.d.ts +3 -0
- package/dist/components/TextInput/index.d.ts.map +1 -0
- package/dist/components/Textarea/Textarea.css +121 -0
- package/dist/components/Textarea/Textarea.css.d.ts +1 -0
- package/dist/components/Textarea/Textarea.d.ts +21 -0
- package/dist/components/Textarea/Textarea.d.ts.map +1 -0
- package/dist/components/Textarea/Textarea.js +61 -0
- package/dist/components/Textarea/Textarea.js.map +1 -0
- package/dist/components/Textarea/index.d.ts +3 -0
- package/dist/components/Textarea/index.d.ts.map +1 -0
- package/dist/components/TitleBar/TitleBar.css +76 -0
- package/dist/components/TitleBar/TitleBar.css.d.ts +1 -0
- package/dist/components/TitleBar/TitleBar.d.ts +20 -0
- package/dist/components/TitleBar/TitleBar.d.ts.map +1 -0
- package/dist/components/TitleBar/TitleBar.js +43 -0
- package/dist/components/TitleBar/TitleBar.js.map +1 -0
- package/dist/components/TitleBar/index.d.ts +3 -0
- package/dist/components/TitleBar/index.d.ts.map +1 -0
- package/dist/components/ToggleButton/ToggleButton.css +162 -0
- package/dist/components/ToggleButton/ToggleButton.css.d.ts +1 -0
- package/dist/components/ToggleButton/ToggleButton.d.ts +27 -0
- package/dist/components/ToggleButton/ToggleButton.d.ts.map +1 -0
- package/dist/components/ToggleButton/ToggleButton.js +52 -0
- package/dist/components/ToggleButton/ToggleButton.js.map +1 -0
- package/dist/components/ToggleButton/index.d.ts +3 -0
- package/dist/components/ToggleButton/index.d.ts.map +1 -0
- package/dist/components/Toolbar/Toolbar.css +202 -0
- package/dist/components/Toolbar/Toolbar.css.d.ts +1 -0
- package/dist/components/Toolbar/Toolbar.d.ts +46 -0
- package/dist/components/Toolbar/Toolbar.d.ts.map +1 -0
- package/dist/components/Toolbar/Toolbar.js +102 -0
- package/dist/components/Toolbar/Toolbar.js.map +1 -0
- package/dist/components/Toolbar/index.d.ts +3 -0
- package/dist/components/Toolbar/index.d.ts.map +1 -0
- package/dist/components/Tooltip/Tooltip.css +86 -0
- package/dist/components/Tooltip/Tooltip.css.d.ts +1 -0
- package/dist/components/Tooltip/Tooltip.d.ts +22 -0
- package/dist/components/Tooltip/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip/Tooltip.js +92 -0
- package/dist/components/Tooltip/Tooltip.js.map +1 -0
- package/dist/components/Tooltip/index.d.ts +3 -0
- package/dist/components/Tooltip/index.d.ts.map +1 -0
- package/dist/components/TreeView/TreeView.css +191 -0
- package/dist/components/TreeView/TreeView.css.d.ts +1 -0
- package/dist/components/TreeView/TreeView.d.ts +46 -0
- package/dist/components/TreeView/TreeView.d.ts.map +1 -0
- package/dist/components/TreeView/TreeView.js +331 -0
- package/dist/components/TreeView/TreeView.js.map +1 -0
- package/dist/components/TreeView/index.d.ts +3 -0
- package/dist/components/TreeView/index.d.ts.map +1 -0
- package/dist/components/Window/Window.css +218 -0
- package/dist/components/Window/Window.css.d.ts +1 -0
- package/dist/components/Window/Window.d.ts +30 -0
- package/dist/components/Window/Window.d.ts.map +1 -0
- package/dist/components/Window/Window.js +156 -0
- package/dist/components/Window/Window.js.map +1 -0
- package/dist/components/Window/index.d.ts +3 -0
- package/dist/components/Window/index.d.ts.map +1 -0
- package/dist/index.d.ts +91 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -3
- package/dist/styles/scrollbars.css +123 -0
- package/dist/styles/scrollbars.css.d.ts +1 -0
- package/dist/styles/theme.css +86 -41
- package/dist/styles/tokens.css +202 -0
- package/dist/styles/tokens.css.d.ts +1 -0
- package/dist/test/a11y.d.ts +3 -0
- package/dist/test/a11y.d.ts.map +1 -0
- package/dist/test/index.d.ts +2 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/package.json +13 -2
- package/dist/components/GreenButton/GreenButton.css +0 -47
- package/dist/components/GreenButton/GreenButton.d.ts +0 -8
- package/dist/components/GreenButton/GreenButton.d.ts.map +0 -1
- package/dist/components/GreenButton/GreenButton.js +0 -20
- package/dist/components/GreenButton/GreenButton.js.map +0 -1
- package/dist/components/GreenButton/index.d.ts +0 -3
- package/dist/components/GreenPanel/GreenPanel.css +0 -45
- package/dist/components/GreenPanel/GreenPanel.d.ts +0 -12
- package/dist/components/GreenPanel/GreenPanel.d.ts.map +0 -1
- package/dist/components/GreenPanel/GreenPanel.js +0 -24
- package/dist/components/GreenPanel/GreenPanel.js.map +0 -1
- package/dist/components/GreenPanel/index.d.ts +0 -3
- /package/dist/components/{GreenButton/GreenButton.css.d.ts → Avatar/Avatar.css.d.ts} +0 -0
- /package/dist/components/{GreenPanel/GreenPanel.css.d.ts → Button/Button.css.d.ts} +0 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { Fragment, forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import "../../styles/scrollbars.css";
|
|
4
|
+
import "./TreeView.css";
|
|
5
|
+
//#region src/components/TreeView/TreeView.tsx
|
|
6
|
+
/** How long a run of typed characters keeps building a typeahead query. */
|
|
7
|
+
var TYPEAHEAD_RESET_MS = 600;
|
|
8
|
+
/** `--vgui-tree-indent` defaults to 16px, which is the doc's inference. */
|
|
9
|
+
var DEFAULT_INDENT = 16;
|
|
10
|
+
function toIdArray(value) {
|
|
11
|
+
if (value === void 0) return [];
|
|
12
|
+
return Array.isArray(value) ? value.slice() : [value];
|
|
13
|
+
}
|
|
14
|
+
function clamp(value, min, max) {
|
|
15
|
+
return Math.min(Math.max(value, min), max);
|
|
16
|
+
}
|
|
17
|
+
/** The doc is explicit that a node with an empty array still renders a leaf. */
|
|
18
|
+
function hasChildren(node) {
|
|
19
|
+
return Array.isArray(node.children) && node.children.length > 0;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Walks the visible nodes in depth-first order, descending into a subtree only
|
|
23
|
+
* while it is expanded — so this list is both the rendering order and the
|
|
24
|
+
* arrow-key/typeahead order, and a collapsed subtree is simply absent.
|
|
25
|
+
*/
|
|
26
|
+
function flatten(nodes, expanded, level = 1, parentId = null, out = []) {
|
|
27
|
+
for (const node of nodes) {
|
|
28
|
+
const children = hasChildren(node);
|
|
29
|
+
out.push({
|
|
30
|
+
node,
|
|
31
|
+
level,
|
|
32
|
+
parentId,
|
|
33
|
+
hasChildren: children
|
|
34
|
+
});
|
|
35
|
+
if (children && expanded.has(node.id)) flatten(node.children, expanded, level + 1, node.id, out);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
/** `defaultExpanded` seeds the uncontrolled expansion state at every depth. */
|
|
40
|
+
function initialExpanded(nodes) {
|
|
41
|
+
return nodes.flatMap((node) => [...node.defaultExpanded ? [node.id] : [], ...initialExpanded(Array.isArray(node.children) ? node.children : [])]);
|
|
42
|
+
}
|
|
43
|
+
var TreeView = forwardRef(function TreeView(props, forwardedRef) {
|
|
44
|
+
const { className, nodes, variant = "default", selected, defaultSelected, multi = false, onSelectedChange, expandOnRowClick = false, indentSize, emptyMessage, label, style, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...rest } = props;
|
|
45
|
+
const baseId = useId();
|
|
46
|
+
const nodeRefs = useRef(/* @__PURE__ */ new Map());
|
|
47
|
+
/**
|
|
48
|
+
* Shift+click anchors on the node that was focused when the range started.
|
|
49
|
+
* It lives in a ref because a pointer press moves DOM focus before the click
|
|
50
|
+
* lands, which would otherwise overwrite the anchor with the clicked node.
|
|
51
|
+
*/
|
|
52
|
+
const anchorRef = useRef(null);
|
|
53
|
+
const typeaheadRef = useRef({
|
|
54
|
+
query: "",
|
|
55
|
+
timer: null
|
|
56
|
+
});
|
|
57
|
+
const [uncontrolledSelected, setUncontrolledSelected] = useState(() => toIdArray(defaultSelected));
|
|
58
|
+
const [expandedIds, setExpandedIds] = useState(() => initialExpanded(nodes));
|
|
59
|
+
const [activeId, setActiveId] = useState(null);
|
|
60
|
+
const [announcement, setAnnouncement] = useState("");
|
|
61
|
+
const expandedSet = useMemo(() => new Set(expandedIds), [expandedIds]);
|
|
62
|
+
const visible = useMemo(() => flatten(nodes, expandedSet), [expandedSet, nodes]);
|
|
63
|
+
const nodeById = useMemo(() => new Map(visible.map((entry) => [entry.node.id, entry])), [visible]);
|
|
64
|
+
const selectedIds = selected === void 0 ? uncontrolledSelected : toIdArray(selected);
|
|
65
|
+
const selectedSet = useMemo(() => new Set(selectedIds), [selectedIds]);
|
|
66
|
+
const fallbackActiveId = visible.find((entry) => selectedSet.has(entry.node.id))?.node.id ?? visible[0]?.node.id ?? null;
|
|
67
|
+
const activeNodeId = activeId !== null && nodeById.has(activeId) ? activeId : fallbackActiveId;
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const typeahead = typeaheadRef.current;
|
|
70
|
+
return () => {
|
|
71
|
+
if (typeahead.timer !== null) clearTimeout(typeahead.timer);
|
|
72
|
+
};
|
|
73
|
+
}, []);
|
|
74
|
+
const setNodeRef = useCallback((id, node) => {
|
|
75
|
+
if (node) nodeRefs.current.set(id, node);
|
|
76
|
+
else nodeRefs.current.delete(id);
|
|
77
|
+
}, []);
|
|
78
|
+
const labelOf = useCallback((id) => nodeById.get(id)?.node.label ?? "", [nodeById]);
|
|
79
|
+
const commit = useCallback((next) => {
|
|
80
|
+
if (selected === void 0) setUncontrolledSelected(next);
|
|
81
|
+
onSelectedChange?.(multi ? next : next[0] ?? "");
|
|
82
|
+
}, [
|
|
83
|
+
multi,
|
|
84
|
+
onSelectedChange,
|
|
85
|
+
selected
|
|
86
|
+
]);
|
|
87
|
+
const describe = useCallback((id, next) => {
|
|
88
|
+
const entry = nodeById.get(id);
|
|
89
|
+
const name = (entry?.node.label ?? "").slice(0, 60);
|
|
90
|
+
const state = next.includes(id) ? "selected" : "not selected";
|
|
91
|
+
const level = entry?.level ?? 1;
|
|
92
|
+
const position = visible.findIndex((candidate) => candidate.node.id === id) + 1;
|
|
93
|
+
return [
|
|
94
|
+
`${name || "Node"}, ${state}`,
|
|
95
|
+
`level ${level}`,
|
|
96
|
+
`${position} of ${visible.length}`
|
|
97
|
+
].join(", ");
|
|
98
|
+
}, [nodeById, visible]);
|
|
99
|
+
const focusNode = useCallback((id) => {
|
|
100
|
+
setActiveId(id);
|
|
101
|
+
anchorRef.current = id;
|
|
102
|
+
const node = nodeRefs.current.get(id);
|
|
103
|
+
if (!node) return;
|
|
104
|
+
/**
|
|
105
|
+
* jsdom has no layout engine, so `scrollIntoView` may be a stub or missing
|
|
106
|
+
* entirely; the tree only asks that the active node be brought into view,
|
|
107
|
+
* so a missing implementation is not fatal.
|
|
108
|
+
*/
|
|
109
|
+
if (typeof node.scrollIntoView === "function") node.scrollIntoView({ block: "nearest" });
|
|
110
|
+
node.focus();
|
|
111
|
+
}, []);
|
|
112
|
+
const moveActive = useCallback((step) => {
|
|
113
|
+
const ids = visible.map((entry) => entry.node.id);
|
|
114
|
+
if (ids.length === 0) return;
|
|
115
|
+
const current = activeNodeId === null ? -1 : ids.indexOf(activeNodeId);
|
|
116
|
+
const next = step === "first" ? 0 : step === "last" ? ids.length - 1 : clamp((current < 0 ? 0 : current) + step, 0, ids.length - 1);
|
|
117
|
+
focusNode(ids[next]);
|
|
118
|
+
}, [
|
|
119
|
+
activeNodeId,
|
|
120
|
+
focusNode,
|
|
121
|
+
visible
|
|
122
|
+
]);
|
|
123
|
+
const setExpanded = useCallback((id, next) => {
|
|
124
|
+
setExpandedIds((previous) => {
|
|
125
|
+
if (next) return previous.includes(id) ? previous : [...previous, id];
|
|
126
|
+
return previous.includes(id) ? previous.filter((entry) => entry !== id) : previous;
|
|
127
|
+
});
|
|
128
|
+
}, []);
|
|
129
|
+
const toggleExpanded = useCallback((id) => setExpanded(id, !expandedSet.has(id)), [expandedSet, setExpanded]);
|
|
130
|
+
const typeahead = useCallback((character) => {
|
|
131
|
+
const state = typeaheadRef.current;
|
|
132
|
+
const buffer = (state.query + character).toLowerCase();
|
|
133
|
+
state.query = buffer;
|
|
134
|
+
if (state.timer !== null) clearTimeout(state.timer);
|
|
135
|
+
state.timer = setTimeout(() => {
|
|
136
|
+
state.query = "";
|
|
137
|
+
}, TYPEAHEAD_RESET_MS);
|
|
138
|
+
const ids = visible.map((entry) => entry.node.id);
|
|
139
|
+
if (ids.length === 0) return;
|
|
140
|
+
const targets = visible.filter((entry) => !entry.node.disabled).map((entry) => entry.node.id);
|
|
141
|
+
if (targets.length === 0) return;
|
|
142
|
+
const start = activeNodeId === null ? -1 : ids.indexOf(activeNodeId);
|
|
143
|
+
for (let offset = 1; offset <= ids.length; offset += 1) {
|
|
144
|
+
const id = ids[(start + offset + ids.length) % ids.length];
|
|
145
|
+
if (!targets.includes(id)) continue;
|
|
146
|
+
if (labelOf(id).toLowerCase().startsWith(buffer)) {
|
|
147
|
+
focusNode(id);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}, [
|
|
152
|
+
activeNodeId,
|
|
153
|
+
focusNode,
|
|
154
|
+
labelOf,
|
|
155
|
+
visible
|
|
156
|
+
]);
|
|
157
|
+
const selectNode = useCallback((id, mode) => {
|
|
158
|
+
const anchor = anchorRef.current ?? activeNodeId;
|
|
159
|
+
const ids = visible.map((entry) => entry.node.id);
|
|
160
|
+
let next;
|
|
161
|
+
const from = anchor === null ? -1 : ids.indexOf(anchor);
|
|
162
|
+
if (mode === "range" && from >= 0 && anchor !== id) {
|
|
163
|
+
const to = ids.indexOf(id);
|
|
164
|
+
next = ids.slice(Math.min(from, to), Math.max(from, to) + 1);
|
|
165
|
+
} else if (mode === "range" || mode === "replace") next = [id];
|
|
166
|
+
else next = selectedSet.has(id) ? selectedIds.filter((entry) => entry !== id) : [...selectedIds, id];
|
|
167
|
+
if (mode !== "range") anchorRef.current = id;
|
|
168
|
+
setAnnouncement(describe(id, next));
|
|
169
|
+
commit(next);
|
|
170
|
+
}, [
|
|
171
|
+
activeNodeId,
|
|
172
|
+
commit,
|
|
173
|
+
describe,
|
|
174
|
+
selectedIds,
|
|
175
|
+
selectedSet,
|
|
176
|
+
visible
|
|
177
|
+
]);
|
|
178
|
+
const selectByKeyboard = useCallback((id) => {
|
|
179
|
+
const entry = nodeById.get(id);
|
|
180
|
+
if (!entry || entry.node.disabled) return;
|
|
181
|
+
selectNode(id, multi ? "toggle" : "replace");
|
|
182
|
+
}, [
|
|
183
|
+
multi,
|
|
184
|
+
nodeById,
|
|
185
|
+
selectNode
|
|
186
|
+
]);
|
|
187
|
+
const handleKeyDown = useCallback((event) => {
|
|
188
|
+
const { key } = event;
|
|
189
|
+
const entry = activeNodeId === null ? void 0 : nodeById.get(activeNodeId);
|
|
190
|
+
if (key === "ArrowDown" || key === "ArrowUp") {
|
|
191
|
+
event.preventDefault();
|
|
192
|
+
moveActive(key === "ArrowDown" ? 1 : -1);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (key === "ArrowRight" && entry !== void 0) {
|
|
196
|
+
event.preventDefault();
|
|
197
|
+
if (!entry.hasChildren) return;
|
|
198
|
+
if (expandedSet.has(entry.node.id)) {
|
|
199
|
+
const first = entry.node.children[0];
|
|
200
|
+
if (first) focusNode(first.id);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
setExpanded(entry.node.id, true);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (key === "ArrowLeft" && entry !== void 0) {
|
|
207
|
+
event.preventDefault();
|
|
208
|
+
if (entry.hasChildren && expandedSet.has(entry.node.id)) {
|
|
209
|
+
setExpanded(entry.node.id, false);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (entry.parentId !== null) focusNode(entry.parentId);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (key === "Home" || key === "End") {
|
|
216
|
+
event.preventDefault();
|
|
217
|
+
moveActive(key === "Home" ? "first" : "last");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (key === "Enter" || key === " ") {
|
|
221
|
+
event.preventDefault();
|
|
222
|
+
if (activeNodeId !== null) selectByKeyboard(activeNodeId);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) typeahead(key);
|
|
226
|
+
}, [
|
|
227
|
+
activeNodeId,
|
|
228
|
+
expandedSet,
|
|
229
|
+
focusNode,
|
|
230
|
+
moveActive,
|
|
231
|
+
nodeById,
|
|
232
|
+
selectByKeyboard,
|
|
233
|
+
setExpanded,
|
|
234
|
+
typeahead
|
|
235
|
+
]);
|
|
236
|
+
const handleNodeClick = useCallback((event, node) => {
|
|
237
|
+
if (node.disabled) return;
|
|
238
|
+
setActiveId(node.id);
|
|
239
|
+
if (Boolean(event.target?.closest?.(".vgui-tree__image")) && hasChildren(node)) {
|
|
240
|
+
toggleExpanded(node.id);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (!multi) selectNode(node.id, "replace");
|
|
244
|
+
else if (event.shiftKey) selectNode(node.id, "range");
|
|
245
|
+
else selectNode(node.id, event.ctrlKey || event.metaKey ? "toggle" : "replace");
|
|
246
|
+
if (expandOnRowClick && hasChildren(node)) toggleExpanded(node.id);
|
|
247
|
+
}, [
|
|
248
|
+
expandOnRowClick,
|
|
249
|
+
multi,
|
|
250
|
+
selectNode,
|
|
251
|
+
toggleExpanded
|
|
252
|
+
]);
|
|
253
|
+
const classes = ["vgui-tree"];
|
|
254
|
+
classes.push(`vgui-tree--${variant}`);
|
|
255
|
+
if (multi) classes.push("vgui-tree--multi");
|
|
256
|
+
if (nodes.length === 0) classes.push("vgui-tree--empty");
|
|
257
|
+
if (className) classes.push(className);
|
|
258
|
+
const treeStyle = {
|
|
259
|
+
...style,
|
|
260
|
+
"--vgui-tree-indent": `${indentSize ?? DEFAULT_INDENT}px`
|
|
261
|
+
};
|
|
262
|
+
const renderNodes = (children, level) => children.map((node) => {
|
|
263
|
+
const nodeHasChildren = hasChildren(node);
|
|
264
|
+
const expanded = nodeHasChildren && expandedSet.has(node.id);
|
|
265
|
+
const isSelected = selectedSet.has(node.id);
|
|
266
|
+
const isActive = node.id === activeNodeId;
|
|
267
|
+
const nodeClasses = ["vgui-tree__node"];
|
|
268
|
+
if (isSelected) nodeClasses.push("vgui-tree__node--selected");
|
|
269
|
+
if (node.disabled) nodeClasses.push("vgui-tree__node--disabled");
|
|
270
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
271
|
+
id: `${baseId}-node-${node.id}`,
|
|
272
|
+
ref: (element) => setNodeRef(node.id, element),
|
|
273
|
+
className: nodeClasses.join(" "),
|
|
274
|
+
role: "treeitem",
|
|
275
|
+
"aria-level": level,
|
|
276
|
+
"aria-expanded": nodeHasChildren ? expanded : void 0,
|
|
277
|
+
"aria-selected": isSelected,
|
|
278
|
+
"aria-disabled": node.disabled ? true : void 0,
|
|
279
|
+
tabIndex: isActive ? 0 : -1,
|
|
280
|
+
style: { "--vgui-tree-level": level - 1 },
|
|
281
|
+
title: node.label,
|
|
282
|
+
onClick: (event) => handleNodeClick(event, node),
|
|
283
|
+
onFocus: () => {
|
|
284
|
+
setActiveId(node.id);
|
|
285
|
+
},
|
|
286
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
287
|
+
className: `vgui-tree__image${nodeHasChildren ? expanded ? " vgui-tree__image--expanded" : " vgui-tree__image--collapsed" : " vgui-tree__image--leaf"}`,
|
|
288
|
+
"aria-hidden": "true"
|
|
289
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
290
|
+
className: "vgui-tree__text",
|
|
291
|
+
children: node.label
|
|
292
|
+
})]
|
|
293
|
+
}), expanded ? /* @__PURE__ */ jsx("div", {
|
|
294
|
+
className: "vgui-tree__group",
|
|
295
|
+
role: "group",
|
|
296
|
+
children: renderNodes(node.children ?? [], level + 1)
|
|
297
|
+
}) : null] }, node.id);
|
|
298
|
+
});
|
|
299
|
+
const isEmpty = nodes.length === 0;
|
|
300
|
+
return /* @__PURE__ */ jsx("div", {
|
|
301
|
+
ref: forwardedRef,
|
|
302
|
+
className: classes.join(" "),
|
|
303
|
+
style: treeStyle,
|
|
304
|
+
...rest,
|
|
305
|
+
children: isEmpty ? /* @__PURE__ */ jsx("div", {
|
|
306
|
+
className: "vgui-tree__subpanel vgui-scroll-surface",
|
|
307
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
308
|
+
className: "vgui-tree__empty",
|
|
309
|
+
role: "status",
|
|
310
|
+
children: emptyMessage
|
|
311
|
+
})
|
|
312
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
313
|
+
className: "vgui-tree__subpanel vgui-scroll-surface",
|
|
314
|
+
role: "tree",
|
|
315
|
+
"aria-label": ariaLabelledBy ? void 0 : ariaLabel ?? label ?? "Tree",
|
|
316
|
+
"aria-labelledby": ariaLabelledBy,
|
|
317
|
+
"aria-multiselectable": multi ? true : void 0,
|
|
318
|
+
onKeyDown: handleKeyDown,
|
|
319
|
+
children: renderNodes(nodes, 1)
|
|
320
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
321
|
+
className: "vgui-tree__live",
|
|
322
|
+
role: "status",
|
|
323
|
+
children: announcement
|
|
324
|
+
})] })
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
TreeView.displayName = "TreeView";
|
|
328
|
+
//#endregion
|
|
329
|
+
export { TreeView };
|
|
330
|
+
|
|
331
|
+
//# sourceMappingURL=TreeView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TreeView.js","names":[],"sources":["../../../src/components/TreeView/TreeView.tsx"],"sourcesContent":["import { Fragment, forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport type {\n CSSProperties,\n HTMLAttributes,\n KeyboardEvent as ReactKeyboardEvent,\n MouseEvent as ReactMouseEvent,\n ReactNode,\n} from 'react'\n\nimport './TreeView.css'\nimport '../../styles/scrollbars.css'\n\nexport interface TreeNode {\n /** Stable identity; also the React key. */\n id: string\n /** Visible node label. */\n label: string\n /** Child nodes. A node with an empty array still renders a leaf. */\n children?: TreeNode[]\n /** Renders disabled and unselectable. */\n disabled?: boolean\n /** Initial expansion when uncontrolled. */\n defaultExpanded?: boolean\n /** Arbitrary payload returned on selection. */\n data?: unknown\n}\n\nexport type TreeViewVariant = 'default' | 'sidebar'\n\nexport interface TreeViewProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n /** Root nodes, in order. */\n nodes: TreeNode[]\n /** `sidebar` draws the two-sided `TreeViewSubPanel` recess and fills its parent. */\n variant?: TreeViewVariant\n /** Controlled selection. Use `string[]` when `multi` is set. */\n selected?: string | string[]\n /** Initial selection when uncontrolled. */\n defaultSelected?: string | string[]\n /** Allows Ctrl/Shift multi-selection. */\n multi?: boolean\n /** Called with the newly selected node id, or ids when `multi` is set. */\n onSelectedChange?: (ids: string | string[]) => void\n /** Expands nodes when the whole row is clicked, not just the disclosure glyph. */\n expandOnRowClick?: boolean\n /** Indent per level in pixels. Defaults to 16. */\n indentSize?: number\n /** Message rendered when there are no nodes. */\n emptyMessage?: string\n /**\n * Accessible name for the tree, used when neither `aria-label` nor\n * `aria-labelledby` is given. ARIA requires a tree to be named, so it falls\n * back to `\"Tree\"`.\n */\n label?: string\n}\n\n/** How long a run of typed characters keeps building a typeahead query. */\nconst TYPEAHEAD_RESET_MS = 600\n\n/** `--vgui-tree-indent` defaults to 16px, which is the doc's inference. */\nconst DEFAULT_INDENT = 16\n\ninterface FlatNode {\n node: TreeNode\n level: number\n parentId: string | null\n hasChildren: boolean\n}\n\nfunction toIdArray(value: string | string[] | undefined): string[] {\n if (value === undefined) return []\n return Array.isArray(value) ? value.slice() : [value]\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\n/** The doc is explicit that a node with an empty array still renders a leaf. */\nfunction hasChildren(node: TreeNode): boolean {\n return Array.isArray(node.children) && node.children.length > 0\n}\n\n/**\n * Walks the visible nodes in depth-first order, descending into a subtree only\n * while it is expanded — so this list is both the rendering order and the\n * arrow-key/typeahead order, and a collapsed subtree is simply absent.\n */\nfunction flatten(\n nodes: TreeNode[],\n expanded: Set<string>,\n level = 1,\n parentId: string | null = null,\n out: FlatNode[] = [],\n): FlatNode[] {\n for (const node of nodes) {\n const children = hasChildren(node)\n out.push({ node, level, parentId, hasChildren: children })\n if (children && expanded.has(node.id)) {\n flatten(node.children as TreeNode[], expanded, level + 1, node.id, out)\n }\n }\n return out\n}\n\n/** `defaultExpanded` seeds the uncontrolled expansion state at every depth. */\nfunction initialExpanded(nodes: TreeNode[]): string[] {\n return nodes.flatMap((node) => [\n ...(node.defaultExpanded ? [node.id] : []),\n ...initialExpanded(Array.isArray(node.children) ? node.children : []),\n ])\n}\n\nexport const TreeView = forwardRef<HTMLDivElement, TreeViewProps>(function TreeView(\n props,\n forwardedRef,\n) {\n const {\n className,\n nodes,\n variant = 'default',\n selected,\n defaultSelected,\n multi = false,\n onSelectedChange,\n expandOnRowClick = false,\n indentSize,\n emptyMessage,\n label,\n style,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...rest\n } = props\n\n const baseId = useId()\n const nodeRefs = useRef(new Map<string, HTMLDivElement>())\n /**\n * Shift+click anchors on the node that was focused when the range started.\n * It lives in a ref because a pointer press moves DOM focus before the click\n * lands, which would otherwise overwrite the anchor with the clicked node.\n */\n const anchorRef = useRef<string | null>(null)\n const typeaheadRef = useRef<{ query: string; timer: ReturnType<typeof setTimeout> | null }>({\n query: '',\n timer: null,\n })\n\n const [uncontrolledSelected, setUncontrolledSelected] = useState<string[]>(() =>\n toIdArray(defaultSelected),\n )\n const [expandedIds, setExpandedIds] = useState<string[]>(() => initialExpanded(nodes))\n const [activeId, setActiveId] = useState<string | null>(null)\n const [announcement, setAnnouncement] = useState('')\n\n const expandedSet = useMemo(() => new Set(expandedIds), [expandedIds])\n const visible = useMemo(() => flatten(nodes, expandedSet), [expandedSet, nodes])\n const nodeById = useMemo(\n () => new Map(visible.map((entry) => [entry.node.id, entry])),\n [visible],\n )\n\n const selectedIds = selected === undefined ? uncontrolledSelected : toIdArray(selected)\n const selectedSet = useMemo(() => new Set(selectedIds), [selectedIds])\n\n // Roving tabindex: exactly one node is the tab stop. A stale active node (its\n // ancestor collapsed, or its node removed) falls back to the selection and\n // then to the first visible node.\n const fallbackActiveId =\n visible.find((entry) => selectedSet.has(entry.node.id))?.node.id ?? visible[0]?.node.id ?? null\n const activeNodeId =\n activeId !== null && nodeById.has(activeId) ? activeId : fallbackActiveId\n\n useEffect(() => {\n const typeahead = typeaheadRef.current\n return () => {\n if (typeahead.timer !== null) clearTimeout(typeahead.timer)\n }\n }, [])\n\n const setNodeRef = useCallback((id: string, node: HTMLDivElement | null) => {\n if (node) nodeRefs.current.set(id, node)\n else nodeRefs.current.delete(id)\n }, [])\n\n const labelOf = useCallback(\n (id: string) => nodeById.get(id)?.node.label ?? '',\n [nodeById],\n )\n\n const commit = useCallback(\n (next: string[]) => {\n if (selected === undefined) setUncontrolledSelected(next)\n onSelectedChange?.(multi ? next : (next[0] ?? ''))\n },\n [multi, onSelectedChange, selected],\n )\n\n const describe = useCallback(\n (id: string, next: string[]) => {\n const entry = nodeById.get(id)\n const name = (entry?.node.label ?? '').slice(0, 60)\n const state = next.includes(id) ? 'selected' : 'not selected'\n const level = entry?.level ?? 1\n const position = visible.findIndex((candidate) => candidate.node.id === id) + 1\n return [\n `${name || 'Node'}, ${state}`,\n `level ${level}`,\n `${position} of ${visible.length}`,\n ].join(', ')\n },\n [nodeById, visible],\n )\n\n const focusNode = useCallback((id: string) => {\n setActiveId(id)\n anchorRef.current = id\n const node = nodeRefs.current.get(id)\n if (!node) return\n /**\n * jsdom has no layout engine, so `scrollIntoView` may be a stub or missing\n * entirely; the tree only asks that the active node be brought into view,\n * so a missing implementation is not fatal.\n */\n if (typeof node.scrollIntoView === 'function') node.scrollIntoView({ block: 'nearest' })\n node.focus()\n }, [])\n\n const moveActive = useCallback(\n (step: number | 'first' | 'last') => {\n const ids = visible.map((entry) => entry.node.id)\n if (ids.length === 0) return\n\n const current = activeNodeId === null ? -1 : ids.indexOf(activeNodeId)\n const base = current < 0 ? 0 : current\n const next =\n step === 'first'\n ? 0\n : step === 'last'\n ? ids.length - 1\n : clamp(base + step, 0, ids.length - 1)\n focusNode(ids[next])\n },\n [activeNodeId, focusNode, visible],\n )\n\n const setExpanded = useCallback((id: string, next: boolean) => {\n setExpandedIds((previous) => {\n if (next) return previous.includes(id) ? previous : [...previous, id]\n return previous.includes(id) ? previous.filter((entry) => entry !== id) : previous\n })\n }, [])\n\n const toggleExpanded = useCallback(\n (id: string) => setExpanded(id, !expandedSet.has(id)),\n [expandedSet, setExpanded],\n )\n\n const typeahead = useCallback(\n (character: string) => {\n const state = typeaheadRef.current\n const buffer = (state.query + character).toLowerCase()\n state.query = buffer\n if (state.timer !== null) clearTimeout(state.timer)\n state.timer = setTimeout(() => {\n state.query = ''\n }, TYPEAHEAD_RESET_MS)\n\n const ids = visible.map((entry) => entry.node.id)\n if (ids.length === 0) return\n\n // Disabled nodes advertise their existence but are not typeahead targets.\n const targets = visible\n .filter((entry) => !entry.node.disabled)\n .map((entry) => entry.node.id)\n if (targets.length === 0) return\n\n const start = activeNodeId === null ? -1 : ids.indexOf(activeNodeId)\n for (let offset = 1; offset <= ids.length; offset += 1) {\n const id = ids[(start + offset + ids.length) % ids.length]\n if (!targets.includes(id)) continue\n if (labelOf(id).toLowerCase().startsWith(buffer)) {\n focusNode(id)\n return\n }\n }\n },\n [activeNodeId, focusNode, labelOf, visible],\n )\n\n const selectNode = useCallback(\n (id: string, mode: 'replace' | 'toggle' | 'range') => {\n const anchor = anchorRef.current ?? activeNodeId\n const ids = visible.map((entry) => entry.node.id)\n let next: string[]\n\n const from = anchor === null ? -1 : ids.indexOf(anchor)\n if (mode === 'range' && from >= 0 && anchor !== id) {\n const to = ids.indexOf(id)\n next = ids.slice(Math.min(from, to), Math.max(from, to) + 1)\n } else if (mode === 'range' || mode === 'replace') {\n next = [id]\n } else {\n next =\n selectedSet.has(id)\n ? selectedIds.filter((entry) => entry !== id)\n : [...selectedIds, id]\n }\n\n // A range keeps its original anchor; every other gesture moves it.\n if (mode !== 'range') anchorRef.current = id\n\n setAnnouncement(describe(id, next))\n commit(next)\n },\n [activeNodeId, commit, describe, selectedIds, selectedSet, visible],\n )\n\n const selectByKeyboard = useCallback(\n (id: string) => {\n const entry = nodeById.get(id)\n if (!entry || entry.node.disabled) return\n selectNode(id, multi ? 'toggle' : 'replace')\n },\n [multi, nodeById, selectNode],\n )\n\n const handleKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const { key } = event\n const entry = activeNodeId === null ? undefined : nodeById.get(activeNodeId)\n\n if (key === 'ArrowDown' || key === 'ArrowUp') {\n event.preventDefault()\n moveActive(key === 'ArrowDown' ? 1 : -1)\n return\n }\n\n if (key === 'ArrowRight' && entry !== undefined) {\n event.preventDefault()\n if (!entry.hasChildren) return\n // An expanded node hands focus to its first child; a collapsed one opens\n // in place.\n if (expandedSet.has(entry.node.id)) {\n const children = entry.node.children as TreeNode[]\n const first = children[0]\n if (first) focusNode(first.id)\n return\n }\n setExpanded(entry.node.id, true)\n return\n }\n\n if (key === 'ArrowLeft' && entry !== undefined) {\n event.preventDefault()\n // An expanded node closes in place; a collapsed child steps out to its\n // parent, which is the ARIA tree model and costs a keyboard user no\n // second tab stop.\n if (entry.hasChildren && expandedSet.has(entry.node.id)) {\n setExpanded(entry.node.id, false)\n return\n }\n if (entry.parentId !== null) focusNode(entry.parentId)\n return\n }\n\n if (key === 'Home' || key === 'End') {\n event.preventDefault()\n moveActive(key === 'Home' ? 'first' : 'last')\n return\n }\n\n if (key === 'Enter' || key === ' ') {\n event.preventDefault()\n if (activeNodeId !== null) selectByKeyboard(activeNodeId)\n return\n }\n\n if (key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {\n typeahead(key)\n }\n },\n [\n activeNodeId,\n expandedSet,\n focusNode,\n moveActive,\n nodeById,\n selectByKeyboard,\n setExpanded,\n typeahead,\n ],\n )\n\n const handleNodeClick = useCallback(\n (event: ReactMouseEvent<HTMLDivElement>, node: TreeNode) => {\n if (node.disabled) return\n\n // Roving tabindex means the pointer must move the active node too.\n setActiveId(node.id)\n\n // The glyph is decorative (`aria-hidden`) because the row already carries\n // `aria-expanded`, so it is hit-tested here rather than being a second\n // interactive control inside the `treeitem`.\n const clickedGlyph = Boolean(\n (event.target as Element | null)?.closest?.('.vgui-tree__image'),\n )\n if (clickedGlyph && hasChildren(node)) {\n toggleExpanded(node.id)\n return\n }\n\n if (!multi) {\n selectNode(node.id, 'replace')\n } else if (event.shiftKey) {\n selectNode(node.id, 'range')\n } else {\n selectNode(node.id, event.ctrlKey || event.metaKey ? 'toggle' : 'replace')\n }\n\n if (expandOnRowClick && hasChildren(node)) toggleExpanded(node.id)\n },\n [expandOnRowClick, multi, selectNode, toggleExpanded],\n )\n\n const classes = ['vgui-tree']\n classes.push(`vgui-tree--${variant}`)\n if (multi) classes.push('vgui-tree--multi')\n if (nodes.length === 0) classes.push('vgui-tree--empty')\n if (className) classes.push(className)\n\n const treeStyle = {\n ...style,\n '--vgui-tree-indent': `${indentSize ?? DEFAULT_INDENT}px`,\n } as CSSProperties\n\n const renderNodes = (children: TreeNode[], level: number): ReactNode =>\n children.map((node) => {\n const nodeHasChildren = hasChildren(node)\n const expanded = nodeHasChildren && expandedSet.has(node.id)\n const isSelected = selectedSet.has(node.id)\n const isActive = node.id === activeNodeId\n const nodeClasses = ['vgui-tree__node']\n if (isSelected) nodeClasses.push('vgui-tree__node--selected')\n if (node.disabled) nodeClasses.push('vgui-tree__node--disabled')\n\n return (\n <Fragment key={node.id}>\n <div\n id={`${baseId}-node-${node.id}`}\n ref={(element) => setNodeRef(node.id, element)}\n className={nodeClasses.join(' ')}\n role=\"treeitem\"\n aria-level={level}\n // `aria-expanded` only on nodes that have children: announcing\n // \"collapsed\" for a leaf is a lie.\n aria-expanded={nodeHasChildren ? expanded : undefined}\n aria-selected={isSelected}\n aria-disabled={node.disabled ? true : undefined}\n // Roving tabindex: the active node is the tree's single tab stop.\n tabIndex={isActive ? 0 : -1}\n style={{ '--vgui-tree-level': level - 1 } as CSSProperties}\n // The recipe truncates long labels, so the full text is kept\n // reachable as a tooltip.\n title={node.label}\n onClick={(event) => handleNodeClick(event, node)}\n onFocus={() => {\n // A disabled node may hold the tab stop — the arrow keys walk\n // onto it so its existence is discoverable — but activation is\n // guarded separately in `selectByKeyboard`.\n setActiveId(node.id)\n }}\n >\n <span\n className={`vgui-tree__image${\n nodeHasChildren\n ? expanded\n ? ' vgui-tree__image--expanded'\n : ' vgui-tree__image--collapsed'\n : ' vgui-tree__image--leaf'\n }`}\n aria-hidden=\"true\"\n />\n <span className=\"vgui-tree__text\">{node.label}</span>\n </div>\n {/* `role=\"group\"` is what makes `aria-level` mean anything; a\n collapsed subtree is unmounted rather than hidden so it leaves the\n accessibility tree with its `aria-expanded=\"false\"` parent. */}\n {expanded ? (\n <div className=\"vgui-tree__group\" role=\"group\">\n {renderNodes(node.children ?? [], level + 1)}\n </div>\n ) : null}\n </Fragment>\n )\n })\n\n const isEmpty = nodes.length === 0\n\n return (\n <div ref={forwardedRef} className={classes.join(' ')} style={treeStyle} {...rest}>\n {isEmpty ? (\n // An empty `tree` is itself an `aria-required-children` violation, so\n // the empty state drops the widget and announces through a status\n // region instead of a dangling role.\n <div className=\"vgui-tree__subpanel vgui-scroll-surface\">\n <p className=\"vgui-tree__empty\" role=\"status\">\n {emptyMessage}\n </p>\n </div>\n ) : (\n <>\n <div\n className=\"vgui-tree__subpanel vgui-scroll-surface\"\n role=\"tree\"\n aria-label={ariaLabelledBy ? undefined : (ariaLabel ?? label ?? 'Tree')}\n aria-labelledby={ariaLabelledBy}\n aria-multiselectable={multi ? true : undefined}\n onKeyDown={handleKeyDown}\n >\n {renderNodes(nodes, 1)}\n </div>\n <div className=\"vgui-tree__live\" role=\"status\">\n {announcement}\n </div>\n </>\n )}\n </div>\n )\n})\n\nTreeView.displayName = 'TreeView'\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TreeView/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Green Steam window (VGUI `Frame`).
|
|
3
|
+
*
|
|
4
|
+
* A beveled surface with a draggable caption strip, a control cluster and a
|
|
5
|
+
* resize grip. Positioning is deliberately left to the consumer: the component
|
|
6
|
+
* renders an unstyled-by-default box, it never bakes in `position: fixed`.
|
|
7
|
+
*
|
|
8
|
+
* Two values from `docs/components/Window.md` have no token — the `220x120`
|
|
9
|
+
* minimum box and the `16px` icon box — so they are routed through local
|
|
10
|
+
* properties an application shell can override, the same pattern `Panel` uses
|
|
11
|
+
* for its `max-width`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
.vgui-window {
|
|
15
|
+
--vgui-window-min-width: 220px;
|
|
16
|
+
--vgui-window-min-height: 120px;
|
|
17
|
+
--vgui-window-icon-size: 16px;
|
|
18
|
+
--vgui-window-padding: 10px;
|
|
19
|
+
--vgui-window-titlebar-gap: 8px;
|
|
20
|
+
--vgui-window-control-gap: 2px;
|
|
21
|
+
--vgui-window-grip-offset: 2px;
|
|
22
|
+
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
position: relative;
|
|
25
|
+
background-color: var(--vgui-surface);
|
|
26
|
+
border-top: var(--vgui-bevel-width) solid var(--vgui-bevel-light);
|
|
27
|
+
border-left: var(--vgui-bevel-width) solid var(--vgui-bevel-light);
|
|
28
|
+
border-bottom: var(--vgui-bevel-width) solid var(--vgui-bevel-dark);
|
|
29
|
+
border-right: var(--vgui-bevel-width) solid var(--vgui-bevel-dark);
|
|
30
|
+
padding: var(--vgui-window-padding);
|
|
31
|
+
min-width: var(--vgui-window-min-width);
|
|
32
|
+
min-height: var(--vgui-window-min-height);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* `Frame:FrameFocus` — Valve lights the frame border while the window has focus. */
|
|
36
|
+
.vgui-window--focused {
|
|
37
|
+
border-top-color: var(--vgui-bevel-light-strong);
|
|
38
|
+
border-left-color: var(--vgui-bevel-light-strong);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.vgui-window--maximized {
|
|
42
|
+
inset: 0;
|
|
43
|
+
width: 100%;
|
|
44
|
+
height: 100%;
|
|
45
|
+
border-radius: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* The caption strip: uppercase + 2px tracking is the theme's signature. */
|
|
49
|
+
.vgui-window__titlebar {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: var(--vgui-window-titlebar-gap);
|
|
53
|
+
height: var(--vgui-titlebar-height);
|
|
54
|
+
line-height: var(--vgui-titlebar-height);
|
|
55
|
+
margin-bottom: 1em;
|
|
56
|
+
cursor: move;
|
|
57
|
+
user-select: none;
|
|
58
|
+
touch-action: none;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* The ring is the accessible default; `.vgui-crisp` opts into Valve's own. */
|
|
62
|
+
.vgui-window__titlebar:focus-visible,
|
|
63
|
+
.vgui-window__grip:focus-visible {
|
|
64
|
+
outline: var(--vgui-focus-ring);
|
|
65
|
+
outline-offset: var(--vgui-focus-ring-offset);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.vgui-crisp .vgui-window__titlebar:focus-visible,
|
|
69
|
+
.vgui-crisp .vgui-window__grip:focus-visible {
|
|
70
|
+
outline: var(--vgui-focus-ring-valve);
|
|
71
|
+
outline-offset: var(--vgui-focus-ring-valve-offset);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.vgui-window__icon {
|
|
75
|
+
flex: 0 0 auto;
|
|
76
|
+
width: var(--vgui-window-icon-size);
|
|
77
|
+
height: var(--vgui-window-icon-size);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* The caption stays at `--vgui-text-strong` in both states on purpose. The
|
|
82
|
+
* states table dims it to `--vgui-text-muted`, but the accessibility section
|
|
83
|
+
* measures that at 3.0:1 over `--vgui-surface` — below AA for 12px text — and
|
|
84
|
+
* resolves the conflict: dim the glyphs, never the caption.
|
|
85
|
+
*/
|
|
86
|
+
.vgui-window__title {
|
|
87
|
+
flex: 1 1 auto;
|
|
88
|
+
text-transform: uppercase;
|
|
89
|
+
letter-spacing: var(--vgui-chrome-tracking);
|
|
90
|
+
font-size: var(--vgui-font-size-small);
|
|
91
|
+
font-weight: bold;
|
|
92
|
+
color: var(--vgui-text-strong);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.vgui-window__controls {
|
|
96
|
+
display: flex;
|
|
97
|
+
gap: var(--vgui-window-control-gap);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.vgui-window__control {
|
|
101
|
+
position: relative;
|
|
102
|
+
width: var(--vgui-frame-control-size);
|
|
103
|
+
height: var(--vgui-frame-control-size);
|
|
104
|
+
padding: 0;
|
|
105
|
+
/* Transparent rather than none — the box has to survive the hover bevel. */
|
|
106
|
+
border: var(--vgui-bevel-width) solid transparent;
|
|
107
|
+
background-color: transparent;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/*
|
|
112
|
+
* 20x20 is under the 24x24 minimum target size, so an invisible overlay grows
|
|
113
|
+
* the hit area without moving the glyph.
|
|
114
|
+
*/
|
|
115
|
+
.vgui-window__control::before {
|
|
116
|
+
content: '';
|
|
117
|
+
position: absolute;
|
|
118
|
+
inset: calc((var(--vgui-frame-control-size) - var(--vgui-min-target-size)) / 2);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.vgui-window__control:hover {
|
|
122
|
+
border-color: var(--vgui-bevel-light);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.vgui-window__control:active {
|
|
126
|
+
border-color: var(--vgui-bevel-dark);
|
|
127
|
+
background-color: var(--vgui-surface-light);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.vgui-window__control:focus-visible {
|
|
131
|
+
outline: var(--vgui-focus-ring);
|
|
132
|
+
outline-offset: var(--vgui-focus-ring-offset);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.vgui-crisp .vgui-window__control:focus-visible {
|
|
136
|
+
outline: var(--vgui-focus-ring-valve);
|
|
137
|
+
outline-offset: var(--vgui-focus-ring-valve-offset);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
* Glyphs are painted as a mask over `background-color`, so they follow the
|
|
142
|
+
* clay tokens under every theme variant instead of being baked into the data
|
|
143
|
+
* URI — a `data:` URI cannot read a CSS variable.
|
|
144
|
+
*/
|
|
145
|
+
.vgui-window__glyph {
|
|
146
|
+
display: block;
|
|
147
|
+
width: 7px;
|
|
148
|
+
height: 7px;
|
|
149
|
+
margin: 0 auto;
|
|
150
|
+
background-color: var(--vgui-clay-glyph-dim);
|
|
151
|
+
mask-image: none;
|
|
152
|
+
mask-repeat: no-repeat;
|
|
153
|
+
mask-position: center;
|
|
154
|
+
mask-size: contain;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.vgui-window--focused .vgui-window__glyph,
|
|
158
|
+
.vgui-window__control:hover .vgui-window__glyph,
|
|
159
|
+
.vgui-window__control:active .vgui-window__glyph {
|
|
160
|
+
background-color: var(--vgui-clay-glyph);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.vgui-window__glyph[data-glyph='minimize'] {
|
|
164
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 7 7'%3E%3Crect x='0.5' y='3' width='6' height='1.5' fill='white'/%3E%3C/svg%3E");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.vgui-window__glyph[data-glyph='maximize'] {
|
|
168
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 7 7'%3E%3Crect x='0.5' y='0.5' width='6' height='6' fill='none' stroke='white' stroke-width='1.5'/%3E%3C/svg%3E");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.vgui-window__glyph[data-glyph='restore'] {
|
|
172
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 7 7'%3E%3Cpath d='M1.5 2.5 h4 v4 h-4 z' fill='none' stroke='white'/%3E%3Cpath d='M2.5 2.5 v-2 h3 v3' fill='none' stroke='white'/%3E%3C/svg%3E");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.vgui-window__glyph[data-glyph='close'] {
|
|
176
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 7 7'%3E%3Cpath d='M0.5 0.5 L6.5 6.5 M6.5 0.5 L0.5 6.5' fill='none' stroke='white' stroke-width='1.5'/%3E%3C/svg%3E");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.vgui-window__body {
|
|
180
|
+
min-width: 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* `resizer` — three diagonal rules of the VGUI grip sprite. */
|
|
184
|
+
.vgui-window__grip {
|
|
185
|
+
position: absolute;
|
|
186
|
+
right: var(--vgui-window-grip-offset);
|
|
187
|
+
bottom: var(--vgui-window-grip-offset);
|
|
188
|
+
width: var(--vgui-resize-grip);
|
|
189
|
+
height: var(--vgui-resize-grip);
|
|
190
|
+
cursor: nwse-resize;
|
|
191
|
+
background-color: var(--vgui-clay-glyph-dim);
|
|
192
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M11 1 L1 11 M11 5 L5 11 M11 9 L9 11' fill='none' stroke='white'/%3E%3C/svg%3E");
|
|
193
|
+
mask-repeat: no-repeat;
|
|
194
|
+
mask-position: center;
|
|
195
|
+
mask-size: contain;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.vgui-window__grip:hover,
|
|
199
|
+
.vgui-window--focused .vgui-window__grip {
|
|
200
|
+
background-color: var(--vgui-clay-glyph);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/*
|
|
204
|
+
* Component-scoped copy of the house screen-reader-only helper, so the control
|
|
205
|
+
* names travel with the component rather than depending on a global stylesheet.
|
|
206
|
+
*/
|
|
207
|
+
.vgui-window .vgui-visually-hidden {
|
|
208
|
+
position: absolute;
|
|
209
|
+
width: 1px;
|
|
210
|
+
height: 1px;
|
|
211
|
+
margin: -1px;
|
|
212
|
+
padding: 0;
|
|
213
|
+
overflow: hidden;
|
|
214
|
+
clip: rect(0 0 0 0);
|
|
215
|
+
clip-path: inset(50%);
|
|
216
|
+
white-space: nowrap;
|
|
217
|
+
border: 0;
|
|
218
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import './Window.css';
|
|
3
|
+
export interface WindowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'onDrag'> {
|
|
4
|
+
/** Uppercase text in the title bar. */
|
|
5
|
+
title: ReactNode;
|
|
6
|
+
/** Optional 16px application glyph shown before the title. */
|
|
7
|
+
icon?: ReactNode;
|
|
8
|
+
/** Show the minimize control. */
|
|
9
|
+
minimizable?: boolean;
|
|
10
|
+
/** Show the maximize control. */
|
|
11
|
+
maximizable?: boolean;
|
|
12
|
+
/** Show the close control. */
|
|
13
|
+
closable?: boolean;
|
|
14
|
+
/** Show the bottom-right resize grip. */
|
|
15
|
+
resizable?: boolean;
|
|
16
|
+
/** Render the focused border and glyph colours. */
|
|
17
|
+
focused?: boolean;
|
|
18
|
+
/** Show the maximize glyph in its toggled state. */
|
|
19
|
+
maximized?: boolean;
|
|
20
|
+
/** Fires when a control is activated; `close` also fires on Escape. */
|
|
21
|
+
onAction?: (action: 'minimize' | 'maximize' | 'restore' | 'close') => void;
|
|
22
|
+
/** Called continuously while dragging the title bar, with the pointer delta. */
|
|
23
|
+
onDrag?: (deltaX: number, deltaY: number) => void;
|
|
24
|
+
/** Accessible label for the control group. */
|
|
25
|
+
controlsLabel?: string;
|
|
26
|
+
/** Native tooltip attribute — `title` is taken by the caption. */
|
|
27
|
+
tooltip?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare const Window: import("react").ForwardRefExoticComponent<WindowProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
30
|
+
//# sourceMappingURL=Window.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Window.d.ts","sourceRoot":"","sources":["../../../src/components/Window/Window.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAGd,SAAS,EACV,MAAM,OAAO,CAAA;AAEd,OAAO,cAAc,CAAA;AAarB,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC3F,uCAAuC;IACvC,KAAK,EAAE,SAAS,CAAA;IAChB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,iCAAiC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,iCAAiC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,yCAAyC;IACzC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,mDAAmD;IACnD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,KAAK,IAAI,CAAA;IAC1E,gFAAgF;IAChF,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACjD,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,eAAO,MAAM,MAAM,wGAwLjB,CAAA"}
|