@keyfunc/keybricks-ds 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/DEPLOY.md +204 -0
- package/DESIGN.md +269 -0
- package/INTEGRATION.md +417 -0
- package/SKILL.md +32 -0
- package/_adherence.oxlintrc.json +1469 -0
- package/_scripts/build-index.mjs +80 -0
- package/_scripts/build-tokens.mjs +135 -0
- package/assets/icons/icon-index.js +1 -0
- package/assets/icons/icon-index.json +1 -0
- package/assets/icons/unicons-index.js +1 -0
- package/assets/icons/unicons-index.json +1 -0
- package/assets/keybricks-logo.png +0 -0
- package/components/_cards/accordion.card.html +32 -0
- package/components/_cards/action-chip.card.html +22 -0
- package/components/_cards/activity.card.html +34 -0
- package/components/_cards/alert.card.html +32 -0
- package/components/_cards/app-chrome.card.html +36 -0
- package/components/_cards/banner.card.html +21 -0
- package/components/_cards/brand-visual.card.html +43 -0
- package/components/_cards/breadcrumb.card.html +23 -0
- package/components/_cards/button-1-base.card.html +39 -0
- package/components/_cards/button-2-states.card.html +56 -0
- package/components/_cards/button-3-sizes-icons.card.html +32 -0
- package/components/_cards/button-4-group.card.html +47 -0
- package/components/_cards/canvas-zoom-controls.card.html +19 -0
- package/components/_cards/cards-indicators.card.html +59 -0
- package/components/_cards/charts-1-sparkline.card.html +55 -0
- package/components/_cards/charts-10-gauge.card.html +31 -0
- package/components/_cards/charts-11-funnel.card.html +28 -0
- package/components/_cards/charts-12-treemap.card.html +23 -0
- package/components/_cards/charts-14-radar.card.html +31 -0
- package/components/_cards/charts-15-distribution.card.html +32 -0
- package/components/_cards/charts-16-pareto.card.html +25 -0
- package/components/_cards/charts-17-candlestick.card.html +34 -0
- package/components/_cards/charts-18-sankey.card.html +29 -0
- package/components/_cards/charts-19-chord.card.html +31 -0
- package/components/_cards/charts-2-line.card.html +46 -0
- package/components/_cards/charts-20-sunburst.card.html +38 -0
- package/components/_cards/charts-21-worldmap.card.html +33 -0
- package/components/_cards/charts-22-gantt.card.html +37 -0
- package/components/_cards/charts-23-bar3d.card.html +26 -0
- package/components/_cards/charts-24-pie3d.card.html +32 -0
- package/components/_cards/charts-25-surface3d.card.html +24 -0
- package/components/_cards/charts-26-scatter3d.card.html +24 -0
- package/components/_cards/charts-27-ribbon3d.card.html +23 -0
- package/components/_cards/charts-3-bar.card.html +71 -0
- package/components/_cards/charts-4-barlist.card.html +30 -0
- package/components/_cards/charts-5-donut.card.html +35 -0
- package/components/_cards/charts-6-scatter.card.html +26 -0
- package/components/_cards/charts-7-heatmap.card.html +28 -0
- package/components/_cards/charts-8-waterfall.card.html +25 -0
- package/components/_cards/charts-9-bullet.card.html +31 -0
- package/components/_cards/charts-99-fullpage.card.html +194 -0
- package/components/_cards/checkbox.card.html +34 -0
- package/components/_cards/codeblock.card.html +19 -0
- package/components/_cards/combobox.card.html +25 -0
- package/components/_cards/context-menu.card.html +21 -0
- package/components/_cards/datatable.card.html +48 -0
- package/components/_cards/date-picker.card.html +31 -0
- package/components/_cards/diffvalue.card.html +21 -0
- package/components/_cards/divider.card.html +24 -0
- package/components/_cards/drawer.card.html +30 -0
- package/components/_cards/dropdown-menu.card.html +38 -0
- package/components/_cards/dropzone.card.html +22 -0
- package/components/_cards/empty-state.card.html +29 -0
- package/components/_cards/filter-bar.card.html +34 -0
- package/components/_cards/icon-button.card.html +27 -0
- package/components/_cards/icons-1-core.card.html +49 -0
- package/components/_cards/icons-2-feather.card.html +39 -0
- package/components/_cards/icons-3-unicons.card.html +44 -0
- package/components/_cards/input.card.html +25 -0
- package/components/_cards/kbd.card.html +20 -0
- package/components/_cards/keyvalue.card.html +23 -0
- package/components/_cards/listrow.card.html +30 -0
- package/components/_cards/loading.card.html +33 -0
- package/components/_cards/log-viewer.card.html +23 -0
- package/components/_cards/modal.card.html +35 -0
- package/components/_cards/multi-select.card.html +26 -0
- package/components/_cards/number-stepper.card.html +24 -0
- package/components/_cards/pagination.card.html +25 -0
- package/components/_cards/people.card.html +46 -0
- package/components/_cards/pipeline-node.card.html +76 -0
- package/components/_cards/pipeline-palette.card.html +54 -0
- package/components/_cards/search.card.html +23 -0
- package/components/_cards/select.card.html +23 -0
- package/components/_cards/slider.card.html +25 -0
- package/components/_cards/smart-table.card.html +37 -0
- package/components/_cards/spreadsheet.card.html +31 -0
- package/components/_cards/stepper.card.html +25 -0
- package/components/_cards/tabs.card.html +25 -0
- package/components/_cards/tag.card.html +26 -0
- package/components/_cards/textarea.card.html +27 -0
- package/components/_cards/timeline.card.html +32 -0
- package/components/_cards/toast.card.html +33 -0
- package/components/_cards/toggles.card.html +68 -0
- package/components/_cards/tooltip.card.html +22 -0
- package/components/_cards/tree.card.html +150 -0
- package/components/_shared/frame-theme.js +16 -0
- package/components/_shared/src-brand.js +1 -0
- package/components/_shared/src-buttons.js +1 -0
- package/components/_shared/src-charts.js +1 -0
- package/components/_shared/src-data.js +1 -0
- package/components/_shared/src-display.js +1 -0
- package/components/_shared/src-feedback.js +1 -0
- package/components/_shared/src-forms.js +1 -0
- package/components/_shared/src-icons.js +1 -0
- package/components/_shared/src-navigation.js +1 -0
- package/components/_shared/src-pipeline.js +1 -0
- package/components/_shared/src-tabs.js +1 -0
- package/components/_shared/svg-download.js +19 -0
- package/components/_shared/view-source.js +185 -0
- package/components/_vue/index.mjs +114 -0
- package/components/brand/BrandVisual.d.ts +17 -0
- package/components/brand/BrandVisual.jsx +29 -0
- package/components/brand/BrandVisual.vue +34 -0
- package/components/brand/_dir.card.html +13 -0
- package/components/brand/brandMotifs.js +210 -0
- package/components/buttons/ActionChip.d.ts +10 -0
- package/components/buttons/ActionChip.jsx +20 -0
- package/components/buttons/ActionChip.vue +18 -0
- package/components/buttons/Button.d.ts +40 -0
- package/components/buttons/Button.jsx +101 -0
- package/components/buttons/Button.vue +87 -0
- package/components/buttons/IconButton.d.ts +26 -0
- package/components/buttons/IconButton.jsx +28 -0
- package/components/buttons/IconButton.vue +21 -0
- package/components/buttons/_dir.card.html +13 -0
- package/components/buttons/buttons.prompt.md +19 -0
- package/components/charts/Bar3D.d.ts +15 -0
- package/components/charts/Bar3D.jsx +93 -0
- package/components/charts/Bar3D.vue +79 -0
- package/components/charts/BarChart.d.ts +23 -0
- package/components/charts/BarChart.jsx +135 -0
- package/components/charts/BarChart.vue +139 -0
- package/components/charts/BarList.d.ts +13 -0
- package/components/charts/BarList.jsx +31 -0
- package/components/charts/BarList.vue +25 -0
- package/components/charts/BoxPlot.d.ts +18 -0
- package/components/charts/BoxPlot.jsx +62 -0
- package/components/charts/BoxPlot.vue +58 -0
- package/components/charts/BulletChart.d.ts +12 -0
- package/components/charts/BulletChart.jsx +41 -0
- package/components/charts/BulletChart.vue +33 -0
- package/components/charts/CandlestickChart.d.ts +15 -0
- package/components/charts/CandlestickChart.jsx +63 -0
- package/components/charts/CandlestickChart.vue +62 -0
- package/components/charts/ChartKit.d.ts +8 -0
- package/components/charts/ChartKit.jsx +76 -0
- package/components/charts/ChartKit.vue +47 -0
- package/components/charts/ChordChart.d.ts +13 -0
- package/components/charts/ChordChart.jsx +55 -0
- package/components/charts/ChordChart.vue +54 -0
- package/components/charts/DonutChart.d.ts +15 -0
- package/components/charts/DonutChart.jsx +73 -0
- package/components/charts/DonutChart.vue +61 -0
- package/components/charts/FunnelChart.d.ts +11 -0
- package/components/charts/FunnelChart.jsx +37 -0
- package/components/charts/FunnelChart.vue +29 -0
- package/components/charts/GanttChart.d.ts +20 -0
- package/components/charts/GanttChart.jsx +80 -0
- package/components/charts/GanttChart.vue +60 -0
- package/components/charts/GaugeChart.d.ts +13 -0
- package/components/charts/GaugeChart.jsx +33 -0
- package/components/charts/GaugeChart.vue +29 -0
- package/components/charts/HeatmapChart.d.ts +13 -0
- package/components/charts/HeatmapChart.jsx +62 -0
- package/components/charts/HeatmapChart.vue +38 -0
- package/components/charts/Histogram.d.ts +16 -0
- package/components/charts/Histogram.jsx +56 -0
- package/components/charts/Histogram.vue +53 -0
- package/components/charts/LineChart.d.ts +17 -0
- package/components/charts/LineChart.jsx +84 -0
- package/components/charts/LineChart.vue +86 -0
- package/components/charts/ParetoChart.d.ts +13 -0
- package/components/charts/ParetoChart.jsx +54 -0
- package/components/charts/ParetoChart.vue +55 -0
- package/components/charts/Pie3D.d.ts +17 -0
- package/components/charts/Pie3D.jsx +145 -0
- package/components/charts/Pie3D.vue +115 -0
- package/components/charts/RadarChart.d.ts +14 -0
- package/components/charts/RadarChart.jsx +79 -0
- package/components/charts/RadarChart.vue +73 -0
- package/components/charts/Ribbon3D.d.ts +13 -0
- package/components/charts/Ribbon3D.jsx +89 -0
- package/components/charts/Ribbon3D.vue +79 -0
- package/components/charts/SankeyChart.d.ts +15 -0
- package/components/charts/SankeyChart.jsx +59 -0
- package/components/charts/SankeyChart.vue +53 -0
- package/components/charts/Scatter3D.d.ts +12 -0
- package/components/charts/Scatter3D.jsx +69 -0
- package/components/charts/Scatter3D.vue +59 -0
- package/components/charts/ScatterChart.d.ts +12 -0
- package/components/charts/ScatterChart.jsx +53 -0
- package/components/charts/ScatterChart.vue +57 -0
- package/components/charts/Sparkline.d.ts +19 -0
- package/components/charts/Sparkline.jsx +63 -0
- package/components/charts/Sparkline.vue +54 -0
- package/components/charts/SunburstChart.d.ts +12 -0
- package/components/charts/SunburstChart.jsx +61 -0
- package/components/charts/SunburstChart.vue +60 -0
- package/components/charts/Surface3D.d.ts +13 -0
- package/components/charts/Surface3D.jsx +70 -0
- package/components/charts/Surface3D.vue +63 -0
- package/components/charts/TreemapChart.d.ts +11 -0
- package/components/charts/TreemapChart.jsx +35 -0
- package/components/charts/TreemapChart.vue +27 -0
- package/components/charts/WaterfallChart.d.ts +11 -0
- package/components/charts/WaterfallChart.jsx +59 -0
- package/components/charts/WaterfallChart.vue +46 -0
- package/components/charts/WorldMap.d.ts +21 -0
- package/components/charts/WorldMap.jsx +107 -0
- package/components/charts/WorldMap.vue +73 -0
- package/components/charts/_dir.card.html +7 -0
- package/components/charts/chart-3d.js +84 -0
- package/components/charts/chart-geometry.js +223 -0
- package/components/charts/charts.prompt.md +24 -0
- package/components/data/CodeBlock.d.ts +9 -0
- package/components/data/CodeBlock.jsx +19 -0
- package/components/data/CodeBlock.vue +22 -0
- package/components/data/DataTable.d.ts +28 -0
- package/components/data/DataTable.jsx +32 -0
- package/components/data/DataTable.vue +25 -0
- package/components/data/DiffValue.d.ts +11 -0
- package/components/data/DiffValue.jsx +15 -0
- package/components/data/DiffValue.vue +13 -0
- package/components/data/FilterBar.d.ts +16 -0
- package/components/data/FilterBar.jsx +22 -0
- package/components/data/FilterBar.vue +23 -0
- package/components/data/KeyValue.d.ts +16 -0
- package/components/data/KeyValue.jsx +25 -0
- package/components/data/KeyValue.vue +26 -0
- package/components/data/ListRow.d.ts +14 -0
- package/components/data/ListRow.jsx +25 -0
- package/components/data/ListRow.vue +25 -0
- package/components/data/Pagination.d.ts +12 -0
- package/components/data/Pagination.jsx +27 -0
- package/components/data/Pagination.vue +24 -0
- package/components/data/SmartTable.d.ts +30 -0
- package/components/data/SmartTable.jsx +73 -0
- package/components/data/SmartTable.vue +74 -0
- package/components/data/Spreadsheet.d.ts +33 -0
- package/components/data/Spreadsheet.jsx +110 -0
- package/components/data/Spreadsheet.vue +95 -0
- package/components/data/_dir.card.html +13 -0
- package/components/data/data.prompt.md +33 -0
- package/components/display/Avatar.d.ts +21 -0
- package/components/display/Avatar.jsx +31 -0
- package/components/display/Avatar.vue +34 -0
- package/components/display/Badge.d.ts +27 -0
- package/components/display/Badge.jsx +40 -0
- package/components/display/Badge.vue +47 -0
- package/components/display/Card.d.ts +27 -0
- package/components/display/Card.jsx +27 -0
- package/components/display/Card.vue +28 -0
- package/components/display/Divider.d.ts +8 -0
- package/components/display/Divider.jsx +14 -0
- package/components/display/Divider.vue +13 -0
- package/components/display/Kbd.d.ts +7 -0
- package/components/display/Kbd.jsx +12 -0
- package/components/display/Kbd.vue +9 -0
- package/components/display/ProgressBar.d.ts +42 -0
- package/components/display/ProgressBar.jsx +70 -0
- package/components/display/ProgressBar.vue +69 -0
- package/components/display/Tag.d.ts +9 -0
- package/components/display/Tag.jsx +15 -0
- package/components/display/Tag.vue +15 -0
- package/components/display/_dir.card.html +13 -0
- package/components/display/display.prompt.md +30 -0
- package/components/feedback/Accordion.d.ts +13 -0
- package/components/feedback/Accordion.jsx +21 -0
- package/components/feedback/Accordion.vue +22 -0
- package/components/feedback/ActivityItem.d.ts +25 -0
- package/components/feedback/ActivityItem.jsx +39 -0
- package/components/feedback/ActivityItem.vue +46 -0
- package/components/feedback/Alert.d.ts +36 -0
- package/components/feedback/Alert.jsx +48 -0
- package/components/feedback/Alert.vue +59 -0
- package/components/feedback/Banner.d.ts +15 -0
- package/components/feedback/Banner.jsx +25 -0
- package/components/feedback/Banner.vue +25 -0
- package/components/feedback/ContextMenu.d.ts +8 -0
- package/components/feedback/ContextMenu.jsx +18 -0
- package/components/feedback/ContextMenu.vue +19 -0
- package/components/feedback/Drawer.d.ts +18 -0
- package/components/feedback/Drawer.jsx +26 -0
- package/components/feedback/Drawer.vue +24 -0
- package/components/feedback/Menu.d.ts +10 -0
- package/components/feedback/Menu.jsx +24 -0
- package/components/feedback/Menu.vue +21 -0
- package/components/feedback/Modal.d.ts +38 -0
- package/components/feedback/Modal.jsx +69 -0
- package/components/feedback/Modal.vue +65 -0
- package/components/feedback/Stepper.d.ts +9 -0
- package/components/feedback/Stepper.jsx +24 -0
- package/components/feedback/Stepper.vue +18 -0
- package/components/feedback/Timeline.d.ts +15 -0
- package/components/feedback/Timeline.jsx +27 -0
- package/components/feedback/Timeline.vue +21 -0
- package/components/feedback/_dir.card.html +13 -0
- package/components/feedback/feedback.prompt.md +35 -0
- package/components/forms/Checkbox.d.ts +13 -0
- package/components/forms/Checkbox.jsx +17 -0
- package/components/forms/Checkbox.vue +18 -0
- package/components/forms/Combobox.d.ts +13 -0
- package/components/forms/Combobox.jsx +34 -0
- package/components/forms/Combobox.vue +35 -0
- package/components/forms/DatePicker.d.ts +12 -0
- package/components/forms/DatePicker.jsx +48 -0
- package/components/forms/DatePicker.vue +46 -0
- package/components/forms/Dropzone.d.ts +20 -0
- package/components/forms/Dropzone.jsx +36 -0
- package/components/forms/Dropzone.vue +28 -0
- package/components/forms/Field.d.ts +13 -0
- package/components/forms/Field.jsx +13 -0
- package/components/forms/Field.vue +12 -0
- package/components/forms/Input.d.ts +18 -0
- package/components/forms/Input.jsx +35 -0
- package/components/forms/Input.vue +38 -0
- package/components/forms/MultiSelect.d.ts +14 -0
- package/components/forms/MultiSelect.jsx +33 -0
- package/components/forms/MultiSelect.vue +36 -0
- package/components/forms/NumberStepper.d.ts +13 -0
- package/components/forms/NumberStepper.jsx +15 -0
- package/components/forms/NumberStepper.vue +17 -0
- package/components/forms/Radio.vue +16 -0
- package/components/forms/RadioGroup.vue +11 -0
- package/components/forms/SearchInput.d.ts +12 -0
- package/components/forms/SearchInput.jsx +17 -0
- package/components/forms/SearchInput.vue +14 -0
- package/components/forms/Select.d.ts +14 -0
- package/components/forms/Select.jsx +17 -0
- package/components/forms/Select.vue +16 -0
- package/components/forms/Slider.d.ts +15 -0
- package/components/forms/Slider.jsx +21 -0
- package/components/forms/Slider.vue +23 -0
- package/components/forms/Switch.d.ts +31 -0
- package/components/forms/Switch.jsx +40 -0
- package/components/forms/Switch.vue +16 -0
- package/components/forms/_dir.card.html +13 -0
- package/components/forms/forms.prompt.md +23 -0
- package/components/icons/Icon.d.ts +19 -0
- package/components/icons/Icon.jsx +93 -0
- package/components/icons/Icon.prompt.md +8 -0
- package/components/icons/Icon.vue +12 -0
- package/components/icons/Unicon.d.ts +12 -0
- package/components/icons/Unicon.jsx +16 -0
- package/components/icons/Unicon.vue +15 -0
- package/components/icons/_dir.card.html +13 -0
- package/components/icons/feather-paths.js +2 -0
- package/components/icons/icon-paths.js +73 -0
- package/components/index.d.ts +87 -0
- package/components/index.js +110 -0
- package/components/navigation/Breadcrumb.d.ts +24 -0
- package/components/navigation/Breadcrumb.jsx +120 -0
- package/components/navigation/Breadcrumb.vue +94 -0
- package/components/navigation/DocTree.d.ts +18 -0
- package/components/navigation/DocTree.jsx +37 -0
- package/components/navigation/DocTree.vue +25 -0
- package/components/navigation/Sidebar.d.ts +29 -0
- package/components/navigation/Sidebar.jsx +75 -0
- package/components/navigation/Sidebar.vue +54 -0
- package/components/navigation/TopBar.d.ts +27 -0
- package/components/navigation/TopBar.jsx +44 -0
- package/components/navigation/TopBar.vue +51 -0
- package/components/navigation/Tree.d.ts +83 -0
- package/components/navigation/Tree.jsx +347 -0
- package/components/navigation/Tree.vue +258 -0
- package/components/navigation/_dir.card.html +13 -0
- package/components/navigation/navigation.prompt.md +22 -0
- package/components/pipeline/ConfigPanel.d.ts +18 -0
- package/components/pipeline/ConfigPanel.jsx +30 -0
- package/components/pipeline/ConfigPanel.vue +32 -0
- package/components/pipeline/DataPreview.d.ts +14 -0
- package/components/pipeline/DataPreview.jsx +24 -0
- package/components/pipeline/DataPreview.vue +21 -0
- package/components/pipeline/NodeCard.d.ts +47 -0
- package/components/pipeline/NodeCard.jsx +97 -0
- package/components/pipeline/NodeCard.vue +75 -0
- package/components/pipeline/PaletteItem.vue +15 -0
- package/components/pipeline/PaletteListItem.vue +15 -0
- package/components/pipeline/PalettePanel.d.ts +20 -0
- package/components/pipeline/PalettePanel.jsx +74 -0
- package/components/pipeline/PalettePanel.vue +45 -0
- package/components/pipeline/PipelineCanvas.d.ts +25 -0
- package/components/pipeline/PipelineCanvas.jsx +94 -0
- package/components/pipeline/PipelineCanvas.vue +73 -0
- package/components/pipeline/ZoomControls.d.ts +9 -0
- package/components/pipeline/ZoomControls.jsx +15 -0
- package/components/pipeline/ZoomControls.vue +15 -0
- package/components/pipeline/_dir.card.html +13 -0
- package/components/pipeline/pipeline.prompt.md +22 -0
- package/components/tabs/SegmentedControl.d.ts +11 -0
- package/components/tabs/SegmentedControl.jsx +21 -0
- package/components/tabs/SegmentedControl.vue +15 -0
- package/components/tabs/Tabs.d.ts +13 -0
- package/components/tabs/Tabs.jsx +52 -0
- package/components/tabs/Tabs.vue +39 -0
- package/components/tabs/ViewToggle.d.ts +8 -0
- package/components/tabs/ViewToggle.jsx +12 -0
- package/components/tabs/ViewToggle.vue +12 -0
- package/components/tabs/_dir.card.html +13 -0
- package/components/tabs/tabs.prompt.md +14 -0
- package/package.json +76 -0
- package/readme.md +150 -0
- package/styles.css +10 -0
- package/tokens/alpha.css +27 -0
- package/tokens/charts.css +55 -0
- package/tokens/colors-extended.css +40 -0
- package/tokens/colors.css +39 -0
- package/tokens/dark.css +96 -0
- package/tokens/fonts.css +4 -0
- package/tokens/interaction.css +48 -0
- package/tokens/layout.css +30 -0
- package/tokens/motion.css +22 -0
- package/tokens/tokens.d.ts +680 -0
- package/tokens/tokens.js +1018 -0
- package/tokens/tokens.json +2928 -0
- package/tokens/typography.css +22 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
export interface TreeItem {
|
|
4
|
+
/** Stable identity; falls back to `label` when omitted */
|
|
5
|
+
key?: string;
|
|
6
|
+
/** Row text; `label` is accepted as an alias */
|
|
7
|
+
title?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
icon?: string;
|
|
10
|
+
/** Leaf glyph hue: orange | green | purple | blue | teal | gray */
|
|
11
|
+
hue?: string;
|
|
12
|
+
/** Right-aligned count */
|
|
13
|
+
count?: number;
|
|
14
|
+
/** Small inline pill after the title */
|
|
15
|
+
tag?: string;
|
|
16
|
+
children?: TreeItem[];
|
|
17
|
+
/** Row is greyed out and inert (selection, check and drag all blocked) */
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
/** Row stays interactive but its checkbox is locked */
|
|
20
|
+
disableCheckbox?: boolean;
|
|
21
|
+
/** Clicking the row does not select it (folder-only trees) */
|
|
22
|
+
selectable?: boolean;
|
|
23
|
+
/** Hide this row's checkbox in a checkable tree */
|
|
24
|
+
checkable?: boolean;
|
|
25
|
+
/** false marks an expandable branch whose children arrive from `loadData` */
|
|
26
|
+
isLeaf?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TreeDropInfo {
|
|
30
|
+
dragKey: string;
|
|
31
|
+
dropKey: string;
|
|
32
|
+
position: "before" | "after" | "inside";
|
|
33
|
+
treeData: TreeItem[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Nested tree view: expand/collapse, single or multiple selection, cascading checkboxes with
|
|
38
|
+
* indeterminate parents, disabled nodes, connector lines, async loading, search highlight,
|
|
39
|
+
* drag to move, keyboard navigation.
|
|
40
|
+
*/
|
|
41
|
+
export interface TreeProps {
|
|
42
|
+
items?: TreeItem[];
|
|
43
|
+
/** Alias for `items` */
|
|
44
|
+
treeData?: TreeItem[];
|
|
45
|
+
/** Keys of initially expanded branches */
|
|
46
|
+
defaultExpanded?: string[];
|
|
47
|
+
/** Controlled expansion */
|
|
48
|
+
expandedKeys?: string[];
|
|
49
|
+
onExpand?: (keys: string[]) => void;
|
|
50
|
+
/** Convenience single-selection seed */
|
|
51
|
+
defaultActive?: string;
|
|
52
|
+
defaultSelected?: string[];
|
|
53
|
+
selectedKeys?: string[];
|
|
54
|
+
onSelect?: (key: string | string[], keys: string[]) => void;
|
|
55
|
+
/** Render a checkbox on every row */
|
|
56
|
+
checkable?: boolean;
|
|
57
|
+
/** Parent and child checkboxes stop cascading into each other */
|
|
58
|
+
checkStrictly?: boolean;
|
|
59
|
+
defaultChecked?: string[];
|
|
60
|
+
checkedKeys?: string[];
|
|
61
|
+
onCheck?: (keys: string[], info: { key: string; checked: boolean; half: string[] }) => void;
|
|
62
|
+
/** Allow more than one selected row */
|
|
63
|
+
multiple?: boolean;
|
|
64
|
+
/** false = rows only expand, never select */
|
|
65
|
+
selectable?: boolean;
|
|
66
|
+
showIcon?: boolean;
|
|
67
|
+
/** Draw connector lines instead of plain indentation */
|
|
68
|
+
showLine?: boolean;
|
|
69
|
+
draggable?: boolean;
|
|
70
|
+
/** Also allow insert-between: a folder's outer 20% becomes a before/after rule. Off by default — a drag moves a node INTO a folder, and files are never drop targets. */
|
|
71
|
+
reorder?: boolean;
|
|
72
|
+
/** Highlights matches and auto-expands their ancestors */
|
|
73
|
+
searchValue?: string;
|
|
74
|
+
autoExpandOnSearch?: boolean;
|
|
75
|
+
/** Return (or resolve) the children of a branch the first time it opens */
|
|
76
|
+
loadData?: (node: TreeItem) => TreeItem[] | Promise<TreeItem[]>;
|
|
77
|
+
onDrop?: (info: TreeDropInfo) => void;
|
|
78
|
+
onContextMenu?: (key: string, event: React.MouseEvent) => void;
|
|
79
|
+
/** Trailing controls revealed on row hover */
|
|
80
|
+
rowActions?: (node: TreeItem) => React.ReactNode;
|
|
81
|
+
style?: React.CSSProperties;
|
|
82
|
+
}
|
|
83
|
+
export declare function Tree(props: TreeProps): JSX.Element;
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
import { Checkbox } from "../forms/Checkbox.jsx";
|
|
4
|
+
|
|
5
|
+
const docHues = { orange: "var(--cat-transform)", green: "var(--cat-source)", purple: "var(--cat-merge)", blue: "var(--info-fg)", teal: "var(--cat-output)", gray: "var(--text-muted)" };
|
|
6
|
+
const INDENT = 18;
|
|
7
|
+
|
|
8
|
+
const keyOf = (n) => (n.key != null ? n.key : n.label);
|
|
9
|
+
const titleOf = (n) => (n.title != null ? n.title : n.label);
|
|
10
|
+
const kidsOf = (n) => n.children || [];
|
|
11
|
+
const isBranch = (n) => kidsOf(n).length > 0 || n.isLeaf === false;
|
|
12
|
+
|
|
13
|
+
function walk(nodes, fn, parentKey = null, depth = 0) {
|
|
14
|
+
nodes.forEach((n) => { fn(n, parentKey, depth); walk(kidsOf(n), fn, keyOf(n), depth + 1); });
|
|
15
|
+
}
|
|
16
|
+
function indexNodes(items) {
|
|
17
|
+
const byKey = {}, parent = {}, all = [];
|
|
18
|
+
walk(items, (n, p, d) => { const k = keyOf(n); byKey[k] = n; parent[k] = p; all.push({ node: n, key: k, depth: d }); });
|
|
19
|
+
return { byKey, parent, all };
|
|
20
|
+
}
|
|
21
|
+
function descendants(node, out = []) {
|
|
22
|
+
kidsOf(node).forEach((c) => { out.push(c); descendants(c, out); });
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
/** Is `key` the dragged node itself, or anywhere inside its subtree? */
|
|
26
|
+
function inSubtree(items, rootKey, key) {
|
|
27
|
+
const { byKey } = indexNodes(items);
|
|
28
|
+
const root = byKey[rootKey];
|
|
29
|
+
if (!root) return false;
|
|
30
|
+
return rootKey === key || descendants(root).some((d) => keyOf(d) === key);
|
|
31
|
+
}
|
|
32
|
+
function keysMatching(items, test) {
|
|
33
|
+
const hit = [];
|
|
34
|
+
const visit = (n, trail) => {
|
|
35
|
+
const t = [...trail, keyOf(n)];
|
|
36
|
+
if (test(n)) hit.push(...trail);
|
|
37
|
+
kidsOf(n).forEach((c) => visit(c, t));
|
|
38
|
+
};
|
|
39
|
+
items.forEach((n) => visit(n, []));
|
|
40
|
+
return [...new Set(hit)];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Cascading check: a parent is checked when every enabled descendant is checked,
|
|
44
|
+
indeterminate when only some are. Disabled nodes never change state. */
|
|
45
|
+
function cascade(items, key, next) {
|
|
46
|
+
const { byKey, parent } = indexNodes(items);
|
|
47
|
+
const set = new Set(next);
|
|
48
|
+
const node = byKey[key];
|
|
49
|
+
if (!node) return next;
|
|
50
|
+
const checked = set.has(key);
|
|
51
|
+
descendants(node).forEach((d) => {
|
|
52
|
+
if (d.disabled || d.disableCheckbox) return;
|
|
53
|
+
checked ? set.add(keyOf(d)) : set.delete(keyOf(d));
|
|
54
|
+
});
|
|
55
|
+
let p = parent[key];
|
|
56
|
+
while (p != null) {
|
|
57
|
+
const pn = byKey[p];
|
|
58
|
+
const kids = kidsOf(pn).filter((c) => !c.disabled && !c.disableCheckbox);
|
|
59
|
+
const on = kids.filter((c) => set.has(keyOf(c)));
|
|
60
|
+
on.length && on.length === kids.length ? set.add(p) : set.delete(p);
|
|
61
|
+
p = parent[p];
|
|
62
|
+
}
|
|
63
|
+
return [...set];
|
|
64
|
+
}
|
|
65
|
+
/* Display state for every branch: full when all enabled descendants are checked,
|
|
66
|
+
half when only some. Keeps parents honest whatever the caller passed in. */
|
|
67
|
+
function rollUp(items, checkedKeys) {
|
|
68
|
+
const full = new Set(checkedKeys), half = new Set();
|
|
69
|
+
const visit = (n) => {
|
|
70
|
+
kidsOf(n).forEach(visit);
|
|
71
|
+
if (!kidsOf(n).length) return;
|
|
72
|
+
const ds = descendants(n).filter((d) => !d.disabled && !d.disableCheckbox);
|
|
73
|
+
if (!ds.length) return;
|
|
74
|
+
const on = ds.filter((d) => full.has(keyOf(d))).length;
|
|
75
|
+
if (on === ds.length) full.add(keyOf(n));
|
|
76
|
+
else { full.delete(keyOf(n)); if (on > 0) half.add(keyOf(n)); }
|
|
77
|
+
};
|
|
78
|
+
items.forEach(visit);
|
|
79
|
+
return { full, half };
|
|
80
|
+
}
|
|
81
|
+
function halfKeys(items, checkedKeys) {
|
|
82
|
+
return [...rollUp(items, checkedKeys).half];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function Highlight({ text, query }) {
|
|
86
|
+
if (!query) return text;
|
|
87
|
+
const i = String(text).toLowerCase().indexOf(query.toLowerCase());
|
|
88
|
+
if (i < 0) return text;
|
|
89
|
+
return <>{String(text).slice(0, i)}<mark style={{ background: "var(--soft-bg)", color: "var(--info-fg)", fontWeight: "var(--weight-medium)", padding: 0, borderRadius: 2 }}>{String(text).slice(i, i + query.length)}</mark>{String(text).slice(i + query.length)}</>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Guide lines for showLine: one 18px column per ancestor level, then the elbow. */
|
|
93
|
+
function Guides({ trail, isLast, hasKids }) {
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
{trail.map((ancestorHasNext, i) => (
|
|
97
|
+
<span key={i} style={{ width: INDENT, alignSelf: "stretch", flexShrink: 0, borderLeft: ancestorHasNext ? "1px solid var(--border-subtle)" : "1px solid transparent", marginLeft: i === 0 ? 6 : 0 }} />
|
|
98
|
+
))}
|
|
99
|
+
<span style={{ width: INDENT, alignSelf: "stretch", flexShrink: 0, position: "relative", marginLeft: trail.length === 0 ? 6 : 0 }}>
|
|
100
|
+
<span style={{ position: "absolute", left: 0, top: 0, height: isLast ? "50%" : "100%", borderLeft: "1px solid var(--border-subtle)" }} />
|
|
101
|
+
<span style={{ position: "absolute", left: 0, top: "50%", width: hasKids ? 6 : INDENT - 4, borderTop: "1px solid var(--border-subtle)" }} />
|
|
102
|
+
</span>
|
|
103
|
+
</>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function Row({ item, state, cfg, api }) {
|
|
108
|
+
const { node, key, depth, trail, isLast } = item;
|
|
109
|
+
const kids = kidsOf(node);
|
|
110
|
+
const branch = isBranch(node);
|
|
111
|
+
const open = state.expanded.includes(key);
|
|
112
|
+
const selected = state.selected.includes(key);
|
|
113
|
+
const checked = state.checked.has(key);
|
|
114
|
+
const half = !checked && state.half.has(key);
|
|
115
|
+
const loading = state.loading.includes(key);
|
|
116
|
+
const focused = state.focus === key;
|
|
117
|
+
const ringed = focused && state.focusMoved;
|
|
118
|
+
const [hover, setHover] = React.useState(false);
|
|
119
|
+
const disabled = !!node.disabled;
|
|
120
|
+
const dropAt = state.drop && state.drop.key === key ? state.drop.pos : null;
|
|
121
|
+
const selectable = cfg.selectable && node.selectable !== false;
|
|
122
|
+
|
|
123
|
+
const chevron = branch ? (
|
|
124
|
+
<span onClick={(e) => { e.stopPropagation(); api.toggle(key); }} style={{ width: 14, height: 14, display: "inline-flex", alignItems: "center", justifyContent: "center", color: "var(--text-muted)", flexShrink: 0, cursor: "pointer", transform: open ? "rotate(90deg)" : "none", transition: "transform var(--dur-fast) var(--ease-out)" }}>
|
|
125
|
+
{loading ? <Icon name="loader" size={12} style={{ animation: "kb-tree-spin 900ms linear infinite" }} /> : <Icon name="chevron-right" size={13} />}
|
|
126
|
+
</span>
|
|
127
|
+
) : <span style={{ width: cfg.showLine ? 0 : 14, flexShrink: 0 }} />;
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<>
|
|
131
|
+
<div role="treeitem" tabIndex={focused ? 0 : -1} aria-expanded={branch ? open : undefined} aria-selected={selected} aria-disabled={disabled || undefined}
|
|
132
|
+
ref={(el) => { if (el && focused && state.focusMoved) el.focus({ preventScroll: true }); }}
|
|
133
|
+
draggable={cfg.draggable && !disabled}
|
|
134
|
+
onDragStart={(e) => { e.dataTransfer.effectAllowed = "move"; api.dragStart(key); }}
|
|
135
|
+
onDragOver={(e) => { if (!cfg.draggable) return; e.preventDefault(); const r = e.currentTarget.getBoundingClientRect(); api.dragOver(key, (e.clientY - r.top) / r.height); }}
|
|
136
|
+
onDragLeave={() => api.dragOver(null)}
|
|
137
|
+
onDrop={(e) => { e.preventDefault(); api.drop(key); }}
|
|
138
|
+
onDragEnd={() => api.dragEnd()}
|
|
139
|
+
onClick={() => { if (disabled) return; api.focus(key); if (selectable) api.select(key); if (branch) api.toggle(key); }}
|
|
140
|
+
onContextMenu={(e) => { if (cfg.onContextMenu) { e.preventDefault(); cfg.onContextMenu(key, e); } }}
|
|
141
|
+
onKeyDown={(e) => api.keyDown(e, item)}
|
|
142
|
+
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
|
143
|
+
style={{
|
|
144
|
+
minHeight: "var(--h-tree-item)", borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", gap: 6,
|
|
145
|
+
padding: cfg.showLine ? "0 8px 0 2px" : "5px 8px", paddingLeft: cfg.showLine ? 2 : 8 + depth * INDENT, boxSizing: "border-box",
|
|
146
|
+
cursor: disabled ? "not-allowed" : "pointer", opacity: disabled ? 0.45 : 1,
|
|
147
|
+
background: dropAt === "inside" ? "var(--soft-bg)" : selected ? "var(--bg-active)" : hover && !disabled ? "var(--bg-hover)" : "transparent",
|
|
148
|
+
outline: dropAt === "inside" ? "2px solid var(--action-primary)" : ringed ? "2px solid var(--focus-border)" : "none", outlineOffset: -2,
|
|
149
|
+
boxShadow: dropAt === "before" ? "inset 0 2px 0 var(--action-primary)" : dropAt === "after" ? "inset 0 -2px 0 var(--action-primary)" : "none",
|
|
150
|
+
transition: "var(--t-hover)",
|
|
151
|
+
}}>
|
|
152
|
+
{cfg.showLine ? <Guides trail={trail} isLast={isLast} hasKids={branch} /> : null}
|
|
153
|
+
{chevron}
|
|
154
|
+
{cfg.checkable && node.checkable !== false ? (
|
|
155
|
+
<span onClick={(e) => e.stopPropagation()} style={{ display: "inline-flex", flexShrink: 0 }}>
|
|
156
|
+
<Checkbox size="sm" checked={checked} indeterminate={half} disabled={disabled || node.disableCheckbox} onChange={() => api.check(key)} />
|
|
157
|
+
</span>
|
|
158
|
+
) : null}
|
|
159
|
+
{cfg.showIcon ? (
|
|
160
|
+
<span style={{ color: branch ? "var(--text-muted)" : docHues[node.hue || "gray"], display: "inline-flex", flexShrink: 0 }}>
|
|
161
|
+
<Icon name={node.icon || (branch ? (open ? "folder-open" : "folder") : "file-text")} size={15} />
|
|
162
|
+
</span>
|
|
163
|
+
) : null}
|
|
164
|
+
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", fontWeight: selected ? "var(--weight-medium)" : "var(--weight-regular)", color: disabled ? "var(--text-muted)" : "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
|
|
165
|
+
<Highlight text={titleOf(node)} query={cfg.searchValue} />
|
|
166
|
+
</span>
|
|
167
|
+
{node.tag ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-2xs)", lineHeight: 1, padding: "2px 5px", borderRadius: "var(--radius-sm)", background: "var(--bg-inset)", color: "var(--text-secondary)", flexShrink: 0 }}>{node.tag}</span> : null}
|
|
168
|
+
{node.count != null ? <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize: "var(--text-2xs)", color: "var(--text-muted)", paddingLeft: 8 }}>{node.count}</span> : null}
|
|
169
|
+
{cfg.rowActions && !disabled ? <span style={{ marginLeft: node.count != null ? 6 : "auto", display: "flex", gap: 2, opacity: hover ? 1 : 0, transition: "var(--t-hover)" }} onClick={(e) => e.stopPropagation()}>{cfg.rowActions(node)}</span> : null}
|
|
170
|
+
</div>
|
|
171
|
+
{open && loading && !kids.length ? (
|
|
172
|
+
<div style={{ paddingLeft: 8 + (depth + 1) * INDENT + 14, fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", color: "var(--text-muted)", height: "var(--h-tree-item)", display: "flex", alignItems: "center" }}>Loading…</div>
|
|
173
|
+
) : null}
|
|
174
|
+
</>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Nested tree view. Expand/collapse, single or multiple selection, cascading checkboxes
|
|
180
|
+
* (with indeterminate parents and `checkStrictly` opt-out), disabled nodes and disabled
|
|
181
|
+
* checkboxes, connector lines, async child loading, search highlighting, drag to move,
|
|
182
|
+
* and full keyboard navigation. Node shape: { key, title, icon, hue, count, tag, children,
|
|
183
|
+
* disabled, disableCheckbox, selectable, checkable, isLeaf }.
|
|
184
|
+
*/
|
|
185
|
+
export function Tree({
|
|
186
|
+
items = [], treeData,
|
|
187
|
+
defaultExpanded = [], expandedKeys, onExpand,
|
|
188
|
+
defaultActive, defaultSelected = [], selectedKeys, onSelect,
|
|
189
|
+
checkable = false, checkStrictly = false, defaultChecked = [], checkedKeys, onCheck,
|
|
190
|
+
multiple = false, selectable = true, showIcon = true, showLine = false, draggable = false,
|
|
191
|
+
searchValue = "", autoExpandOnSearch = true, loadData, onDrop, onContextMenu, rowActions, style, reorder = false,
|
|
192
|
+
}) {
|
|
193
|
+
const raw = treeData || items;
|
|
194
|
+
/* Callers usually pass an inline array literal, so compare by value —
|
|
195
|
+
re-seeding on identity alone would wipe async-loaded children. */
|
|
196
|
+
const rawKey = JSON.stringify(raw);
|
|
197
|
+
const [data, setData] = React.useState(raw);
|
|
198
|
+
const seenKey = React.useRef(rawKey);
|
|
199
|
+
React.useEffect(() => { if (seenKey.current !== rawKey) { seenKey.current = rawKey; setData(raw); } }, [rawKey]);
|
|
200
|
+
|
|
201
|
+
const seedExpanded = defaultExpanded.length ? defaultExpanded : defaultActive ? [] : [];
|
|
202
|
+
const [expandedS, setExpandedS] = React.useState(seedExpanded);
|
|
203
|
+
const [selectedS, setSelectedS] = React.useState(defaultActive ? [defaultActive] : defaultSelected);
|
|
204
|
+
const [checkedS, setCheckedS] = React.useState(defaultChecked);
|
|
205
|
+
const [loading, setLoading] = React.useState([]);
|
|
206
|
+
const [loaded, setLoaded] = React.useState([]);
|
|
207
|
+
const [focus, setFocus] = React.useState(null);
|
|
208
|
+
const [focusMoved, setFocusMoved] = React.useState(false);
|
|
209
|
+
const [dragKey, setDragKey] = React.useState(null);
|
|
210
|
+
const [drop, setDrop] = React.useState(null);
|
|
211
|
+
|
|
212
|
+
const expanded = expandedKeys || expandedS;
|
|
213
|
+
const selected = selectedKeys || selectedS;
|
|
214
|
+
const checked = checkedKeys || checkedS;
|
|
215
|
+
|
|
216
|
+
const searchExpanded = React.useMemo(
|
|
217
|
+
() => (searchValue && autoExpandOnSearch ? keysMatching(data, (n) => String(titleOf(n)).toLowerCase().includes(searchValue.toLowerCase())) : []),
|
|
218
|
+
[data, searchValue, autoExpandOnSearch]
|
|
219
|
+
);
|
|
220
|
+
const openKeys = React.useMemo(() => [...new Set([...expanded, ...searchExpanded])], [expanded, searchExpanded]);
|
|
221
|
+
const view = React.useMemo(
|
|
222
|
+
() => (checkable && !checkStrictly ? rollUp(data, checked) : { full: new Set(checked), half: new Set() }),
|
|
223
|
+
[checkable, checkStrictly, data, checked]
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
/* Visible rows, with the sibling map each row needs to draw its guide lines. */
|
|
227
|
+
const rows = React.useMemo(() => {
|
|
228
|
+
const out = [];
|
|
229
|
+
const push = (nodes, depth, trail) => {
|
|
230
|
+
nodes.forEach((n, i) => {
|
|
231
|
+
const isLast = i === nodes.length - 1;
|
|
232
|
+
const key = keyOf(n);
|
|
233
|
+
out.push({ node: n, key, depth, trail, isLast });
|
|
234
|
+
if (kidsOf(n).length && openKeys.includes(key)) push(kidsOf(n), depth + 1, [...trail, !isLast]);
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
push(data, 0, []);
|
|
238
|
+
return out;
|
|
239
|
+
}, [data, openKeys]);
|
|
240
|
+
|
|
241
|
+
const setExpanded = (next) => { if (!expandedKeys) setExpandedS(next); onExpand && onExpand(next); };
|
|
242
|
+
|
|
243
|
+
const api = {
|
|
244
|
+
focus: (k) => { setFocus(k); setFocusMoved(false); },
|
|
245
|
+
toggle: (k) => {
|
|
246
|
+
const open = openKeys.includes(k);
|
|
247
|
+
if (!open && loadData && !loaded.includes(k) && !kidsOf(indexNodes(data).byKey[k]).length) {
|
|
248
|
+
setLoading((l) => [...l, k]);
|
|
249
|
+
setExpanded([...expanded, k]);
|
|
250
|
+
Promise.resolve(loadData(indexNodes(data).byKey[k])).then((kids) => {
|
|
251
|
+
setData((d) => {
|
|
252
|
+
const clone = JSON.parse(JSON.stringify(d));
|
|
253
|
+
const { byKey } = indexNodes(clone);
|
|
254
|
+
if (byKey[k]) byKey[k].children = kids || [];
|
|
255
|
+
return clone;
|
|
256
|
+
});
|
|
257
|
+
setLoaded((l) => [...l, k]);
|
|
258
|
+
setLoading((l) => l.filter((x) => x !== k));
|
|
259
|
+
});
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
setExpanded(open ? expanded.filter((x) => x !== k) : [...expanded, k]);
|
|
263
|
+
},
|
|
264
|
+
select: (k) => {
|
|
265
|
+
const next = multiple ? (selected.includes(k) ? selected.filter((x) => x !== k) : [...selected, k]) : selected.includes(k) && multiple ? [] : [k];
|
|
266
|
+
if (!selectedKeys) setSelectedS(next);
|
|
267
|
+
onSelect && onSelect(multiple ? next : k, next);
|
|
268
|
+
},
|
|
269
|
+
check: (k) => {
|
|
270
|
+
const on = view.full.has(k);
|
|
271
|
+
let next = on ? [...view.full].filter((x) => x !== k) : [...view.full, k];
|
|
272
|
+
if (!checkStrictly) next = cascade(data, k, next);
|
|
273
|
+
if (!checkedKeys) setCheckedS(next);
|
|
274
|
+
onCheck && onCheck(next, { key: k, checked: !on, half: checkStrictly ? [] : halfKeys(data, next) });
|
|
275
|
+
},
|
|
276
|
+
dragStart: (k) => setDragKey(k),
|
|
277
|
+
/* Validity lives here, not in the row: only branches can be dropped *into*, and a node
|
|
278
|
+
can never land inside its own subtree (that would detach the whole branch). */
|
|
279
|
+
dragOver: (k, ratio) => {
|
|
280
|
+
if (k == null) return setDrop(null);
|
|
281
|
+
const node = indexNodes(data).byKey[k];
|
|
282
|
+
/* Only folders are drop targets: a file can't contain anything, so hovering one shows
|
|
283
|
+
nothing at all. With reorder, a folder's outer 20% becomes an insert rule instead. */
|
|
284
|
+
if (!node || node.disabled || !dragKey || !isBranch(node) || inSubtree(data, dragKey, k)) return setDrop(null);
|
|
285
|
+
const pos = reorder ? (ratio < 0.2 ? "before" : ratio > 0.8 ? "after" : "inside") : "inside";
|
|
286
|
+
setDrop({ key: k, pos });
|
|
287
|
+
},
|
|
288
|
+
dragEnd: () => { setDragKey(null); setDrop(null); },
|
|
289
|
+
drop: (k) => {
|
|
290
|
+
const info = drop && drop.key === k ? drop : null;
|
|
291
|
+
setDrop(null);
|
|
292
|
+
if (!dragKey || !info || inSubtree(data, dragKey, k)) return setDragKey(null);
|
|
293
|
+
/* Computed outside setData: React re-invokes updaters (StrictMode, concurrent
|
|
294
|
+
rebasing), which would fire the consumer's onDrop twice for one gesture. */
|
|
295
|
+
const clone = JSON.parse(JSON.stringify(data));
|
|
296
|
+
let moved = null;
|
|
297
|
+
const prune = (nodes) => nodes.filter((n) => {
|
|
298
|
+
if (keyOf(n) === dragKey) { moved = n; return false; }
|
|
299
|
+
if (n.children) n.children = prune(n.children);
|
|
300
|
+
return true;
|
|
301
|
+
});
|
|
302
|
+
const rootA = prune(clone);
|
|
303
|
+
if (!moved) return setDragKey(null);
|
|
304
|
+
let inserted = false;
|
|
305
|
+
const insert = (nodes) => {
|
|
306
|
+
const out = [];
|
|
307
|
+
nodes.forEach((n) => {
|
|
308
|
+
if (keyOf(n) === k && info.pos === "before") { out.push(moved); inserted = true; }
|
|
309
|
+
if (keyOf(n) === k && info.pos === "inside") { n.children = [...(n.children || []), moved]; inserted = true; }
|
|
310
|
+
else if (n.children) n.children = insert(n.children);
|
|
311
|
+
out.push(n);
|
|
312
|
+
if (keyOf(n) === k && info.pos === "after") { out.push(moved); inserted = true; }
|
|
313
|
+
});
|
|
314
|
+
return out;
|
|
315
|
+
};
|
|
316
|
+
const next = insert(rootA);
|
|
317
|
+
if (!inserted) return setDragKey(null);
|
|
318
|
+
setData(next);
|
|
319
|
+
/* Dropping into a collapsed folder would otherwise look like the node vanished. */
|
|
320
|
+
if (info.pos === "inside" && !expanded.includes(k)) setExpanded([...expanded, k]);
|
|
321
|
+
onDrop && onDrop({ dragKey, dropKey: k, position: info.pos, treeData: next });
|
|
322
|
+
setDragKey(null);
|
|
323
|
+
},
|
|
324
|
+
keyDown: (e, item) => {
|
|
325
|
+
const i = rows.findIndex((r) => r.key === item.key);
|
|
326
|
+
const go = (n) => { const t = rows[n]; if (!t) return; setFocus(t.key); setFocusMoved(true); };
|
|
327
|
+
if (e.key === "ArrowDown") { e.preventDefault(); go(i + 1); }
|
|
328
|
+
else if (e.key === "ArrowUp") { e.preventDefault(); go(i - 1); }
|
|
329
|
+
else if (e.key === "ArrowRight") { e.preventDefault(); if (isBranch(item.node) && !openKeys.includes(item.key)) api.toggle(item.key); else go(i + 1); }
|
|
330
|
+
else if (e.key === "ArrowLeft") { e.preventDefault(); if (isBranch(item.node) && openKeys.includes(item.key)) api.toggle(item.key); else { const p = rows.slice(0, i).reverse().find((r) => r.depth === item.depth - 1); if (p) { setFocus(p.key); setFocusMoved(true); } } }
|
|
331
|
+
else if (e.key === "Enter") { e.preventDefault(); if (!item.node.disabled) { if (selectable && item.node.selectable !== false) api.select(item.key); if (isBranch(item.node)) api.toggle(item.key); } }
|
|
332
|
+
else if (e.key === " ") { e.preventDefault(); if (checkable && !item.node.disabled && item.node.checkable !== false && !item.node.disableCheckbox) api.check(item.key); }
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const state = { expanded: openKeys, selected, checked: view.full, half: view.half, loading, focus: focus || (rows[0] && rows[0].key), focusMoved, drop };
|
|
337
|
+
const cfg = { checkable, checkStrictly, multiple, selectable, showIcon, showLine, draggable, searchValue, onContextMenu, rowActions };
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<div role="tree" aria-multiselectable={multiple || undefined} style={{ display: "flex", flexDirection: "column", gap: showLine ? 0 : 1, ...style }}>
|
|
341
|
+
<style>{"@keyframes kb-tree-spin{to{transform:rotate(360deg)}}"}</style>
|
|
342
|
+
{rows.length === 0 ? (
|
|
343
|
+
<div style={{ padding: "10px 8px", fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", color: "var(--text-muted)" }}>No matching items</div>
|
|
344
|
+
) : rows.map((r) => <Row key={r.key} item={r} state={state} cfg={cfg} api={api} />)}
|
|
345
|
+
</div>
|
|
346
|
+
);
|
|
347
|
+
}
|