@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,258 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed, watch } from 'vue'
|
|
3
|
+
import Icon from '../icons/Icon.vue'
|
|
4
|
+
import Checkbox from '../forms/Checkbox.vue'
|
|
5
|
+
// Nested tree view: expand/collapse, single or multiple selection, cascading checkboxes with
|
|
6
|
+
// indeterminate parents (checkStrictly opts out), disabled nodes, connector lines, async
|
|
7
|
+
// loadData, search highlight, drag to move, keyboard navigation.
|
|
8
|
+
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)' }
|
|
9
|
+
const INDENT = 18
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
items: { type: Array, default: () => [] },
|
|
12
|
+
defaultExpanded: { type: Array, default: () => [] },
|
|
13
|
+
defaultActive: String,
|
|
14
|
+
defaultSelected: { type: Array, default: () => [] },
|
|
15
|
+
defaultChecked: { type: Array, default: () => [] },
|
|
16
|
+
checkable: Boolean, checkStrictly: Boolean, multiple: Boolean,
|
|
17
|
+
selectable: { type: Boolean, default: true },
|
|
18
|
+
showIcon: { type: Boolean, default: true },
|
|
19
|
+
showLine: Boolean, draggable: Boolean,
|
|
20
|
+
searchValue: { type: String, default: '' },
|
|
21
|
+
autoExpandOnSearch: { type: Boolean, default: true },
|
|
22
|
+
reorder: Boolean,
|
|
23
|
+
loadData: Function, rowActions: Function, style: Object,
|
|
24
|
+
})
|
|
25
|
+
const emit = defineEmits(['select', 'check', 'expand', 'drop'])
|
|
26
|
+
|
|
27
|
+
const data = ref(JSON.parse(JSON.stringify(props.items)))
|
|
28
|
+
// Compare by value: re-seeding on identity alone would wipe async-loaded children.
|
|
29
|
+
watch(() => JSON.stringify(props.items), (next, prev) => { if (next !== prev) data.value = JSON.parse(next) })
|
|
30
|
+
const expanded = ref([...props.defaultExpanded])
|
|
31
|
+
const selected = ref(props.defaultActive ? [props.defaultActive] : [...props.defaultSelected])
|
|
32
|
+
const checked = ref([...props.defaultChecked])
|
|
33
|
+
const loading = ref([]); const loaded = ref([]); const focus = ref(null)
|
|
34
|
+
const dragKey = ref(null); const drop = ref(null)
|
|
35
|
+
|
|
36
|
+
const keyOf = n => (n.key != null ? n.key : n.label)
|
|
37
|
+
const titleOf = n => (n.title != null ? n.title : n.label)
|
|
38
|
+
const kidsOf = n => n.children || []
|
|
39
|
+
const isBranch = n => kidsOf(n).length > 0 || n.isLeaf === false
|
|
40
|
+
function descendants(n, out = []) { for (const c of kidsOf(n)) { out.push(c); descendants(c, out) } return out }
|
|
41
|
+
/** Is `key` the dragged node itself, or anywhere inside its subtree? */
|
|
42
|
+
function inSubtree(rootKey, key) {
|
|
43
|
+
const root = index(data.value).byKey[rootKey]
|
|
44
|
+
if (!root) return false
|
|
45
|
+
return rootKey === key || descendants(root).some(d => keyOf(d) === key)
|
|
46
|
+
}
|
|
47
|
+
function index(nodes, parent = null, acc = { byKey: {}, parent: {} }) {
|
|
48
|
+
for (const n of nodes) { acc.byKey[keyOf(n)] = n; acc.parent[keyOf(n)] = parent; index(kidsOf(n), keyOf(n), acc) }
|
|
49
|
+
return acc
|
|
50
|
+
}
|
|
51
|
+
function ancestorsOfMatches(nodes, test, trail = [], hit = []) {
|
|
52
|
+
for (const n of nodes) {
|
|
53
|
+
if (test(n)) hit.push(...trail)
|
|
54
|
+
ancestorsOfMatches(kidsOf(n), test, [...trail, keyOf(n)], hit)
|
|
55
|
+
}
|
|
56
|
+
return [...new Set(hit)]
|
|
57
|
+
}
|
|
58
|
+
const searchExpanded = computed(() => props.searchValue && props.autoExpandOnSearch
|
|
59
|
+
? ancestorsOfMatches(data.value, n => String(titleOf(n)).toLowerCase().includes(props.searchValue.toLowerCase()))
|
|
60
|
+
: [])
|
|
61
|
+
const openKeys = computed(() => [...new Set([...expanded.value, ...searchExpanded.value])])
|
|
62
|
+
|
|
63
|
+
const view = computed(() => {
|
|
64
|
+
const full = new Set(checked.value); const half = new Set()
|
|
65
|
+
if (!props.checkable || props.checkStrictly) return { full, half }
|
|
66
|
+
const visit = n => {
|
|
67
|
+
kidsOf(n).forEach(visit)
|
|
68
|
+
if (!kidsOf(n).length) return
|
|
69
|
+
const ds = descendants(n).filter(d => !d.disabled && !d.disableCheckbox)
|
|
70
|
+
if (!ds.length) return
|
|
71
|
+
const on = ds.filter(d => full.has(keyOf(d))).length
|
|
72
|
+
if (on === ds.length) full.add(keyOf(n))
|
|
73
|
+
else { full.delete(keyOf(n)); if (on > 0) half.add(keyOf(n)) }
|
|
74
|
+
}
|
|
75
|
+
data.value.forEach(visit)
|
|
76
|
+
return { full, half }
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const rows = computed(() => {
|
|
80
|
+
const out = []
|
|
81
|
+
const push = (nodes, depth, trail) => {
|
|
82
|
+
nodes.forEach((n, i) => {
|
|
83
|
+
const isLast = i === nodes.length - 1
|
|
84
|
+
out.push({ n, key: keyOf(n), depth, trail, isLast })
|
|
85
|
+
if (kidsOf(n).length && openKeys.value.includes(keyOf(n))) push(kidsOf(n), depth + 1, [...trail, !isLast])
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
push(data.value, 0, [])
|
|
89
|
+
return out
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
function toggle(k) {
|
|
93
|
+
const open = openKeys.value.includes(k)
|
|
94
|
+
const { byKey } = index(data.value)
|
|
95
|
+
if (!open && props.loadData && !loaded.value.includes(k) && !kidsOf(byKey[k]).length) {
|
|
96
|
+
loading.value = [...loading.value, k]
|
|
97
|
+
expanded.value = [...expanded.value, k]
|
|
98
|
+
Promise.resolve(props.loadData(byKey[k])).then(kids => {
|
|
99
|
+
const clone = JSON.parse(JSON.stringify(data.value))
|
|
100
|
+
const t = index(clone).byKey[k]
|
|
101
|
+
if (t) t.children = kids || []
|
|
102
|
+
data.value = clone
|
|
103
|
+
loaded.value = [...loaded.value, k]
|
|
104
|
+
loading.value = loading.value.filter(x => x !== k)
|
|
105
|
+
})
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
expanded.value = open ? expanded.value.filter(x => x !== k) : [...expanded.value, k]
|
|
109
|
+
emit('expand', expanded.value)
|
|
110
|
+
}
|
|
111
|
+
function select(k) {
|
|
112
|
+
selected.value = props.multiple
|
|
113
|
+
? (selected.value.includes(k) ? selected.value.filter(x => x !== k) : [...selected.value, k])
|
|
114
|
+
: [k]
|
|
115
|
+
emit('select', props.multiple ? selected.value : k, selected.value)
|
|
116
|
+
}
|
|
117
|
+
function check(k) {
|
|
118
|
+
const set = new Set(view.value.full)
|
|
119
|
+
const on = set.has(k)
|
|
120
|
+
on ? set.delete(k) : set.add(k)
|
|
121
|
+
if (!props.checkStrictly) {
|
|
122
|
+
const { byKey, parent } = index(data.value)
|
|
123
|
+
descendants(byKey[k]).forEach(d => {
|
|
124
|
+
if (d.disabled || d.disableCheckbox) return
|
|
125
|
+
on ? set.delete(keyOf(d)) : set.add(keyOf(d))
|
|
126
|
+
})
|
|
127
|
+
let p = parent[k]
|
|
128
|
+
while (p != null) {
|
|
129
|
+
const kids = kidsOf(byKey[p]).filter(c => !c.disabled && !c.disableCheckbox)
|
|
130
|
+
const hit = kids.filter(c => set.has(keyOf(c)))
|
|
131
|
+
hit.length && hit.length === kids.length ? set.add(p) : set.delete(p)
|
|
132
|
+
p = parent[p]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
checked.value = [...set]
|
|
136
|
+
emit('check', checked.value, { key: k, checked: !on, half: [...view.value.half] })
|
|
137
|
+
}
|
|
138
|
+
function rowClick(n) {
|
|
139
|
+
if (n.disabled) return
|
|
140
|
+
focus.value = keyOf(n)
|
|
141
|
+
if (props.selectable && n.selectable !== false) select(keyOf(n))
|
|
142
|
+
if (isBranch(n)) toggle(keyOf(n))
|
|
143
|
+
}
|
|
144
|
+
function onDragOver(e, k) {
|
|
145
|
+
if (!props.draggable) return
|
|
146
|
+
e.preventDefault()
|
|
147
|
+
if (!dragKey.value) return
|
|
148
|
+
const n = index(data.value).byKey[k]
|
|
149
|
+
// Only folders are drop targets: hovering a file shows nothing, since it can't contain anything.
|
|
150
|
+
if (!n || n.disabled || !isBranch(n) || inSubtree(dragKey.value, k)) { drop.value = null; return }
|
|
151
|
+
const r = e.currentTarget.getBoundingClientRect()
|
|
152
|
+
const y = (e.clientY - r.top) / r.height
|
|
153
|
+
drop.value = { key: k, pos: props.reorder ? (y < 0.2 ? 'before' : y > 0.8 ? 'after' : 'inside') : 'inside' }
|
|
154
|
+
}
|
|
155
|
+
function onDrop(k) {
|
|
156
|
+
const info = drop.value && drop.value.key === k ? drop.value : null
|
|
157
|
+
drop.value = null
|
|
158
|
+
if (!dragKey.value || !info || inSubtree(dragKey.value, k)) { dragKey.value = null; return }
|
|
159
|
+
const clone = JSON.parse(JSON.stringify(data.value))
|
|
160
|
+
let moved = null
|
|
161
|
+
const prune = nodes => nodes.filter(n => {
|
|
162
|
+
if (keyOf(n) === dragKey.value) { moved = n; return false }
|
|
163
|
+
if (n.children) n.children = prune(n.children)
|
|
164
|
+
return true
|
|
165
|
+
})
|
|
166
|
+
const pruned = prune(clone)
|
|
167
|
+
if (!moved) { dragKey.value = null; return }
|
|
168
|
+
const insert = nodes => {
|
|
169
|
+
const out = []
|
|
170
|
+
for (const n of nodes) {
|
|
171
|
+
if (keyOf(n) === k && info.pos === 'before') out.push(moved)
|
|
172
|
+
if (keyOf(n) === k && info.pos === 'inside') n.children = [...(n.children || []), moved]
|
|
173
|
+
else if (n.children) n.children = insert(n.children)
|
|
174
|
+
out.push(n)
|
|
175
|
+
if (keyOf(n) === k && info.pos === 'after') out.push(moved)
|
|
176
|
+
}
|
|
177
|
+
return out
|
|
178
|
+
}
|
|
179
|
+
data.value = insert(pruned)
|
|
180
|
+
if (info.pos === 'inside' && !expanded.value.includes(k)) expanded.value = [...expanded.value, k]
|
|
181
|
+
emit('drop', { dragKey: dragKey.value, dropKey: k, position: info.pos, treeData: data.value })
|
|
182
|
+
dragKey.value = null
|
|
183
|
+
}
|
|
184
|
+
function keyDown(e, row) {
|
|
185
|
+
const i = rows.value.findIndex(r => r.key === row.key)
|
|
186
|
+
const go = n => { const t = rows.value[n]; if (t) focus.value = t.key }
|
|
187
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); go(i + 1) }
|
|
188
|
+
else if (e.key === 'ArrowUp') { e.preventDefault(); go(i - 1) }
|
|
189
|
+
else if (e.key === 'ArrowRight') { e.preventDefault(); isBranch(row.n) && !openKeys.value.includes(row.key) ? toggle(row.key) : go(i + 1) }
|
|
190
|
+
else if (e.key === 'ArrowLeft') { e.preventDefault(); if (isBranch(row.n) && openKeys.value.includes(row.key)) toggle(row.key) }
|
|
191
|
+
else if (e.key === 'Enter') { e.preventDefault(); rowClick(row.n) }
|
|
192
|
+
else if (e.key === ' ') { e.preventDefault(); if (props.checkable && !row.n.disabled && row.n.checkable !== false && !row.n.disableCheckbox) check(row.key) }
|
|
193
|
+
}
|
|
194
|
+
function parts(text) {
|
|
195
|
+
const q = props.searchValue
|
|
196
|
+
const s = String(text)
|
|
197
|
+
if (!q) return [{ t: s, hit: false }]
|
|
198
|
+
const i = s.toLowerCase().indexOf(q.toLowerCase())
|
|
199
|
+
if (i < 0) return [{ t: s, hit: false }]
|
|
200
|
+
return [{ t: s.slice(0, i), hit: false }, { t: s.slice(i, i + q.length), hit: true }, { t: s.slice(i + q.length), hit: false }]
|
|
201
|
+
}
|
|
202
|
+
const dropAt = k => (drop.value && drop.value.key === k ? drop.value.pos : null)
|
|
203
|
+
</script>
|
|
204
|
+
<template>
|
|
205
|
+
<div role="tree" :style="{ display: 'flex', flexDirection: 'column', gap: showLine ? 0 : '1px', ...style }">
|
|
206
|
+
<div v-for="row in rows" :key="row.key" role="treeitem" class="kb-tree-row"
|
|
207
|
+
:tabindex="focus === row.key || (!focus && row === rows[0]) ? 0 : -1"
|
|
208
|
+
:aria-expanded="isBranch(row.n) ? openKeys.includes(row.key) : undefined"
|
|
209
|
+
:aria-selected="selected.includes(row.key)" :aria-disabled="row.n.disabled || undefined"
|
|
210
|
+
:draggable="draggable && !row.n.disabled"
|
|
211
|
+
@dragstart="dragKey = row.key" @dragover="onDragOver($event, row.key)" @dragleave="drop = null"
|
|
212
|
+
@drop.prevent="onDrop(row.key)" @dragend="dragKey = null; drop = null"
|
|
213
|
+
@click="rowClick(row.n)" @keydown="keyDown($event, row)"
|
|
214
|
+
:class="{ 'is-disabled': row.n.disabled }"
|
|
215
|
+
:style="{
|
|
216
|
+
minHeight: 'var(--h-tree-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: '6px',
|
|
217
|
+
padding: showLine ? '0 8px 0 2px' : '5px 8px', paddingLeft: showLine ? '2px' : (8 + row.depth * INDENT) + 'px', boxSizing: 'border-box',
|
|
218
|
+
cursor: row.n.disabled ? 'not-allowed' : 'pointer', opacity: row.n.disabled ? 0.45 : 1,
|
|
219
|
+
background: dropAt(row.key) === 'inside' ? 'var(--soft-bg)' : selected.includes(row.key) ? 'var(--bg-active)' : 'transparent',
|
|
220
|
+
outline: dropAt(row.key) === 'inside' ? '2px solid var(--action-primary)' : 'none',
|
|
221
|
+
outlineOffset: '-2px',
|
|
222
|
+
boxShadow: dropAt(row.key) === 'before' ? 'inset 0 2px 0 var(--action-primary)' : dropAt(row.key) === 'after' ? 'inset 0 -2px 0 var(--action-primary)' : 'none',
|
|
223
|
+
transition: 'var(--t-hover)' }">
|
|
224
|
+
<template v-if="showLine">
|
|
225
|
+
<span v-for="(hasNext, i) in row.trail" :key="i"
|
|
226
|
+
:style="{ width: INDENT + 'px', alignSelf: 'stretch', flexShrink: 0, marginLeft: i === 0 ? '6px' : 0, borderLeft: hasNext ? '1px solid var(--border-subtle)' : '1px solid transparent' }" />
|
|
227
|
+
<span :style="{ width: INDENT + 'px', alignSelf: 'stretch', flexShrink: 0, position: 'relative', marginLeft: row.trail.length ? 0 : '6px' }">
|
|
228
|
+
<span :style="{ position: 'absolute', left: 0, top: 0, height: row.isLast ? '50%' : '100%', borderLeft: '1px solid var(--border-subtle)' }" />
|
|
229
|
+
<span :style="{ position: 'absolute', left: 0, top: '50%', width: (isBranch(row.n) ? 6 : INDENT - 4) + 'px', borderTop: '1px solid var(--border-subtle)' }" />
|
|
230
|
+
</span>
|
|
231
|
+
</template>
|
|
232
|
+
<span v-if="isBranch(row.n)" @click.stop="toggle(row.key)"
|
|
233
|
+
:style="{ width: '14px', height: '14px', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-muted)', flexShrink: 0, cursor: 'pointer', transform: openKeys.includes(row.key) ? 'rotate(90deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }">
|
|
234
|
+
<Icon :name="loading.includes(row.key) ? 'loader' : 'chevron-right'" :size="13" :class="loading.includes(row.key) ? 'kb-tree-spin' : ''" />
|
|
235
|
+
</span>
|
|
236
|
+
<span v-else :style="{ width: showLine ? 0 : '14px', flexShrink: 0 }" />
|
|
237
|
+
<span v-if="checkable && row.n.checkable !== false" @click.stop style="display:inline-flex;flex-shrink:0">
|
|
238
|
+
<Checkbox size="sm" :checked="view.full.has(row.key)" :indeterminate="view.half.has(row.key)"
|
|
239
|
+
:disabled="row.n.disabled || row.n.disableCheckbox" @change="check(row.key)" />
|
|
240
|
+
</span>
|
|
241
|
+
<span v-if="showIcon" :style="{ color: isBranch(row.n) ? 'var(--text-muted)' : docHues[row.n.hue || 'gray'], display: 'inline-flex', flexShrink: 0 }">
|
|
242
|
+
<Icon :name="row.n.icon || (isBranch(row.n) ? (openKeys.includes(row.key) ? 'folder-open' : 'folder') : 'file-text')" :size="15" />
|
|
243
|
+
</span>
|
|
244
|
+
<span :style="{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', lineHeight: 'var(--leading-sm)', fontWeight: selected.includes(row.key) ? 'var(--weight-medium)' : 'var(--weight-regular)', color: row.n.disabled ? 'var(--text-muted)' : 'var(--text-heading)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }">
|
|
245
|
+
<template v-for="(p, i) in parts(titleOf(row.n))" :key="i"><mark v-if="p.hit" style="background:var(--soft-bg);color:var(--info-fg);font-weight:var(--weight-medium);padding:0;border-radius:2px">{{ p.t }}</mark><template v-else>{{ p.t }}</template></template>
|
|
246
|
+
</span>
|
|
247
|
+
<span v-if="row.n.tag" style="font-family:var(--font-sans);font-size:var(--text-2xs);line-height:1;padding:2px 5px;border-radius:var(--radius-sm);background:var(--bg-inset);color:var(--text-secondary);flex-shrink:0">{{ row.n.tag }}</span>
|
|
248
|
+
<span v-if="row.n.count != null" style="margin-left:auto;font-family:var(--font-mono);font-size:var(--text-2xs);color:var(--text-muted);padding-left:8px">{{ row.n.count }}</span>
|
|
249
|
+
</div>
|
|
250
|
+
<div v-if="!rows.length" style="padding:10px 8px;font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-muted)">No matching items</div>
|
|
251
|
+
</div>
|
|
252
|
+
</template>
|
|
253
|
+
<style scoped>
|
|
254
|
+
.kb-tree-row:not(.is-disabled):hover{background:var(--bg-hover)}
|
|
255
|
+
.kb-tree-row:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}
|
|
256
|
+
.kb-tree-spin{animation:kb-tree-spin 900ms linear infinite}
|
|
257
|
+
@keyframes kb-tree-spin{to{transform:rotate(360deg)}}
|
|
258
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!-- @dsCard group="Directory Index" viewport="700x70" name="Navigation dir" subtitle="Full card: Components → App Chrome" -->
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html><head><meta charset="utf-8"><link rel="stylesheet" href="../../styles.css">
|
|
4
|
+
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
|
5
|
+
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
|
6
|
+
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
7
|
+
<script src="../../_ds_bundle.js"></script>
|
|
8
|
+
<style>body{margin:0;background:var(--bg-surface);padding:16px;font-family:var(--font-sans)}</style></head>
|
|
9
|
+
<body><div id="root"></div>
|
|
10
|
+
<script type="text/babel">
|
|
11
|
+
const { Breadcrumb } = window.KeybricksDesignSystem_71231a;
|
|
12
|
+
ReactDOM.createRoot(document.getElementById("root")).render(<Breadcrumb items={["Management","System Migration","Import"]}/>);
|
|
13
|
+
</script></body></html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
App chrome: `Sidebar` (+`KEYBRICKS_NAV` model), `TopBar`, `PageToolbar` (editor header), `PageHeading`, `DocTree` (secondary panel).
|
|
2
|
+
|
|
3
|
+
```jsx
|
|
4
|
+
<Sidebar items={KEYBRICKS_NAV} active="Home" collapsed={false} onToggleCollapse={...} logo="../../assets/keybricks-logo.png"/>
|
|
5
|
+
<TopBar/>
|
|
6
|
+
<PageToolbar title="Oracle Adapter" onBack={...} actions={<>
|
|
7
|
+
<Button variant="ghost" icon="settings-2">Custom Parameters</Button>
|
|
8
|
+
<Button variant="ghost" icon="play">Run Test</Button>
|
|
9
|
+
<Button icon="send">Submit</Button></>}/>
|
|
10
|
+
<DocTree items={[{label:"Q1 2026",kind:"folder"},{label:"财务分析",hue:"orange"},{label:"Revenue Analysis",hue:"green"}]}/>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Set `logo` to the correct relative path to `assets/keybricks-logo.png`.
|
|
14
|
+
|
|
15
|
+
`Breadcrumb` and `ThemeToggle` (additions):
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
<Breadcrumb items={["Management","System Migration","Import"]}/>
|
|
19
|
+
<ThemeToggle/> // uncontrolled: writes html[data-theme] + localStorage
|
|
20
|
+
<ThemeToggle dark={dark} onToggle={setDark}/> // controlled
|
|
21
|
+
applyStoredTheme(); // call once at app start
|
|
22
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** 400px right node-configuration panel (header, preview-rows controls, tabs, scrolling body). */
|
|
3
|
+
export interface ConfigPanelProps {
|
|
4
|
+
title?: string;
|
|
5
|
+
/** "Data Source · Detail Sheet" */
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
tabs?: string[];
|
|
8
|
+
activeTab?: string;
|
|
9
|
+
onTab?: (tab: string) => void;
|
|
10
|
+
previewRows?: number;
|
|
11
|
+
onPreviewRows?: (n: number) => void;
|
|
12
|
+
onSave?: () => void;
|
|
13
|
+
onPreview?: () => void;
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
height?: number | string;
|
|
16
|
+
style?: React.CSSProperties;
|
|
17
|
+
}
|
|
18
|
+
export declare function ConfigPanel(props: ConfigPanelProps): JSX.Element;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Button } from "../buttons/Button.jsx";
|
|
3
|
+
import { PanelTabs } from "../tabs/Tabs.jsx";
|
|
4
|
+
import { NumberStepper } from "../forms/NumberStepper.jsx";
|
|
5
|
+
|
|
6
|
+
/** Right node-config panel — 400px: title/subtitle + Save Settings, preview-rows row, PanelTabs, body slot. */
|
|
7
|
+
export function ConfigPanel({ title, subtitle, tabs = ["Node Info", "Calc Rules", "Output Fields", "SQL Preview"], activeTab, onTab, previewRows = 100, onPreviewRows, onSave, onPreview, children, height = "100%", style }) {
|
|
8
|
+
const [tab, setTab] = React.useState(tabs[0]);
|
|
9
|
+
const cur = activeTab != null ? activeTab : tab;
|
|
10
|
+
return (
|
|
11
|
+
<div style={{ width: "var(--panel-w)", height, background: "var(--bg-surface)", borderLeft: "1px solid var(--border-card)", display: "flex", flexDirection: "column", boxSizing: "border-box", flexShrink: 0, ...style }}>
|
|
12
|
+
<div style={{ padding: "12px 16px 0", borderBottom: "1px solid var(--border-card)", flexShrink: 0 }}>
|
|
13
|
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
|
|
14
|
+
<div style={{ display: "flex", flexDirection: "column" }}>
|
|
15
|
+
<span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-semibold)", fontSize: "var(--text-md)", lineHeight: "17.5px", letterSpacing: "var(--tracking-md)", color: "var(--text-heading)" }}>{title}</span>
|
|
16
|
+
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", lineHeight: "var(--leading-xs)", letterSpacing: "var(--tracking-xs)", color: "var(--text-muted)", paddingTop: 5 }}>{subtitle}</span>
|
|
17
|
+
</div>
|
|
18
|
+
<Button size="sm" onClick={onSave}>Save Settings</Button>
|
|
19
|
+
</div>
|
|
20
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8, padding: "8px 0" }}>
|
|
21
|
+
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-secondary)" }}>Preview rows</span>
|
|
22
|
+
<NumberStepper value={previewRows} onChange={onPreviewRows} />
|
|
23
|
+
<Button variant="primary-soft" size="sm" icon="eye" style={{ height: 28, borderRadius: "var(--radius-sm)", padding: "4px 10px" }} onClick={onPreview}>Data Preview</Button>
|
|
24
|
+
</div>
|
|
25
|
+
<PanelTabs tabs={tabs} active={cur} onChange={(t) => { setTab(t); onTab && onTab(t); }} style={{ margin: "0 -16px", padding: "0 16px", borderBottom: "none" }} />
|
|
26
|
+
</div>
|
|
27
|
+
<div style={{ flex: 1, overflowY: "auto", padding: 16, display: "flex", flexDirection: "column", gap: 16 }}>{children}</div>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import Button from '../buttons/Button.vue'
|
|
4
|
+
import PanelTabs from '../tabs/Tabs.vue'
|
|
5
|
+
import NumberStepper from '../forms/NumberStepper.vue'
|
|
6
|
+
// Right node-config panel — 400px: title/subtitle + Save Settings, preview-rows row, PanelTabs, body slot.
|
|
7
|
+
const props = defineProps({ title: String, subtitle: String, tabs: { type: Array, default: () => ['Node Info', 'Calc Rules', 'Output Fields', 'SQL Preview'] }, activeTab: String, previewRows: { type: Number, default: 100 }, height: { type: [String, Number], default: '100%' }, style: Object })
|
|
8
|
+
const emit = defineEmits(['tab', 'update:previewRows', 'save', 'preview'])
|
|
9
|
+
const tabState = ref(props.tabs[0])
|
|
10
|
+
const cur = computed(() => props.activeTab != null ? props.activeTab : tabState.value)
|
|
11
|
+
function setTab(t) { tabState.value = t; emit('tab', t) }
|
|
12
|
+
</script>
|
|
13
|
+
<template>
|
|
14
|
+
<div :style="{ width: 'var(--panel-w)', height, background: 'var(--bg-surface)', borderLeft: '1px solid var(--border-card)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', flexShrink: 0, ...style }">
|
|
15
|
+
<div style="padding:12px 16px 0;border-bottom:1px solid var(--border-card);flex-shrink:0">
|
|
16
|
+
<div style="display:flex;justify-content:space-between;align-items:flex-start">
|
|
17
|
+
<div style="display:flex;flex-direction:column">
|
|
18
|
+
<span style="font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);line-height:17.5px;letter-spacing:var(--tracking-md);color:var(--text-heading)">{{ title }}</span>
|
|
19
|
+
<span style="font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);letter-spacing:var(--tracking-xs);color:var(--text-muted);padding-top:5px">{{ subtitle }}</span>
|
|
20
|
+
</div>
|
|
21
|
+
<Button size="sm" @click="emit('save')">Save Settings</Button>
|
|
22
|
+
</div>
|
|
23
|
+
<div style="display:flex;align-items:center;gap:8px;padding:8px 0">
|
|
24
|
+
<span style="font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)">Preview rows</span>
|
|
25
|
+
<NumberStepper :model-value="previewRows" @update:modelValue="v => emit('update:previewRows', v)" />
|
|
26
|
+
<Button variant="primary-soft" size="sm" icon="eye" style="height:28px;border-radius:var(--radius-sm);padding:4px 10px" @click="emit('preview')">Data Preview</Button>
|
|
27
|
+
</div>
|
|
28
|
+
<PanelTabs :tabs="tabs" :active="cur" @change="setTab" style="margin:0 -16px;padding:0 16px;border-bottom:none" />
|
|
29
|
+
</div>
|
|
30
|
+
<div style="flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px"><slot /></div>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { DataTableColumn } from "../data/DataTable";
|
|
3
|
+
/** Bottom 250px data-preview drawer with Debug Mode checkbox and dense table. */
|
|
4
|
+
export interface DataPreviewProps {
|
|
5
|
+
title?: string;
|
|
6
|
+
columns?: DataTableColumn[];
|
|
7
|
+
rows?: Record<string, any>[];
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
onDebug?: (v: boolean) => void;
|
|
10
|
+
collapsed?: boolean;
|
|
11
|
+
onCollapse?: () => void;
|
|
12
|
+
style?: React.CSSProperties;
|
|
13
|
+
}
|
|
14
|
+
export declare function DataPreview(props: DataPreviewProps): JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
import { IconButton } from "../buttons/IconButton.jsx";
|
|
4
|
+
import { Checkbox } from "../forms/Checkbox.jsx";
|
|
5
|
+
import { DataTable } from "../data/DataTable.jsx";
|
|
6
|
+
|
|
7
|
+
/** Bottom data-preview drawer — 250px: 40px header (title, Debug Mode, collapse) + dense table. */
|
|
8
|
+
export function DataPreview({ title = "Data Preview", columns = [], rows = [], debug = false, onDebug, collapsed, onCollapse, style }) {
|
|
9
|
+
return (
|
|
10
|
+
<div style={{ background: "var(--bg-surface)", borderTop: "1px solid var(--border-card)", display: "flex", flexDirection: "column", height: collapsed ? 40 : "var(--drawer-h)", overflow: "hidden", boxSizing: "border-box", flexShrink: 0, transition: "height .15s ease", ...style }}>
|
|
11
|
+
<div style={{ height: 40, display: "flex", alignItems: "center", gap: 12, padding: "0 16px", boxSizing: "border-box", flexShrink: 0 }}>
|
|
12
|
+
<span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-semibold)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", color: "var(--text-heading)" }}>{title}</span>
|
|
13
|
+
<span style={{ flex: 1 }} />
|
|
14
|
+
<Checkbox label="Debug Mode" checked={debug} onChange={onDebug} />
|
|
15
|
+
<IconButton icon={collapsed ? "chevron-up" : "chevron-down"} size="xs" muted onClick={onCollapse} />
|
|
16
|
+
</div>
|
|
17
|
+
{!collapsed ? (
|
|
18
|
+
<div style={{ overflowY: "auto", borderTop: "1px solid var(--border-subtle)" }}>
|
|
19
|
+
<DataTable dense columns={columns} rows={rows} headerBorder={false} />
|
|
20
|
+
</div>
|
|
21
|
+
) : null}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import IconButton from '../buttons/IconButton.vue'
|
|
3
|
+
import Checkbox from '../forms/Checkbox.vue'
|
|
4
|
+
import DataTable from '../data/DataTable.vue'
|
|
5
|
+
// Bottom data-preview drawer — 250px: 40px header (title, Debug Mode, collapse) + dense table.
|
|
6
|
+
defineProps({ title: { type: String, default: 'Data Preview' }, columns: { type: Array, default: () => [] }, rows: { type: Array, default: () => [] }, debug: Boolean, collapsed: Boolean, style: Object })
|
|
7
|
+
const emit = defineEmits(['update:debug', 'toggleCollapse'])
|
|
8
|
+
</script>
|
|
9
|
+
<template>
|
|
10
|
+
<div :style="{ background: 'var(--bg-surface)', borderTop: '1px solid var(--border-card)', display: 'flex', flexDirection: 'column', height: collapsed ? 40 : 'var(--drawer-h)', overflow: 'hidden', boxSizing: 'border-box', flexShrink: 0, transition: 'height .15s ease', ...style }">
|
|
11
|
+
<div style="height:40px;display:flex;align-items:center;gap:12px;padding:0 16px;box-sizing:border-box;flex-shrink:0">
|
|
12
|
+
<span style="font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)">{{ title }}</span>
|
|
13
|
+
<span style="flex:1" />
|
|
14
|
+
<Checkbox label="Debug Mode" :checked="debug" @change="v => emit('update:debug', v)" />
|
|
15
|
+
<IconButton :icon="collapsed ? 'chevron-up' : 'chevron-down'" size="xs" muted @click="emit('toggleCollapse')" />
|
|
16
|
+
</div>
|
|
17
|
+
<div v-if="!collapsed" style="overflow-y:auto;border-top:1px solid var(--border-subtle)">
|
|
18
|
+
<DataTable dense :columns="columns" :rows="rows" :header-border="false" />
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Canvas pipeline node — 220×90 r16 with tinted 48px icon tile, ports, status badges.
|
|
4
|
+
*/
|
|
5
|
+
export interface NodeCardProps {
|
|
6
|
+
title?: string;
|
|
7
|
+
/** e.g. "Detail Sheet", "Association Match" */
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
icon?: string;
|
|
10
|
+
/** 13px glyph before the subtitle (default "info"); false hides it */
|
|
11
|
+
subtitleIcon?: string | false;
|
|
12
|
+
hue?: "source" | "join" | "transform" | "merge" | "output";
|
|
13
|
+
selected?: boolean;
|
|
14
|
+
/** Icon-tile overlay dot */
|
|
15
|
+
badge?: "error" | "warning" | "clock";
|
|
16
|
+
/** [] no input · [null] plain port · ["A","B"] labeled pills */
|
|
17
|
+
inputs?: (string | null)[];
|
|
18
|
+
output?: boolean;
|
|
19
|
+
showMenu?: boolean;
|
|
20
|
+
onClick?: () => void;
|
|
21
|
+
onMenu?: (e: React.MouseEvent) => void;
|
|
22
|
+
/** Pointer-down on the output port — the canvas uses this to start a wire */
|
|
23
|
+
onOutputDown?: (e: React.PointerEvent) => void;
|
|
24
|
+
/** Pointer-up on input port `i` */
|
|
25
|
+
onInputUp?: (i: number, e: React.PointerEvent) => void;
|
|
26
|
+
onInputEnter?: (i: number) => void;
|
|
27
|
+
onInputLeave?: (i: number) => void;
|
|
28
|
+
/** "active" while a wire is being dragged out of this node */
|
|
29
|
+
outputTone?: PortTone;
|
|
30
|
+
/** Per-input tone; "target" marks the port under the cursor mid-drag */
|
|
31
|
+
inputTones?: PortTone[];
|
|
32
|
+
style?: React.CSSProperties;
|
|
33
|
+
}
|
|
34
|
+
export declare function NodeCard(props: NodeCardProps): JSX.Element;
|
|
35
|
+
|
|
36
|
+
/** default = grey · active = dragging from it · target = valid drop, shows a crosshair */
|
|
37
|
+
export type PortTone = "default" | "active" | "target";
|
|
38
|
+
export interface PortProps {
|
|
39
|
+
tone?: PortTone;
|
|
40
|
+
/** Enables hover lighting and the grab cursor */
|
|
41
|
+
interactive?: boolean;
|
|
42
|
+
style?: React.CSSProperties;
|
|
43
|
+
}
|
|
44
|
+
/** 22px round port. */
|
|
45
|
+
export declare function Port(props: PortProps): JSX.Element;
|
|
46
|
+
/** 28×22 "A >" input pill. */
|
|
47
|
+
export declare function PortLabel(props: PortProps & { label?: string }): JSX.Element;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
import { IconTile, StatusBadge } from "../display/Badge.jsx";
|
|
4
|
+
|
|
5
|
+
/* Ports are the connection handles: they light up on hover, stay lit while a wire is
|
|
6
|
+
being dragged out of them, and show a crosshair when they're a valid drop target. */
|
|
7
|
+
function portSkin(tone, hover, interactive) {
|
|
8
|
+
const hot = tone === "active" || tone === "target" || (interactive && hover);
|
|
9
|
+
return {
|
|
10
|
+
background: tone === "target" ? "var(--soft-bg)" : "var(--bg-surface)",
|
|
11
|
+
border: `2px solid ${hot ? "var(--action-primary)" : "var(--port-border)"}`,
|
|
12
|
+
boxShadow: tone === "active" || tone === "target" ? "0 0 0 3px var(--focus-ring)" : "none",
|
|
13
|
+
color: hot ? "var(--action-primary)" : "var(--text-muted)",
|
|
14
|
+
cursor: interactive ? (tone === "active" ? "grabbing" : "grab") : "default",
|
|
15
|
+
transition: "var(--t-hover)",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Round output/input port — 22px. tone: "default" | "active" (dragging from it) | "target" (drop here). */
|
|
20
|
+
export function Port({ tone = "default", interactive = false, style, onMouseEnter, onMouseLeave, ...rest }) {
|
|
21
|
+
const [hover, setHover] = React.useState(false);
|
|
22
|
+
return (
|
|
23
|
+
<span {...rest}
|
|
24
|
+
onMouseEnter={(e) => { setHover(true); onMouseEnter && onMouseEnter(e); }}
|
|
25
|
+
onMouseLeave={(e) => { setHover(false); onMouseLeave && onMouseLeave(e); }}
|
|
26
|
+
style={{ width: 22, height: 22, borderRadius: "var(--radius-full)", boxSizing: "border-box", display: "inline-flex", alignItems: "center", justifyContent: "center", ...portSkin(tone, hover, interactive), ...style }}>
|
|
27
|
+
<Icon name={tone === "target" ? "plus" : "arrow-right"} size={12} strokeWidth={2.2} />
|
|
28
|
+
</span>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Labeled input port pill ("A >", "B >") — 28×22, same three tones as Port. */
|
|
33
|
+
export function PortLabel({ label = "A", tone = "default", interactive = false, style, onMouseEnter, onMouseLeave, ...rest }) {
|
|
34
|
+
const [hover, setHover] = React.useState(false);
|
|
35
|
+
return (
|
|
36
|
+
<span {...rest}
|
|
37
|
+
onMouseEnter={(e) => { setHover(true); onMouseEnter && onMouseEnter(e); }}
|
|
38
|
+
onMouseLeave={(e) => { setHover(false); onMouseLeave && onMouseLeave(e); }}
|
|
39
|
+
style={{ width: 28, height: 22, borderRadius: "var(--radius-full)", boxSizing: "border-box", display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 2, fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", letterSpacing: "var(--tracking-xs)", lineHeight: 1, ...portSkin(tone, hover, interactive), ...style }}>
|
|
40
|
+
{tone === "target" ? <Icon name="plus" size={12} strokeWidth={2.2} /> : <>{label}<Icon name="chevron-right" size={9} strokeWidth={2.5} /></>}
|
|
41
|
+
</span>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Canvas node card — 220×90, r16, soft shadow. Selected = blue border + blue-100 ring.
|
|
47
|
+
* badge: "error" | "warning" | "clock" overlay on the icon tile.
|
|
48
|
+
* Port interaction is driven by the canvas: onOutputDown starts a wire, onInputUp lands it,
|
|
49
|
+
* and outputTone / inputTones light the handles.
|
|
50
|
+
*/
|
|
51
|
+
export function NodeCard({ title, subtitle, icon = "table", subtitleIcon, hue = "source", selected, badge, inputs = [], output = true, showMenu = true, onClick, onMenu, onOutputDown, onInputUp, onInputEnter, onInputLeave, outputTone = "default", inputTones = [], style }) {
|
|
52
|
+
const live = !!(onOutputDown || onInputUp);
|
|
53
|
+
return (
|
|
54
|
+
<div onClick={onClick} style={{ position: "relative", width: 220, height: 90, borderRadius: "var(--radius-node)", background: "var(--bg-surface)", border: `1px solid ${selected ? "var(--selected-border)" : "var(--border-control)"}`, boxShadow: selected ? "var(--shadow-selected)" : "var(--shadow-node)", boxSizing: "border-box", cursor: "pointer", ...style }}>
|
|
55
|
+
<div style={{ display: "flex", alignItems: "center", gap: 12, padding: 16, height: "100%", boxSizing: "border-box" }}>
|
|
56
|
+
<span style={{ position: "relative", flexShrink: 0 }}>
|
|
57
|
+
<IconTile icon={icon} hue={hue} kind="node" />
|
|
58
|
+
{badge === "error" ? <StatusBadge tone="danger" icon="bug" size={20} style={{ position: "absolute", top: -6, right: -6 }} /> : null}
|
|
59
|
+
{badge === "warning" ? <StatusBadge tone="warning" icon="triangle-alert" size={20} style={{ position: "absolute", top: -6, right: -6 }} /> : null}
|
|
60
|
+
{badge === "clock" ? <StatusBadge tone="pending" icon="clock" size={20} style={{ position: "absolute", top: -6, right: -6 }} /> : null}
|
|
61
|
+
</span>
|
|
62
|
+
<div style={{ display: "flex", flexDirection: "column", minWidth: 0, flex: 1, paddingRight: 18 }}>
|
|
63
|
+
<span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-semibold)", fontSize: "var(--text-base)", lineHeight: "17.875px", letterSpacing: "var(--tracking-base)", color: "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{title}</span>
|
|
64
|
+
<span style={{ display: "flex", alignItems: "flex-start", gap: 4, paddingTop: 4, minWidth: 0 }}>
|
|
65
|
+
{subtitleIcon !== false ? <span style={{ color: "var(--text-muted)", display: "inline-flex", flexShrink: 0, paddingTop: 1 }}><Icon name={subtitleIcon || "info"} size={13} /></span> : null}
|
|
66
|
+
{/* Node types run long ("Field Value Calculation"), so the subtitle wraps to a
|
|
67
|
+
second line and only then clamps — the card size never changes. */}
|
|
68
|
+
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", lineHeight: "13.75px", letterSpacing: "var(--tracking-xs)", color: "var(--text-muted)", minWidth: 0, overflow: "hidden", display: "-webkit-box", WebkitBoxOrient: "vertical", WebkitLineClamp: 2, overflowWrap: "break-word" }}>{subtitle}</span>
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
{showMenu ? (
|
|
73
|
+
<button type="button" onClick={(e) => { e.stopPropagation(); onMenu && onMenu(e); }} style={{ position: "absolute", top: 10, right: 12, width: 23, height: 23, borderRadius: "var(--radius-md)", border: "none", background: "transparent", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--text-muted)", padding: 0 }}
|
|
74
|
+
onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-hover)"} onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
|
|
75
|
+
<Icon name="ellipsis" size={15} />
|
|
76
|
+
</button>
|
|
77
|
+
) : null}
|
|
78
|
+
{inputs.length === 0 ? null : inputs.length === 1 && !inputs[0] ? (
|
|
79
|
+
<Port tone={inputTones[0] || "default"} interactive={live}
|
|
80
|
+
onMouseEnter={onInputEnter ? () => onInputEnter(0) : undefined} onMouseLeave={onInputLeave ? () => onInputLeave(0) : undefined}
|
|
81
|
+
onPointerUp={onInputUp ? (e) => onInputUp(0, e) : undefined}
|
|
82
|
+
style={{ position: "absolute", left: -11, top: 33 }} />
|
|
83
|
+
) : inputs.map((l, i) => (
|
|
84
|
+
<PortLabel key={i} label={l} tone={inputTones[i] || "default"} interactive={live}
|
|
85
|
+
onMouseEnter={onInputEnter ? () => onInputEnter(i) : undefined} onMouseLeave={onInputLeave ? () => onInputLeave(i) : undefined}
|
|
86
|
+
onPointerUp={onInputUp ? (e) => onInputUp(i, e) : undefined}
|
|
87
|
+
style={{ position: "absolute", left: -14, top: inputs.length === 1 ? 33 : 12 + i * 38 }} />
|
|
88
|
+
))}
|
|
89
|
+
{output ? (
|
|
90
|
+
<Port tone={outputTone} interactive={live}
|
|
91
|
+
onPointerDown={onOutputDown ? (e) => { e.stopPropagation(); onOutputDown(e); } : undefined}
|
|
92
|
+
onClick={onOutputDown ? (e) => e.stopPropagation() : undefined}
|
|
93
|
+
style={{ position: "absolute", right: -11, top: 33 }} />
|
|
94
|
+
) : null}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|