@textui/widgets 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/chart/area-chart.d.ts +4 -0
- package/dist/chart/area-chart.d.ts.map +1 -0
- package/dist/chart/area-chart.js +3 -0
- package/dist/chart/bar-chart.d.ts +32 -0
- package/dist/chart/bar-chart.d.ts.map +1 -0
- package/dist/chart/bar-chart.js +32 -0
- package/dist/chart/gauge.d.ts +26 -0
- package/dist/chart/gauge.d.ts.map +1 -0
- package/dist/chart/gauge.js +26 -0
- package/dist/chart/heatmap.d.ts +13 -0
- package/dist/chart/heatmap.d.ts.map +1 -0
- package/dist/chart/heatmap.js +27 -0
- package/dist/chart/histogram.d.ts +12 -0
- package/dist/chart/histogram.d.ts.map +1 -0
- package/dist/chart/histogram.js +22 -0
- package/dist/chart/index.d.ts +20 -0
- package/dist/chart/index.d.ts.map +1 -0
- package/dist/chart/index.js +32 -0
- package/dist/chart/line-chart.d.ts +7 -0
- package/dist/chart/line-chart.d.ts.map +1 -0
- package/dist/chart/line-chart.js +75 -0
- package/dist/chart/shared.d.ts +34 -0
- package/dist/chart/shared.d.ts.map +1 -0
- package/dist/chart/shared.js +78 -0
- package/dist/chart/sparkline.d.ts +15 -0
- package/dist/chart/sparkline.d.ts.map +1 -0
- package/dist/chart/sparkline.js +22 -0
- package/dist/control/button.d.ts +27 -0
- package/dist/control/button.d.ts.map +1 -0
- package/dist/control/button.js +130 -0
- package/dist/control/checkbox.d.ts +10 -0
- package/dist/control/checkbox.d.ts.map +1 -0
- package/dist/control/checkbox.js +31 -0
- package/dist/control/index.d.ts +20 -0
- package/dist/control/index.d.ts.map +1 -0
- package/dist/control/index.js +36 -0
- package/dist/control/radio-group.d.ts +17 -0
- package/dist/control/radio-group.d.ts.map +1 -0
- package/dist/control/radio-group.js +32 -0
- package/dist/control/search-box.d.ts +7 -0
- package/dist/control/search-box.d.ts.map +1 -0
- package/dist/control/search-box.js +3 -0
- package/dist/control/select.d.ts +42 -0
- package/dist/control/select.d.ts.map +1 -0
- package/dist/control/select.js +166 -0
- package/dist/control/shared.d.ts +41 -0
- package/dist/control/shared.d.ts.map +1 -0
- package/dist/control/shared.js +1 -0
- package/dist/control/slider.d.ts +14 -0
- package/dist/control/slider.d.ts.map +1 -0
- package/dist/control/slider.js +45 -0
- package/dist/control/switch.d.ts +10 -0
- package/dist/control/switch.d.ts.map +1 -0
- package/dist/control/switch.js +32 -0
- package/dist/control/text-area.d.ts +87 -0
- package/dist/control/text-area.d.ts.map +1 -0
- package/dist/control/text-area.js +299 -0
- package/dist/control/text-input.d.ts +10 -0
- package/dist/control/text-input.d.ts.map +1 -0
- package/dist/control/text-input.js +129 -0
- package/dist/data/code-viewer.d.ts +71 -0
- package/dist/data/code-viewer.d.ts.map +1 -0
- package/dist/data/code-viewer.js +232 -0
- package/dist/data/feed.d.ts +43 -0
- package/dist/data/feed.d.ts.map +1 -0
- package/dist/data/feed.js +178 -0
- package/dist/data/index.d.ts +54 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +187 -0
- package/dist/data/list.d.ts +31 -0
- package/dist/data/list.d.ts.map +1 -0
- package/dist/data/list.js +104 -0
- package/dist/data/log-viewer.d.ts +26 -0
- package/dist/data/log-viewer.d.ts.map +1 -0
- package/dist/data/log-viewer.js +69 -0
- package/dist/data/markdown-view.d.ts +31 -0
- package/dist/data/markdown-view.d.ts.map +1 -0
- package/dist/data/markdown-view.js +139 -0
- package/dist/data/pagination.d.ts +9 -0
- package/dist/data/pagination.d.ts.map +1 -0
- package/dist/data/pagination.js +18 -0
- package/dist/data/scroll-thumb.d.ts +15 -0
- package/dist/data/scroll-thumb.d.ts.map +1 -0
- package/dist/data/scroll-thumb.js +32 -0
- package/dist/data/shared.d.ts +12 -0
- package/dist/data/shared.d.ts.map +1 -0
- package/dist/data/shared.js +17 -0
- package/dist/data/tree.d.ts +35 -0
- package/dist/data/tree.d.ts.map +1 -0
- package/dist/data/tree.js +158 -0
- package/dist/decorations.d.ts +88 -0
- package/dist/decorations.d.ts.map +1 -0
- package/dist/decorations.js +135 -0
- package/dist/display/alert.d.ts +8 -0
- package/dist/display/alert.d.ts.map +1 -0
- package/dist/display/alert.js +12 -0
- package/dist/display/badge.d.ts +10 -0
- package/dist/display/badge.d.ts.map +1 -0
- package/dist/display/badge.js +21 -0
- package/dist/display/card.d.ts +8 -0
- package/dist/display/card.d.ts.map +1 -0
- package/dist/display/card.js +12 -0
- package/dist/display/empty-state.d.ts +10 -0
- package/dist/display/empty-state.d.ts.map +1 -0
- package/dist/display/empty-state.js +5 -0
- package/dist/display/error-state.d.ts +9 -0
- package/dist/display/error-state.d.ts.map +1 -0
- package/dist/display/error-state.js +7 -0
- package/dist/display/heading.d.ts +6 -0
- package/dist/display/heading.d.ts.map +1 -0
- package/dist/display/heading.js +8 -0
- package/dist/display/index.d.ts +25 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +52 -0
- package/dist/display/key-value.d.ts +14 -0
- package/dist/display/key-value.d.ts.map +1 -0
- package/dist/display/key-value.js +11 -0
- package/dist/display/label.d.ts +6 -0
- package/dist/display/label.d.ts.map +1 -0
- package/dist/display/label.js +3 -0
- package/dist/display/marquee.d.ts +36 -0
- package/dist/display/marquee.d.ts.map +1 -0
- package/dist/display/marquee.js +55 -0
- package/dist/display/progress.d.ts +39 -0
- package/dist/display/progress.d.ts.map +1 -0
- package/dist/display/progress.js +46 -0
- package/dist/display/skeleton.d.ts +20 -0
- package/dist/display/skeleton.d.ts.map +1 -0
- package/dist/display/skeleton.js +20 -0
- package/dist/display/spinner.d.ts +7 -0
- package/dist/display/spinner.d.ts.map +1 -0
- package/dist/display/spinner.js +9 -0
- package/dist/display/status-dot.d.ts +8 -0
- package/dist/display/status-dot.d.ts.map +1 -0
- package/dist/display/status-dot.js +13 -0
- package/dist/display/timeline.d.ts +12 -0
- package/dist/display/timeline.d.ts.map +1 -0
- package/dist/display/timeline.js +22 -0
- package/dist/find.d.ts +74 -0
- package/dist/find.d.ts.map +1 -0
- package/dist/find.js +102 -0
- package/dist/form/danger-zone.d.ts +11 -0
- package/dist/form/danger-zone.d.ts.map +1 -0
- package/dist/form/danger-zone.js +15 -0
- package/dist/form/field.d.ts +19 -0
- package/dist/form/field.d.ts.map +1 -0
- package/dist/form/field.js +35 -0
- package/dist/form/form-actions.d.ts +11 -0
- package/dist/form/form-actions.d.ts.map +1 -0
- package/dist/form/form-actions.js +16 -0
- package/dist/form/form-section.d.ts +7 -0
- package/dist/form/form-section.d.ts.map +1 -0
- package/dist/form/form-section.js +5 -0
- package/dist/form/form.d.ts +7 -0
- package/dist/form/form.d.ts.map +1 -0
- package/dist/form/form.js +3 -0
- package/dist/form/index.d.ts +42 -0
- package/dist/form/index.d.ts.map +1 -0
- package/dist/form/index.js +140 -0
- package/dist/form/shared.d.ts +25 -0
- package/dist/form/shared.d.ts.map +1 -0
- package/dist/form/shared.js +9 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/layout/center.d.ts +7 -0
- package/dist/layout/center.d.ts.map +1 -0
- package/dist/layout/center.js +8 -0
- package/dist/layout/column.d.ts +3 -0
- package/dist/layout/column.d.ts.map +1 -0
- package/dist/layout/column.js +2 -0
- package/dist/layout/divider.d.ts +11 -0
- package/dist/layout/divider.d.ts.map +1 -0
- package/dist/layout/divider.js +13 -0
- package/dist/layout/grid.d.ts +10 -0
- package/dist/layout/grid.d.ts.map +1 -0
- package/dist/layout/grid.js +22 -0
- package/dist/layout/index.d.ts +19 -0
- package/dist/layout/index.d.ts.map +1 -0
- package/dist/layout/index.js +36 -0
- package/dist/layout/panel.d.ts +37 -0
- package/dist/layout/panel.d.ts.map +1 -0
- package/dist/layout/panel.js +71 -0
- package/dist/layout/row.d.ts +7 -0
- package/dist/layout/row.d.ts.map +1 -0
- package/dist/layout/row.js +2 -0
- package/dist/layout/scroll-view.d.ts +27 -0
- package/dist/layout/scroll-view.d.ts.map +1 -0
- package/dist/layout/scroll-view.js +93 -0
- package/dist/layout/splitter.d.ts +10 -0
- package/dist/layout/splitter.d.ts.map +1 -0
- package/dist/layout/splitter.js +9 -0
- package/dist/layout/stack.d.ts +7 -0
- package/dist/layout/stack.d.ts.map +1 -0
- package/dist/layout/stack.js +5 -0
- package/dist/navigation/breadcrumb.d.ts +14 -0
- package/dist/navigation/breadcrumb.d.ts.map +1 -0
- package/dist/navigation/breadcrumb.js +24 -0
- package/dist/navigation/index.d.ts +17 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +30 -0
- package/dist/navigation/key-hints.d.ts +11 -0
- package/dist/navigation/key-hints.d.ts.map +1 -0
- package/dist/navigation/key-hints.js +13 -0
- package/dist/navigation/menu.d.ts +35 -0
- package/dist/navigation/menu.d.ts.map +1 -0
- package/dist/navigation/menu.js +88 -0
- package/dist/navigation/status-bar.d.ts +18 -0
- package/dist/navigation/status-bar.d.ts.map +1 -0
- package/dist/navigation/status-bar.js +12 -0
- package/dist/navigation/tabs.d.ts +20 -0
- package/dist/navigation/tabs.d.ts.map +1 -0
- package/dist/navigation/tabs.js +41 -0
- package/dist/navigation/toolbar.d.ts +14 -0
- package/dist/navigation/toolbar.d.ts.map +1 -0
- package/dist/navigation/toolbar.js +10 -0
- package/dist/navigation/wizard.d.ts +15 -0
- package/dist/navigation/wizard.d.ts.map +1 -0
- package/dist/navigation/wizard.js +17 -0
- package/dist/overlay/command-palette.d.ts +63 -0
- package/dist/overlay/command-palette.d.ts.map +1 -0
- package/dist/overlay/command-palette.js +415 -0
- package/dist/overlay/dialog.d.ts +16 -0
- package/dist/overlay/dialog.d.ts.map +1 -0
- package/dist/overlay/dialog.js +44 -0
- package/dist/overlay/index.d.ts +84 -0
- package/dist/overlay/index.d.ts.map +1 -0
- package/dist/overlay/index.js +157 -0
- package/dist/overlay/layer-scope.d.ts +21 -0
- package/dist/overlay/layer-scope.d.ts.map +1 -0
- package/dist/overlay/layer-scope.js +5 -0
- package/dist/overlay/path-picker.d.ts +14 -0
- package/dist/overlay/path-picker.d.ts.map +1 -0
- package/dist/overlay/path-picker.js +200 -0
- package/dist/overlay/prompt-dialog.d.ts +12 -0
- package/dist/overlay/prompt-dialog.d.ts.map +1 -0
- package/dist/overlay/prompt-dialog.js +29 -0
- package/dist/overlay/shared.d.ts +17 -0
- package/dist/overlay/shared.d.ts.map +1 -0
- package/dist/overlay/shared.js +17 -0
- package/dist/overlay/toast-host.d.ts +8 -0
- package/dist/overlay/toast-host.d.ts.map +1 -0
- package/dist/overlay/toast-host.js +19 -0
- package/dist/overlay/toast.d.ts +9 -0
- package/dist/overlay/toast.d.ts.map +1 -0
- package/dist/overlay/toast.js +20 -0
- package/dist/overlay/tooltip.d.ts +6 -0
- package/dist/overlay/tooltip.d.ts.map +1 -0
- package/dist/overlay/tooltip.js +11 -0
- package/dist/panel/index.d.ts +79 -0
- package/dist/panel/index.d.ts.map +1 -0
- package/dist/panel/index.js +256 -0
- package/dist/panel/resource-panel.d.ts +93 -0
- package/dist/panel/resource-panel.d.ts.map +1 -0
- package/dist/panel/resource-panel.js +213 -0
- package/dist/panel/shared.d.ts +6 -0
- package/dist/panel/shared.d.ts.map +1 -0
- package/dist/panel/shared.js +4 -0
- package/dist/shells/console-shell.d.ts +8 -0
- package/dist/shells/console-shell.d.ts.map +1 -0
- package/dist/shells/console-shell.js +30 -0
- package/dist/shells/index.d.ts +20 -0
- package/dist/shells/index.d.ts.map +1 -0
- package/dist/shells/index.js +61 -0
- package/dist/shells/paper-shell.d.ts +9 -0
- package/dist/shells/paper-shell.d.ts.map +1 -0
- package/dist/shells/paper-shell.js +23 -0
- package/dist/shells/plain-shell.d.ts +3 -0
- package/dist/shells/plain-shell.d.ts.map +1 -0
- package/dist/shells/plain-shell.js +11 -0
- package/dist/shells/shared.d.ts +12 -0
- package/dist/shells/shared.d.ts.map +1 -0
- package/dist/shells/shared.js +21 -0
- package/dist/shells/workbench-shell.d.ts +9 -0
- package/dist/shells/workbench-shell.d.ts.map +1 -0
- package/dist/shells/workbench-shell.js +43 -0
- package/dist/surface/bar-layout.d.ts +4 -0
- package/dist/surface/bar-layout.d.ts.map +1 -0
- package/dist/surface/bar-layout.js +7 -0
- package/dist/surface/index.d.ts +58 -0
- package/dist/surface/index.d.ts.map +1 -0
- package/dist/surface/index.js +48 -0
- package/dist/surface/inline-layout.d.ts +4 -0
- package/dist/surface/inline-layout.d.ts.map +1 -0
- package/dist/surface/inline-layout.js +7 -0
- package/dist/surface/mount-view.d.ts +7 -0
- package/dist/surface/mount-view.d.ts.map +1 -0
- package/dist/surface/mount-view.js +19 -0
- package/dist/surface/rail-layout.d.ts +4 -0
- package/dist/surface/rail-layout.d.ts.map +1 -0
- package/dist/surface/rail-layout.js +19 -0
- package/dist/surface/shared.d.ts +7 -0
- package/dist/surface/shared.d.ts.map +1 -0
- package/dist/surface/shared.js +1 -0
- package/dist/surface/single-layout.d.ts +4 -0
- package/dist/surface/single-layout.d.ts.map +1 -0
- package/dist/surface/single-layout.js +18 -0
- package/dist/surface/split-layout.d.ts +4 -0
- package/dist/surface/split-layout.d.ts.map +1 -0
- package/dist/surface/split-layout.js +7 -0
- package/dist/surface/stack-layout.d.ts +4 -0
- package/dist/surface/stack-layout.d.ts.map +1 -0
- package/dist/surface/stack-layout.js +22 -0
- package/dist/surface/surface-area.d.ts +16 -0
- package/dist/surface/surface-area.d.ts.map +1 -0
- package/dist/surface/surface-area.js +32 -0
- package/dist/surface/tabs-layout.d.ts +4 -0
- package/dist/surface/tabs-layout.d.ts.map +1 -0
- package/dist/surface/tabs-layout.js +24 -0
- package/dist/tone.d.ts +16 -0
- package/dist/tone.d.ts.map +1 -0
- package/dist/tone.js +45 -0
- package/dist/viewport.d.ts +30 -0
- package/dist/viewport.d.ts.map +1 -0
- package/dist/viewport.js +17 -0
- package/package.json +54 -0
- package/src/chart/area-chart.ts +9 -0
- package/src/chart/bar-chart.ts +74 -0
- package/src/chart/gauge.ts +60 -0
- package/src/chart/heatmap.ts +49 -0
- package/src/chart/histogram.ts +36 -0
- package/src/chart/index.ts +36 -0
- package/src/chart/line-chart.ts +100 -0
- package/src/chart/shared.ts +112 -0
- package/src/chart/sparkline.ts +46 -0
- package/src/control/button.ts +184 -0
- package/src/control/checkbox.ts +48 -0
- package/src/control/index.ts +40 -0
- package/src/control/radio-group.ts +60 -0
- package/src/control/search-box.ts +15 -0
- package/src/control/select.ts +230 -0
- package/src/control/shared.ts +41 -0
- package/src/control/slider.ts +63 -0
- package/src/control/switch.ts +39 -0
- package/src/control/text-area.ts +390 -0
- package/src/control/text-input.ts +150 -0
- package/src/data/code-viewer.ts +337 -0
- package/src/data/feed.ts +204 -0
- package/src/data/index.ts +243 -0
- package/src/data/list.ts +149 -0
- package/src/data/log-viewer.ts +105 -0
- package/src/data/markdown-view.ts +182 -0
- package/src/data/pagination.ts +31 -0
- package/src/data/scroll-thumb.ts +40 -0
- package/src/data/shared.ts +23 -0
- package/src/data/tree.ts +215 -0
- package/src/decorations.ts +176 -0
- package/src/display/alert.ts +28 -0
- package/src/display/badge.ts +40 -0
- package/src/display/card.ts +23 -0
- package/src/display/empty-state.ts +21 -0
- package/src/display/error-state.ts +23 -0
- package/src/display/heading.ts +17 -0
- package/src/display/index.ts +55 -0
- package/src/display/key-value.ts +32 -0
- package/src/display/label.ts +11 -0
- package/src/display/marquee.ts +88 -0
- package/src/display/progress.ts +92 -0
- package/src/display/skeleton.ts +34 -0
- package/src/display/spinner.ts +20 -0
- package/src/display/status-dot.ts +24 -0
- package/src/display/timeline.ts +47 -0
- package/src/find.ts +138 -0
- package/src/form/danger-zone.ts +34 -0
- package/src/form/field.ts +61 -0
- package/src/form/form-actions.ts +35 -0
- package/src/form/form-section.ts +16 -0
- package/src/form/form.ts +13 -0
- package/src/form/index.ts +191 -0
- package/src/form/shared.ts +36 -0
- package/src/index.ts +63 -0
- package/src/layout/center.ts +17 -0
- package/src/layout/column.ts +6 -0
- package/src/layout/divider.ts +31 -0
- package/src/layout/grid.ts +34 -0
- package/src/layout/index.ts +39 -0
- package/src/layout/panel.ts +114 -0
- package/src/layout/row.ts +11 -0
- package/src/layout/scroll-view.ts +115 -0
- package/src/layout/splitter.ts +24 -0
- package/src/layout/stack.ts +12 -0
- package/src/navigation/breadcrumb.ts +43 -0
- package/src/navigation/index.ts +33 -0
- package/src/navigation/key-hints.ts +25 -0
- package/src/navigation/menu.ts +134 -0
- package/src/navigation/status-bar.ts +43 -0
- package/src/navigation/tabs.ts +73 -0
- package/src/navigation/toolbar.ts +25 -0
- package/src/navigation/wizard.ts +43 -0
- package/src/overlay/command-palette.ts +485 -0
- package/src/overlay/dialog.ts +65 -0
- package/src/overlay/index.ts +232 -0
- package/src/overlay/layer-scope.ts +26 -0
- package/src/overlay/path-picker.ts +215 -0
- package/src/overlay/prompt-dialog.ts +45 -0
- package/src/overlay/shared.ts +19 -0
- package/src/overlay/toast-host.ts +30 -0
- package/src/overlay/toast.ts +35 -0
- package/src/overlay/tooltip.ts +17 -0
- package/src/panel/index.ts +317 -0
- package/src/panel/resource-panel.ts +304 -0
- package/src/panel/shared.ts +8 -0
- package/src/shells/console-shell.ts +47 -0
- package/src/shells/index.ts +66 -0
- package/src/shells/paper-shell.ts +31 -0
- package/src/shells/plain-shell.ts +19 -0
- package/src/shells/shared.ts +24 -0
- package/src/shells/workbench-shell.ts +62 -0
- package/src/surface/bar-layout.ts +11 -0
- package/src/surface/index.ts +53 -0
- package/src/surface/inline-layout.ts +11 -0
- package/src/surface/mount-view.ts +30 -0
- package/src/surface/rail-layout.ts +27 -0
- package/src/surface/shared.ts +7 -0
- package/src/surface/single-layout.ts +25 -0
- package/src/surface/split-layout.ts +11 -0
- package/src/surface/stack-layout.ts +32 -0
- package/src/surface/surface-area.ts +46 -0
- package/src/surface/tabs-layout.ts +30 -0
- package/src/tone.ts +48 -0
- package/src/viewport.ts +45 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type { BoxProps, ComponentNode, RenderOutput } from '@textui/core';
|
|
2
|
+
import {
|
|
3
|
+
defineComponent, h, useApp, useEffect, useFocus, useInput, useMeasure, useState, useTheme,
|
|
4
|
+
} from '@textui/core';
|
|
5
|
+
|
|
6
|
+
export interface SelectOption {
|
|
7
|
+
value: string;
|
|
8
|
+
label: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
icon?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SelectProps extends BoxProps {
|
|
15
|
+
options: SelectOption[];
|
|
16
|
+
value?: string;
|
|
17
|
+
onChange?(value: string): void;
|
|
18
|
+
label?: string;
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
/** Show the list inline instead of collapsing to one line. */
|
|
21
|
+
open?: boolean;
|
|
22
|
+
/** Rows shown at once when open. */
|
|
23
|
+
visibleRows?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Where the list goes when it opens.
|
|
26
|
+
*
|
|
27
|
+
* `inline` grows the control: the options appear inside the same border, and
|
|
28
|
+
* everything under it moves down. Honest about the space it takes and the
|
|
29
|
+
* only one that cannot be clipped, which is why it is the default.
|
|
30
|
+
*
|
|
31
|
+
* `floating` puts the list on the floating layer, anchored under the
|
|
32
|
+
* control. Nothing below it moves, which is what you want in a form or a
|
|
33
|
+
* dense row of controls - the layout does not jump as you open and shut it.
|
|
34
|
+
*
|
|
35
|
+
* `modal` puts it in the middle of the screen over a scrim. For a list long
|
|
36
|
+
* enough or a choice consequential enough that the rest of the screen is a
|
|
37
|
+
* distraction.
|
|
38
|
+
*
|
|
39
|
+
* The keys are the same in all three, because the control keeps the keyboard
|
|
40
|
+
* in all three: the layer is somewhere to *draw* the list, not somewhere the
|
|
41
|
+
* focus goes. Arrow keys, enter and escape are answered by the same handler
|
|
42
|
+
* whichever mode it is in.
|
|
43
|
+
*/
|
|
44
|
+
mode?: 'inline' | 'floating' | 'modal';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const Select = defineComponent<SelectProps>('Select', (props) => {
|
|
48
|
+
const theme = useTheme();
|
|
49
|
+
const {
|
|
50
|
+
options, value, onChange, label, placeholder,
|
|
51
|
+
open: openProp, visibleRows = 6, disabled, mode = 'inline', ...rest
|
|
52
|
+
} = props;
|
|
53
|
+
|
|
54
|
+
const app = useApp();
|
|
55
|
+
const focus = useFocus({ disabled });
|
|
56
|
+
const measured = useMeasure();
|
|
57
|
+
const empty = placeholder ?? `Select${theme.glyphs.ellipsis}`;
|
|
58
|
+
const [open, setOpen] = useState(openProp ?? false);
|
|
59
|
+
const index = Math.max(0, options.findIndex((o) => o.value === value));
|
|
60
|
+
const [highlight, setHighlight] = useState(index);
|
|
61
|
+
|
|
62
|
+
useInput(
|
|
63
|
+
(event) => {
|
|
64
|
+
if (disabled) return false;
|
|
65
|
+
if (!open) {
|
|
66
|
+
if (event.name === 'enter' || event.name === 'space' || event.name === 'down') {
|
|
67
|
+
setOpen(true);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
switch (event.name) {
|
|
73
|
+
case 'up': setHighlight((highlight - 1 + options.length) % options.length); return true;
|
|
74
|
+
case 'down': setHighlight((highlight + 1) % options.length); return true;
|
|
75
|
+
case 'enter': {
|
|
76
|
+
const chosen = options[highlight];
|
|
77
|
+
if (chosen && !chosen.disabled) onChange?.(chosen.value);
|
|
78
|
+
setOpen(false);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
case 'escape': setOpen(false); return true;
|
|
82
|
+
default: return false;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{ focusId: focus.id },
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const selected = options.find((o) => o.value === value);
|
|
89
|
+
const start = Math.max(0, Math.min(highlight - Math.floor(visibleRows / 2), options.length - visibleRows));
|
|
90
|
+
const window = options.slice(start, start + visibleRows);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The options, wherever they are being drawn.
|
|
94
|
+
*
|
|
95
|
+
* One function rather than one per mode: three copies of a highlighted row
|
|
96
|
+
* is three chances for the modes to drift into looking like different
|
|
97
|
+
* controls, and the whole claim here is that they are one control shown in
|
|
98
|
+
* three places.
|
|
99
|
+
*/
|
|
100
|
+
function list(): RenderOutput {
|
|
101
|
+
return h('box', { direction: 'column' },
|
|
102
|
+
...window.map((option, i) => {
|
|
103
|
+
const active = start + i === highlight;
|
|
104
|
+
return h('box', {
|
|
105
|
+
key: option.value,
|
|
106
|
+
direction: 'row',
|
|
107
|
+
gap: 1,
|
|
108
|
+
// Ink, not a filled row.
|
|
109
|
+
//
|
|
110
|
+
// A background swatch is the heaviest mark available and it has
|
|
111
|
+
// to invert the text to stay readable, so the highlighted option
|
|
112
|
+
// is the one option whose colour tells you nothing about itself.
|
|
113
|
+
// Accent and an underline say "here" without repainting the row.
|
|
114
|
+
fg: option.disabled ? 'disabled' : active ? 'accent' : undefined,
|
|
115
|
+
onClick: () => {
|
|
116
|
+
if (option.disabled) return;
|
|
117
|
+
onChange?.(option.value);
|
|
118
|
+
setOpen(false);
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
h('text', { content: active ? theme.glyphs.chevronRight : ' ' }),
|
|
122
|
+
option.icon ? h('text', { content: option.icon }) : null,
|
|
123
|
+
h('text', { content: option.label, underline: active, bold: active }),
|
|
124
|
+
h('box', { flex: 1 }),
|
|
125
|
+
option.value === value ? h('text', { content: theme.glyphs.check }) : null,
|
|
126
|
+
);
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/*
|
|
131
|
+
* Opening a layer, and keeping it in step.
|
|
132
|
+
*
|
|
133
|
+
* Two effects rather than one, because they answer different questions. The
|
|
134
|
+
* first is "is there a layer" and runs when the mode or the open state
|
|
135
|
+
* changes; the second is "what is in it" and runs whenever the highlight
|
|
136
|
+
* moves. Reopening on every arrow key would work and would also mean a new
|
|
137
|
+
* layer entry per keystroke, with whatever a layer manager does on open -
|
|
138
|
+
* ordering, focus bookkeeping, the scrim - done four times to move a cursor
|
|
139
|
+
* four rows.
|
|
140
|
+
*
|
|
141
|
+
* The control keeps the keyboard either way, so `trapFocus` stays off: the
|
|
142
|
+
* layer is a surface to draw the list on, and moving focus into it would
|
|
143
|
+
* take the arrow keys away from the handler that answers them.
|
|
144
|
+
*/
|
|
145
|
+
const layerId = `select:${focus.id}`;
|
|
146
|
+
const floating = mode !== 'inline';
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!floating || !open) return undefined;
|
|
150
|
+
const handle = app.layers.open({
|
|
151
|
+
id: layerId,
|
|
152
|
+
layer: mode === 'modal' ? 'modal' : 'floating',
|
|
153
|
+
scrim: mode === 'modal',
|
|
154
|
+
// Escape is the control's, and closing it there disposes this. Handled
|
|
155
|
+
// in both places, the layer would close and the control would still
|
|
156
|
+
// think it was open.
|
|
157
|
+
dismissOnEscape: false,
|
|
158
|
+
dismissOnOutsideClick: true,
|
|
159
|
+
position: mode === 'modal'
|
|
160
|
+
? { kind: 'center' }
|
|
161
|
+
: { kind: 'anchor', targetId: focus.id, side: 'bottom', align: 'start' },
|
|
162
|
+
node: panel(),
|
|
163
|
+
onClose: () => setOpen(false),
|
|
164
|
+
});
|
|
165
|
+
return () => { handle.dispose(); };
|
|
166
|
+
}, [floating, open, mode]);
|
|
167
|
+
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (!floating || !open) return;
|
|
170
|
+
app.layers.update(layerId, { node: panel() });
|
|
171
|
+
}, [highlight, value, options.length]);
|
|
172
|
+
|
|
173
|
+
/** The list with something to sit in, which inline gets from the control. */
|
|
174
|
+
function panel(): ComponentNode {
|
|
175
|
+
return h('box', {
|
|
176
|
+
direction: 'column',
|
|
177
|
+
// As wide as the control it came out of, so the two line up.
|
|
178
|
+
//
|
|
179
|
+
// `useMeasure` reports the *content* rect, and this panel has a frame
|
|
180
|
+
// and a gutter of its own to pay for - the same two the control is
|
|
181
|
+
// already paying - so it is four cells narrower than the box it is
|
|
182
|
+
// supposed to sit under unless they are added back.
|
|
183
|
+
width: Math.max(20, measured.width + 4),
|
|
184
|
+
border: theme.border,
|
|
185
|
+
bg: 'overlay',
|
|
186
|
+
padding: [0, 1],
|
|
187
|
+
}, list()) as ComponentNode;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* One box, open or shut.
|
|
192
|
+
*
|
|
193
|
+
* The list used to be a second bordered box under the first, so opening the
|
|
194
|
+
* control drew two rules back to back and the options read as a separate
|
|
195
|
+
* thing that happened to be nearby. They are the control - the same border
|
|
196
|
+
* holds both, with a rule between them where the two borders used to be.
|
|
197
|
+
*/
|
|
198
|
+
return h('box', {
|
|
199
|
+
id: focus.id,
|
|
200
|
+
role: 'combobox',
|
|
201
|
+
label,
|
|
202
|
+
direction: 'column',
|
|
203
|
+
border: { style: theme.border, color: focus.focused ? 'focus' : 'border' },
|
|
204
|
+
padding: [0, 1],
|
|
205
|
+
...rest,
|
|
206
|
+
},
|
|
207
|
+
h('box', {
|
|
208
|
+
direction: 'row',
|
|
209
|
+
gap: 1,
|
|
210
|
+
onClick: () => { if (!disabled) setOpen(!open); },
|
|
211
|
+
},
|
|
212
|
+
label ? h('text', { content: label, fg: 'muted' }) : null,
|
|
213
|
+
h('text', {
|
|
214
|
+
content: selected?.label ?? empty,
|
|
215
|
+
fg: selected ? undefined : 'subtle',
|
|
216
|
+
flex: 1,
|
|
217
|
+
truncate: 'end',
|
|
218
|
+
}),
|
|
219
|
+
h('text', { content: open ? theme.glyphs.chevronUp : theme.glyphs.chevronDown, fg: 'muted' })),
|
|
220
|
+
|
|
221
|
+
// A borderless theme has no rule to draw, and a blank row there would be
|
|
222
|
+
// the gap this was meant to close.
|
|
223
|
+
mode === 'inline' && open && theme.border !== 'none'
|
|
224
|
+
? h('box', { height: 1, fill: theme.borderChars().top, fg: 'borderSubtle' })
|
|
225
|
+
: null,
|
|
226
|
+
|
|
227
|
+
mode === 'inline' && open ? list() : null,
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
|
|
3
|
+
export interface TextInputProps extends BoxProps {
|
|
4
|
+
value: string;
|
|
5
|
+
onChange?(value: string): void;
|
|
6
|
+
onSubmit?(value: string): void;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Keep the label as the field's name but do not draw it inside the field -
|
|
11
|
+
* for a form or a dialog that already shows it beside or above the input.
|
|
12
|
+
*/
|
|
13
|
+
hideLabel?: boolean;
|
|
14
|
+
/** Replace every character, for secrets. */
|
|
15
|
+
mask?: string;
|
|
16
|
+
/** Stop accepting input past this many characters. */
|
|
17
|
+
maxLength?: number;
|
|
18
|
+
autoFocus?: boolean;
|
|
19
|
+
/** Draw a search glyph before the field. */
|
|
20
|
+
search?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* A stable focus id, so a command can send the reader here by name.
|
|
23
|
+
*
|
|
24
|
+
* Without one a control's id is derived from its instance, which nothing
|
|
25
|
+
* outside the render can know - so "focus the filter" has nothing to name
|
|
26
|
+
* and the key that would do it cannot be written.
|
|
27
|
+
*/
|
|
28
|
+
focusId?: string;
|
|
29
|
+
/**
|
|
30
|
+
* The caret tried to leave the field.
|
|
31
|
+
*
|
|
32
|
+
* A single-line input answers `left` and `right` itself right up to the
|
|
33
|
+
* ends, so a caller that wants those keys past the ends cannot have them
|
|
34
|
+
* from a key handler - the field takes the key and reports nothing. This is
|
|
35
|
+
* how it reports: the palette drills into a command's choices on `right`,
|
|
36
|
+
* and the path picker goes up a folder on `left`. `TextArea` has had this
|
|
37
|
+
* from the start; the two controls disagreeing is what left both of those
|
|
38
|
+
* keys silently doing nothing.
|
|
39
|
+
*/
|
|
40
|
+
onEdge?(edge: 'start' | 'end'): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useFocus, useInput, useTheme } from '@textui/core';
|
|
3
|
+
|
|
4
|
+
export interface SliderProps extends BoxProps {
|
|
5
|
+
value: number;
|
|
6
|
+
min?: number;
|
|
7
|
+
max?: number;
|
|
8
|
+
step?: number;
|
|
9
|
+
label?: string;
|
|
10
|
+
onChange?(value: number): void;
|
|
11
|
+
/** Cells the track occupies. */
|
|
12
|
+
trackWidth?: number;
|
|
13
|
+
format?(value: number): string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Slider = defineComponent<SliderProps>('Slider', (props) => {
|
|
17
|
+
const theme = useTheme();
|
|
18
|
+
const {
|
|
19
|
+
value, min = 0, max = 100, step = 1, label, onChange,
|
|
20
|
+
trackWidth = 20, format, disabled, ...rest
|
|
21
|
+
} = props;
|
|
22
|
+
const focus = useFocus({ disabled });
|
|
23
|
+
|
|
24
|
+
const clamp = (v: number): number => Math.max(min, Math.min(max, v));
|
|
25
|
+
|
|
26
|
+
useInput(
|
|
27
|
+
(event) => {
|
|
28
|
+
if (disabled) return false;
|
|
29
|
+
const big = Math.max(step, Math.round((max - min) / 10));
|
|
30
|
+
switch (event.name) {
|
|
31
|
+
case 'left': onChange?.(clamp(value - step)); return true;
|
|
32
|
+
case 'right': onChange?.(clamp(value + step)); return true;
|
|
33
|
+
case 'pagedown': onChange?.(clamp(value - big)); return true;
|
|
34
|
+
case 'pageup': onChange?.(clamp(value + big)); return true;
|
|
35
|
+
case 'home': onChange?.(min); return true;
|
|
36
|
+
case 'end': onChange?.(max); return true;
|
|
37
|
+
default: return false;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{ focusId: focus.id },
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const ratio = max === min ? 0 : (clamp(value) - min) / (max - min);
|
|
44
|
+
const filled = Math.round(ratio * (trackWidth - 1));
|
|
45
|
+
|
|
46
|
+
// Track, thumb and remainder all come from the theme, so an ascii terminal
|
|
47
|
+
// gets `--o--` rather than a row of question marks.
|
|
48
|
+
const thumb = theme.glyphs.bulletFilled;
|
|
49
|
+
const done = theme.glyphs.progressFull;
|
|
50
|
+
const todo = theme.glyphs.progressEmpty;
|
|
51
|
+
const track = Array.from({ length: trackWidth }, (_, i) =>
|
|
52
|
+
i === filled ? thumb : i < filled ? done : todo,
|
|
53
|
+
).join('');
|
|
54
|
+
|
|
55
|
+
return h('box', { id: focus.id, role: 'slider', label, direction: 'row', gap: 1, ...rest },
|
|
56
|
+
label ? h('text', { content: label, fg: 'muted' }) : null,
|
|
57
|
+
h('text', {
|
|
58
|
+
content: track,
|
|
59
|
+
fg: disabled ? 'disabled' : focus.focused ? 'accent' : 'border',
|
|
60
|
+
}),
|
|
61
|
+
h('text', { content: format ? format(value) : String(value), fg: 'muted' }),
|
|
62
|
+
);
|
|
63
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useFocus, useInput } from '@textui/core';
|
|
3
|
+
|
|
4
|
+
export interface SwitchProps extends BoxProps {
|
|
5
|
+
label?: string;
|
|
6
|
+
value?: boolean;
|
|
7
|
+
onChange?(value: boolean): void;
|
|
8
|
+
/** Words either side, so the state reads without colour. */
|
|
9
|
+
labels?: [off: string, on: string];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const Switch = defineComponent<SwitchProps>('Switch', (props) => {
|
|
13
|
+
const { label, value = false, onChange, labels = ['off', 'on'], disabled, ...rest } = props;
|
|
14
|
+
const focus = useFocus({ disabled });
|
|
15
|
+
|
|
16
|
+
useInput(
|
|
17
|
+
(event) => {
|
|
18
|
+
if (disabled) return false;
|
|
19
|
+
if (event.name === 'space' || event.name === 'enter') { onChange?.(!value); return true; }
|
|
20
|
+
if (event.name === 'left') { onChange?.(false); return true; }
|
|
21
|
+
if (event.name === 'right') { onChange?.(true); return true; }
|
|
22
|
+
return false;
|
|
23
|
+
},
|
|
24
|
+
{ focusId: focus.id },
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return h('box', {
|
|
28
|
+
id: focus.id, role: 'switch', label, selected: value,
|
|
29
|
+
direction: 'row', gap: 1, bold: focus.focused,
|
|
30
|
+
onClick: () => { if (!disabled) onChange?.(!value); },
|
|
31
|
+
...rest,
|
|
32
|
+
},
|
|
33
|
+
label ? h('text', { content: label }) : null,
|
|
34
|
+
h('text', {
|
|
35
|
+
content: value ? `[${labels[1]}]` : `[${labels[0]}]`,
|
|
36
|
+
fg: disabled ? 'disabled' : value ? 'accent' : 'muted',
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
});
|