@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,415 @@
|
|
|
1
|
+
import { defineComponent, h, useEffect, useFocusScope, useInput, useRuntime, useState, useTheme, } from '@textui/core';
|
|
2
|
+
import { TextInput } from '../control/index.js';
|
|
3
|
+
import { Menu } from '../navigation/index.js';
|
|
4
|
+
import { hint } from './shared.js';
|
|
5
|
+
/**
|
|
6
|
+
* The command palette.
|
|
7
|
+
*
|
|
8
|
+
* It searches the command registry rather than a list someone maintained, so
|
|
9
|
+
* a command registered anywhere is reachable here the moment it exists - which
|
|
10
|
+
* is the payoff for insisting actions are commands. It also *runs* what it
|
|
11
|
+
* finds, so choosing "Open dialog" here and pressing the button that opens a
|
|
12
|
+
* dialog are the same act, reaching the same code.
|
|
13
|
+
*
|
|
14
|
+
* A command that declares an argument with `choices` gets a second level:
|
|
15
|
+
* choosing it lists the choices, and picking one runs the command with it.
|
|
16
|
+
* That is where sub-items come from - the command says what it needs and the
|
|
17
|
+
* palette asks, rather than every caller inventing its own submenu.
|
|
18
|
+
*/
|
|
19
|
+
export const CommandPalette = defineComponent('CommandPalette', (props) => {
|
|
20
|
+
const theme = useTheme();
|
|
21
|
+
const runtime = useRuntime();
|
|
22
|
+
const { commands, placeholder, onRun, onClose, execute = true, grouped = true, visibleRows = 8, width = 60, openAt, ...rest } = props;
|
|
23
|
+
const [query, setQuery] = useState('');
|
|
24
|
+
const [highlight, setHighlight] = useState(0);
|
|
25
|
+
/** The command being asked about, when the palette has drilled in. */
|
|
26
|
+
const [pending, setPending] = useState(null);
|
|
27
|
+
const [choices, setChoices] = useState([]);
|
|
28
|
+
/**
|
|
29
|
+
* Whether the answer to "what may I choose" is still on its way.
|
|
30
|
+
*
|
|
31
|
+
* An empty list is a real answer - a host advertises a harness and no models
|
|
32
|
+
* until somebody has signed into it - and it is not the same answer as "the
|
|
33
|
+
* request is in flight". Both draw an empty menu, so without this the panel
|
|
34
|
+
* that will never have anything in it and the one that is about to look
|
|
35
|
+
* identical, and neither of them says so.
|
|
36
|
+
*/
|
|
37
|
+
const [asking, setAsking] = useState(false);
|
|
38
|
+
/** Bumped after a row that stays, so a `commands` function is read again. */
|
|
39
|
+
const [, setRefresh] = useState(0);
|
|
40
|
+
// The search field asks for focus itself. See `Dialog`.
|
|
41
|
+
useFocusScope({ trap: true, restore: true });
|
|
42
|
+
const app = runtime.app();
|
|
43
|
+
const given = typeof commands === 'function' ? commands() : commands;
|
|
44
|
+
const all = given ?? app?.commands.list({ slot: 'palette', enabledOnly: true }) ?? [];
|
|
45
|
+
const matches = pending ? [] : filterCommands(all, query);
|
|
46
|
+
const offered = pending ? filterChoices(choices, query) : [];
|
|
47
|
+
const rows = pending ? offered.map((choice) => choice.value) : matches.map((c) => c.id);
|
|
48
|
+
const index = Math.max(0, Math.min(highlight, rows.length - 1));
|
|
49
|
+
const items = pending
|
|
50
|
+
? (offered.length > 0
|
|
51
|
+
? offered.map((choice) => ({
|
|
52
|
+
id: choice.value,
|
|
53
|
+
label: choice.label ?? choice.value,
|
|
54
|
+
...(choice.icon ? { icon: choice.icon } : {}),
|
|
55
|
+
// What it means, when the choice says; "default" otherwise, which is
|
|
56
|
+
// the only thing the palette itself knows about a value.
|
|
57
|
+
...(choice.description
|
|
58
|
+
? { description: choice.description }
|
|
59
|
+
: pending.arg.default === choice.value ? { description: 'default' } : {}),
|
|
60
|
+
}))
|
|
61
|
+
// One row, saying which kind of nothing this is. A disabled item rather
|
|
62
|
+
// than no items: the panel keeps its height, so the answer arriving does
|
|
63
|
+
// not move everything under it.
|
|
64
|
+
: [{
|
|
65
|
+
id: '',
|
|
66
|
+
label: asking
|
|
67
|
+
? `Asking${theme.glyphs.ellipsis}`
|
|
68
|
+
: (query === '' ? 'Nothing to choose' : 'No match'),
|
|
69
|
+
disabled: true,
|
|
70
|
+
}])
|
|
71
|
+
: matches.map((command, i) => ({
|
|
72
|
+
id: command.id,
|
|
73
|
+
label: command.title,
|
|
74
|
+
// `badge` when the row has state to report, the category otherwise.
|
|
75
|
+
// The icon stays put either way: it is what the row *is*.
|
|
76
|
+
description: command.badge ?? command.category,
|
|
77
|
+
icon: command.icon,
|
|
78
|
+
// A row may stand for a command registered under another id, and the
|
|
79
|
+
// key a person would press belongs to that one.
|
|
80
|
+
shortcut: command.shortcut ?? app?.keybindings.forCommand(command.id)[0],
|
|
81
|
+
// A chevron, from `Menu`, for anything that will ask a question.
|
|
82
|
+
children: argumentOf(command) ? [] : undefined,
|
|
83
|
+
separatorBefore: grouped && i > 0 && matches[i - 1].category !== command.category,
|
|
84
|
+
}));
|
|
85
|
+
const back = () => {
|
|
86
|
+
// `null` is "never mind": whatever the preview did gets undone by whoever
|
|
87
|
+
// did it, because only the command knows what it changed.
|
|
88
|
+
pending?.arg.preview?.(null);
|
|
89
|
+
setPending(null);
|
|
90
|
+
setChoices([]);
|
|
91
|
+
setQuery('');
|
|
92
|
+
setHighlight(0);
|
|
93
|
+
};
|
|
94
|
+
const finish = (id, args) => {
|
|
95
|
+
// A row that only flips something stays: closing after every switch means
|
|
96
|
+
// reopening the list to reach the next one, which is the wrong answer for
|
|
97
|
+
// a list of switches and the right one for a list of actions. The command
|
|
98
|
+
// says which it is.
|
|
99
|
+
const definition = given?.find((c) => c.id === id) ?? app?.commands.get(id);
|
|
100
|
+
const dismiss = definition?.keepOpen !== true;
|
|
101
|
+
// Close first: the command may open a layer of its own, and the palette
|
|
102
|
+
// should be gone by the time it does rather than sitting underneath it.
|
|
103
|
+
if (dismiss)
|
|
104
|
+
onClose?.();
|
|
105
|
+
if (execute) {
|
|
106
|
+
// A caller may hand the palette rows the registry has never seen - a
|
|
107
|
+
// list built for one screen, a switch per surface the running shell
|
|
108
|
+
// happens to have. Running those through `execute` would find nothing
|
|
109
|
+
// and silently do nothing, so a definition that came in through `commands`
|
|
110
|
+
// and is not registered is run directly.
|
|
111
|
+
const local = given?.find((c) => c.id === id);
|
|
112
|
+
if (local && !app?.commands.get(id)) {
|
|
113
|
+
void local.run?.(args ?? {}, {
|
|
114
|
+
app: app,
|
|
115
|
+
store: runtime.store,
|
|
116
|
+
args: args ?? {},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
void app?.execute(id, args, 'palette');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// A row that stayed just changed something the list is describing, so the
|
|
124
|
+
// list has to be asked again. Nothing else will invalidate this component:
|
|
125
|
+
// it is not subscribed to whatever the row touched, and should not be.
|
|
126
|
+
if (!dismiss)
|
|
127
|
+
setRefresh((n) => n + 1);
|
|
128
|
+
onRun?.(id, args);
|
|
129
|
+
};
|
|
130
|
+
/** Ask about one command. `choices` may be a function, and may be async. */
|
|
131
|
+
const drillInto = (command, arg, collected = {}) => {
|
|
132
|
+
const resolved = typeof arg.choices === 'function' ? arg.choices() : arg.choices ?? [];
|
|
133
|
+
setPending({ command, arg, collected });
|
|
134
|
+
setQuery('');
|
|
135
|
+
setHighlight(0);
|
|
136
|
+
if (Array.isArray(resolved)) {
|
|
137
|
+
setAsking(false);
|
|
138
|
+
setChoices(resolved.map(asChoice));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
setChoices([]);
|
|
142
|
+
setAsking(true);
|
|
143
|
+
// Answered either way: a `choices` function that rejects leaves the panel
|
|
144
|
+
// saying "nothing to choose", which is true of what it can offer.
|
|
145
|
+
void resolved
|
|
146
|
+
.then((list) => setChoices(list.map(asChoice)))
|
|
147
|
+
.catch(() => setChoices([]))
|
|
148
|
+
.finally(() => setAsking(false));
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Answer one argument, then ask about the next one or run it.
|
|
152
|
+
*
|
|
153
|
+
* A command with two things to ask about used to have the first one
|
|
154
|
+
* collected and the second one skipped, which meant running it with an
|
|
155
|
+
* argument it said was required - and `execute` refuses that, loudly.
|
|
156
|
+
*/
|
|
157
|
+
const answer = (value) => {
|
|
158
|
+
if (!pending)
|
|
159
|
+
return;
|
|
160
|
+
const collected = { ...pending.collected, [pending.arg.name]: value };
|
|
161
|
+
const next = argumentOf(pending.command, collected);
|
|
162
|
+
if (next) {
|
|
163
|
+
drillInto(pending.command, next, collected);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
finish(pending.command.id, collected);
|
|
167
|
+
};
|
|
168
|
+
const choose = () => {
|
|
169
|
+
if (pending) {
|
|
170
|
+
// An argument with choices is answered by picking one; an argument
|
|
171
|
+
// without any is answered by typing it. The palette already has a field
|
|
172
|
+
// and a list, so it is the same overlay either way - and without this a
|
|
173
|
+
// command that asks for a name could be *opened* and never *answered*.
|
|
174
|
+
if (pending.arg.choices === undefined) {
|
|
175
|
+
const typed = query.trim();
|
|
176
|
+
// Nothing typed is not an answer. Running anyway hands the command an
|
|
177
|
+
// empty required argument, which is the error it exists to prevent.
|
|
178
|
+
if (typed === '')
|
|
179
|
+
return;
|
|
180
|
+
answer(typed);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const value = rows[index];
|
|
184
|
+
if (value === undefined)
|
|
185
|
+
return;
|
|
186
|
+
answer(value);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const command = matches[index];
|
|
190
|
+
if (!command)
|
|
191
|
+
return;
|
|
192
|
+
const arg = argumentOf(command);
|
|
193
|
+
if (!arg) {
|
|
194
|
+
finish(command.id);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
drillInto(command, arg);
|
|
198
|
+
};
|
|
199
|
+
// Opening at a command is the same act as choosing it, minus the choosing.
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (!openAt)
|
|
202
|
+
return;
|
|
203
|
+
const command = all.find((c) => c.id === openAt) ?? app?.commands.get(openAt);
|
|
204
|
+
const arg = command ? argumentOf(command) : undefined;
|
|
205
|
+
if (command && arg)
|
|
206
|
+
drillInto(command, arg);
|
|
207
|
+
}, [openAt]);
|
|
208
|
+
// The field owns typing; the list owns up, down and enter. Without this
|
|
209
|
+
// split, Enter submits the search box and the highlighted command is
|
|
210
|
+
// never the thing that runs.
|
|
211
|
+
useInput((event) => {
|
|
212
|
+
if (event.name === 'escape') {
|
|
213
|
+
// Opened at a command, there is nothing behind the question that
|
|
214
|
+
// anybody chose to be at: the caller drilled in on their behalf, and
|
|
215
|
+
// the level underneath is a list of one. Backing out to it reads as
|
|
216
|
+
// "escape did nothing", and the second escape - the one that would
|
|
217
|
+
// close it - is spent leaving a screen nobody asked to see.
|
|
218
|
+
if (pending && !openAt) {
|
|
219
|
+
back();
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
// Whatever a highlighted choice previewed has to be put back on the
|
|
223
|
+
// way out, and `null` is how the command is told to undo it - only it
|
|
224
|
+
// knows what it changed. `back()` did that, so closing instead of
|
|
225
|
+
// backing has to do it too, or escaping a theme picker leaves the
|
|
226
|
+
// theme it was merely showing you.
|
|
227
|
+
pending?.arg.preview?.(null);
|
|
228
|
+
onClose?.();
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
if (event.name === 'left' && pending && query === '' && !openAt) {
|
|
232
|
+
back();
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
// Wrapping, both ways. A list you can walk off the end of makes you
|
|
236
|
+
// check where you are before every press; one that comes round means the
|
|
237
|
+
// last item is one key from the first.
|
|
238
|
+
const wrap = (n) => rows.length === 0 ? 0 : (n + rows.length) % rows.length;
|
|
239
|
+
const move = (to) => {
|
|
240
|
+
setHighlight(to);
|
|
241
|
+
// Show what the choice would do while it is merely highlighted. The
|
|
242
|
+
// command put the `preview` there; the palette only reports movement.
|
|
243
|
+
if (pending)
|
|
244
|
+
pending.arg.preview?.(rows[to] ?? null);
|
|
245
|
+
return true;
|
|
246
|
+
};
|
|
247
|
+
if (event.name === 'up')
|
|
248
|
+
return move(wrap(index - 1));
|
|
249
|
+
if (event.name === 'down')
|
|
250
|
+
return move(wrap(index + 1));
|
|
251
|
+
return false;
|
|
252
|
+
}, { global: true });
|
|
253
|
+
const highlighted = pending ? undefined : matches[index];
|
|
254
|
+
const move = `${theme.glyphs.arrowUp}${theme.glyphs.arrowDown} move`;
|
|
255
|
+
// What the highlighted row is, in full.
|
|
256
|
+
//
|
|
257
|
+
// The rows themselves have to fit, so a choice's own sentence is truncated
|
|
258
|
+
// there - and a sentence about what a mode does is exactly the thing a
|
|
259
|
+
// person needs whole. So the line under the list follows the highlight: the
|
|
260
|
+
// choice's description while there is one, the question's otherwise.
|
|
261
|
+
const chosen = pending ? offered[index] : undefined;
|
|
262
|
+
const detail = pending
|
|
263
|
+
? chosen?.description
|
|
264
|
+
?? pending.arg.description ?? `${pending.command.title} needs a ${pending.arg.name}`
|
|
265
|
+
: highlighted?.description ?? highlighted?.id ?? '';
|
|
266
|
+
return h('box', {
|
|
267
|
+
role: 'dialog',
|
|
268
|
+
label: 'Commands',
|
|
269
|
+
border: theme.border,
|
|
270
|
+
bg: 'overlay',
|
|
271
|
+
width,
|
|
272
|
+
direction: 'column',
|
|
273
|
+
// A border is a gutter as well as a line. Without one - `paper` sets
|
|
274
|
+
// `border: 'none'` - the rows run flush to the panel edge and the last
|
|
275
|
+
// character sits against whatever is behind it.
|
|
276
|
+
...(theme.border === 'none' ? { padding: { left: 1, right: 1 } } : {}),
|
|
277
|
+
// The crumb names the command, not its argument. "commands › Theme" is
|
|
278
|
+
// where you are; "commands › id" is what the parameter happens to be
|
|
279
|
+
// called, which is the author's business rather than the reader's.
|
|
280
|
+
title: pending
|
|
281
|
+
? ` commands ${theme.glyphs.breadcrumb} ${pending.command.title} `
|
|
282
|
+
: ' commands ',
|
|
283
|
+
...rest,
|
|
284
|
+
}, h(TextInput, {
|
|
285
|
+
value: query,
|
|
286
|
+
onChange: (next) => {
|
|
287
|
+
setQuery(next);
|
|
288
|
+
setHighlight(0);
|
|
289
|
+
},
|
|
290
|
+
onSubmit: choose,
|
|
291
|
+
/*
|
|
292
|
+
* `right` drills into a command's choices, which the hint row has been
|
|
293
|
+
* advertising as `> sub-items` all along.
|
|
294
|
+
*
|
|
295
|
+
* It used to be a case in the handler above, and that handler is global
|
|
296
|
+
* - so it ran only if the focused field declined the key first, and a
|
|
297
|
+
* single-line field never declines `right`. The key did nothing and the
|
|
298
|
+
* footer said it did something. `onEdge` is how a focused field hands
|
|
299
|
+
* a key back.
|
|
300
|
+
*/
|
|
301
|
+
onEdge: (edge) => {
|
|
302
|
+
if (edge !== 'end' || pending)
|
|
303
|
+
return;
|
|
304
|
+
const command = matches[index];
|
|
305
|
+
if (command && argumentOf(command))
|
|
306
|
+
choose();
|
|
307
|
+
},
|
|
308
|
+
placeholder: pending
|
|
309
|
+
? (pending.arg.description ?? (pending.arg.choices === undefined
|
|
310
|
+
? `${pending.command.title}${theme.glyphs.ellipsis}`
|
|
311
|
+
: `Choose ${pending.command.title.toLowerCase()}${theme.glyphs.ellipsis}`))
|
|
312
|
+
: (placeholder ?? `Type a command${theme.glyphs.ellipsis}`),
|
|
313
|
+
search: true,
|
|
314
|
+
autoFocus: true,
|
|
315
|
+
border: 'none',
|
|
316
|
+
}), h('box', { height: 1, fill: theme.borderChars().top, fg: 'borderSubtle' }), h(Menu, {
|
|
317
|
+
items,
|
|
318
|
+
visibleRows,
|
|
319
|
+
interactive: false,
|
|
320
|
+
activeId: rows[index],
|
|
321
|
+
onSelect: (id) => {
|
|
322
|
+
const at = rows.indexOf(id);
|
|
323
|
+
if (at >= 0)
|
|
324
|
+
setHighlight(at);
|
|
325
|
+
choose();
|
|
326
|
+
},
|
|
327
|
+
}), h('box', { height: 1, fill: theme.borderChars().top, fg: 'borderSubtle' }),
|
|
328
|
+
// What the highlighted row actually is, and how to move around. A palette
|
|
329
|
+
// that shows only titles makes you run something to find out what it does.
|
|
330
|
+
h('box', { direction: 'row', gap: 1 }, h('text', { content: detail, fg: 'muted', flex: 1, truncate: 'end' }), h('text', { content: `${rows.length}`, fg: 'subtle' })), h('box', { direction: 'row', gap: 1 }, h('text', {
|
|
331
|
+
content: pending
|
|
332
|
+
? (pending.arg.choices === undefined
|
|
333
|
+
? hint(theme, ['type it', 'enter confirm', 'esc back'])
|
|
334
|
+
// Nothing to choose is nothing to press enter on, and offering it
|
|
335
|
+
// is how a panel reads as broken rather than as empty.
|
|
336
|
+
: hint(theme, rows.length > 0 ? [move, 'enter choose', 'esc back'] : ['esc back']))
|
|
337
|
+
: hint(theme, [move, 'enter run', `${theme.glyphs.chevronRight} sub-items`, 'esc close']),
|
|
338
|
+
fg: 'subtle',
|
|
339
|
+
truncate: 'end',
|
|
340
|
+
})));
|
|
341
|
+
});
|
|
342
|
+
/**
|
|
343
|
+
* The argument the palette should ask about, if there is one.
|
|
344
|
+
*
|
|
345
|
+
* A fixed set of choices, or something required that nothing has supplied.
|
|
346
|
+
* Only the first kind used to count, which meant a command declaring "I need a
|
|
347
|
+
* title" ran with no title and did nothing - the one failure mode that looks
|
|
348
|
+
* exactly like a broken key.
|
|
349
|
+
*
|
|
350
|
+
* Exported because "will this command ask something?" is a question other
|
|
351
|
+
* chrome has to answer too - a menu row wants a chevron and wants to hand off
|
|
352
|
+
* rather than run - and the menu bar had written its own version that
|
|
353
|
+
* *called* the `choices` function to find out. A function is allowed to be
|
|
354
|
+
* async, so that answer was sometimes a promise, and the copy went on to call
|
|
355
|
+
* `.map` on it. Whether a command will ask is a property of its declaration;
|
|
356
|
+
* nothing has to be resolved to know it.
|
|
357
|
+
*/
|
|
358
|
+
export function argumentOf(command, collected = {}) {
|
|
359
|
+
return (command.args ?? []).find((arg) => collected[arg.name] === undefined &&
|
|
360
|
+
(arg.choices !== undefined || (arg.required === true && arg.default === undefined)));
|
|
361
|
+
}
|
|
362
|
+
/** The short form and the long one, as one shape. */
|
|
363
|
+
function asChoice(choice) {
|
|
364
|
+
return typeof choice === 'string' ? { value: choice } : choice;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Filter on everything a person can see.
|
|
368
|
+
*
|
|
369
|
+
* The label, because that is what they are reading; the value, because a host
|
|
370
|
+
* id is often what somebody types from memory; and the description, because
|
|
371
|
+
* "asks before editing" is how you find a mode whose label is "Default".
|
|
372
|
+
*/
|
|
373
|
+
function filterChoices(choices, query) {
|
|
374
|
+
const q = query.trim().toLowerCase();
|
|
375
|
+
if (q === '')
|
|
376
|
+
return choices;
|
|
377
|
+
return choices.filter((choice) => `${choice.label ?? ''} ${choice.value} ${choice.description ?? ''}`
|
|
378
|
+
.toLowerCase().includes(q));
|
|
379
|
+
}
|
|
380
|
+
export function filterCommands(commands, query) {
|
|
381
|
+
const q = query.trim().toLowerCase();
|
|
382
|
+
if (q === '')
|
|
383
|
+
return commands;
|
|
384
|
+
const scored = [];
|
|
385
|
+
for (const command of commands) {
|
|
386
|
+
const haystacks = [command.title, command.id, ...(command.keywords ?? [])];
|
|
387
|
+
let best = -1;
|
|
388
|
+
for (const haystack of haystacks) {
|
|
389
|
+
const score = subsequenceScore(haystack.toLowerCase(), q);
|
|
390
|
+
if (score > best)
|
|
391
|
+
best = score;
|
|
392
|
+
}
|
|
393
|
+
if (best >= 0)
|
|
394
|
+
scored.push({ command, score: best });
|
|
395
|
+
}
|
|
396
|
+
return scored.sort((a, b) => b.score - a.score).map((s) => s.command);
|
|
397
|
+
}
|
|
398
|
+
function subsequenceScore(haystack, needle) {
|
|
399
|
+
if (haystack.startsWith(needle))
|
|
400
|
+
return 1000 - haystack.length;
|
|
401
|
+
if (haystack.includes(needle))
|
|
402
|
+
return 500 - haystack.indexOf(needle);
|
|
403
|
+
let hi = 0;
|
|
404
|
+
let score = 0;
|
|
405
|
+
let streak = 0;
|
|
406
|
+
for (const char of needle) {
|
|
407
|
+
const found = haystack.indexOf(char, hi);
|
|
408
|
+
if (found === -1)
|
|
409
|
+
return -1;
|
|
410
|
+
streak = found === hi ? streak + 1 : 0;
|
|
411
|
+
score += 10 + streak * 5 - (found - hi);
|
|
412
|
+
hi = found + 1;
|
|
413
|
+
}
|
|
414
|
+
return score;
|
|
415
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BoxProps, SemanticVariant } from '@textui/core';
|
|
2
|
+
export interface DialogProps extends BoxProps {
|
|
3
|
+
title?: string;
|
|
4
|
+
/** Buttons along the bottom. The first is the default action. */
|
|
5
|
+
actions?: {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
tone?: SemanticVariant;
|
|
9
|
+
onPress?(): void;
|
|
10
|
+
}[];
|
|
11
|
+
onClose?(): void;
|
|
12
|
+
/** Cells wide. The dialog centres itself in whatever is left. */
|
|
13
|
+
width?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare const Dialog: (props: DialogProps) => import("@textui/core").RenderOutput;
|
|
16
|
+
//# sourceMappingURL=dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../src/overlay/dialog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAI9D,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,eAAe,CAAC;QAAC,OAAO,CAAC,IAAI,IAAI,CAAA;KAAE,EAAE,CAAC;IACpF,OAAO,CAAC,IAAI,IAAI,CAAC;IACjB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,MAAM,6DAmDjB,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { defineComponent, h, useFocusScope, useInput, useTheme } from '@textui/core';
|
|
2
|
+
import { Button } from '../control/index.js';
|
|
3
|
+
export const Dialog = defineComponent('Dialog', (props) => {
|
|
4
|
+
const theme = useTheme();
|
|
5
|
+
const { title, actions = [], onClose, children, width = 50, ...rest } = props;
|
|
6
|
+
// No `autoFocus` on the scope: a dialog's own controls say which of them
|
|
7
|
+
// wants focus - the first action, or the field in a prompt - and the scope
|
|
8
|
+
// taking it first would hand it to whichever box happened to register
|
|
9
|
+
// earliest. The flag never fired before, because a scope is empty when it
|
|
10
|
+
// is activated; now that it works, it has to mean what it says.
|
|
11
|
+
useFocusScope({ trap: true, restore: true });
|
|
12
|
+
// Only consume escape when there is something to do with it. Consuming it
|
|
13
|
+
// regardless would stop the layer manager dismissing a dialog that was
|
|
14
|
+
// opened without an `onClose` - which is most of them.
|
|
15
|
+
useInput((event) => {
|
|
16
|
+
if (event.name !== 'escape' || !onClose)
|
|
17
|
+
return false;
|
|
18
|
+
onClose();
|
|
19
|
+
return true;
|
|
20
|
+
}, { global: true });
|
|
21
|
+
return h('box', {
|
|
22
|
+
role: 'dialog',
|
|
23
|
+
label: title,
|
|
24
|
+
border: theme.border,
|
|
25
|
+
bg: 'overlay',
|
|
26
|
+
padding: [0, 1],
|
|
27
|
+
width,
|
|
28
|
+
direction: 'column',
|
|
29
|
+
title,
|
|
30
|
+
...rest,
|
|
31
|
+
}, h('box', { direction: 'column', flex: 1 }, children), actions.length > 0
|
|
32
|
+
? h('box', { direction: 'row', gap: 1, justify: 'end' }, ...actions.map((action, i) => h(Button, {
|
|
33
|
+
key: action.id,
|
|
34
|
+
label: action.label,
|
|
35
|
+
tone: action.tone,
|
|
36
|
+
// Every action is a line at rest; the focused one fills. Making
|
|
37
|
+
// the default action solid as well meant two filled buttons the
|
|
38
|
+
// moment focus moved, and no way to tell which one Enter meant.
|
|
39
|
+
variant: 'outline',
|
|
40
|
+
autoFocus: i === 0,
|
|
41
|
+
onPress: action.onPress,
|
|
42
|
+
})))
|
|
43
|
+
: null);
|
|
44
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ComponentDefinition, Disposable, LayerEntry, LayerPosition, SemanticVariant, TextUIApp } from '@textui/core';
|
|
2
|
+
import type { ToastHostProps } from './toast-host.js';
|
|
3
|
+
/**
|
|
4
|
+
* Overlays.
|
|
5
|
+
*
|
|
6
|
+
* Every one of these is a layer entry, not a component that draws itself over
|
|
7
|
+
* its neighbours - so focus trapping, dismissal and paint order are decided
|
|
8
|
+
* once by the layer manager rather than five times, slightly differently.
|
|
9
|
+
*/
|
|
10
|
+
export * from './command-palette.js';
|
|
11
|
+
export * from './dialog.js';
|
|
12
|
+
export * from './layer-scope.js';
|
|
13
|
+
export * from './path-picker.js';
|
|
14
|
+
export * from './prompt-dialog.js';
|
|
15
|
+
export * from './toast.js';
|
|
16
|
+
export * from './toast-host.js';
|
|
17
|
+
export * from './tooltip.js';
|
|
18
|
+
export interface ConfirmOptions {
|
|
19
|
+
title?: string;
|
|
20
|
+
message: string;
|
|
21
|
+
confirmLabel?: string;
|
|
22
|
+
cancelLabel?: string;
|
|
23
|
+
tone?: SemanticVariant;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The composition rule the brief asks for: a dialog can be assembled by hand
|
|
27
|
+
* out of public components, and the common case gets a one-liner that returns
|
|
28
|
+
* a promise. Both go through the same layer manager.
|
|
29
|
+
*/
|
|
30
|
+
export declare function confirm(layers: {
|
|
31
|
+
open(entry: LayerEntry): {
|
|
32
|
+
dispose(): void;
|
|
33
|
+
};
|
|
34
|
+
}, options: ConfirmOptions): Promise<boolean>;
|
|
35
|
+
export interface PromptOptions {
|
|
36
|
+
title?: string;
|
|
37
|
+
message?: string;
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
initialValue?: string;
|
|
40
|
+
mask?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare function prompt(layers: {
|
|
43
|
+
open(entry: LayerEntry): {
|
|
44
|
+
dispose(): void;
|
|
45
|
+
};
|
|
46
|
+
}, options: PromptOptions): Promise<string | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Picking a path by looking at it.
|
|
49
|
+
*
|
|
50
|
+
* The third question, beside `confirm` and `prompt`, and it exists because the
|
|
51
|
+
* other two cannot ask it. A path typed blind into a text field is the worst
|
|
52
|
+
* way to give one: you have to already know the answer, which is the opposite
|
|
53
|
+
* of what picking is for.
|
|
54
|
+
*
|
|
55
|
+
* It reads the **resource registry**, never the filesystem. `search` made that
|
|
56
|
+
* choice already and it is why search works on whatever is mounted; a picker
|
|
57
|
+
* that reached for `node:fs` would work on `file:` and nothing else, and the
|
|
58
|
+
* first thing anyone wants to pick off a remote is a file.
|
|
59
|
+
*/
|
|
60
|
+
export interface PickOptions {
|
|
61
|
+
/** Where to start looking. A directory URI. */
|
|
62
|
+
start: string;
|
|
63
|
+
/** What counts as an answer. Directories are walked through either way. */
|
|
64
|
+
wants?: 'file' | 'directory';
|
|
65
|
+
title?: string;
|
|
66
|
+
placeholder?: string;
|
|
67
|
+
visibleRows?: number;
|
|
68
|
+
width?: number;
|
|
69
|
+
}
|
|
70
|
+
export declare function pick(app: TextUIApp, options: PickOptions): Promise<string | null>;
|
|
71
|
+
export interface NotifyOptions {
|
|
72
|
+
message: string;
|
|
73
|
+
tone?: SemanticVariant;
|
|
74
|
+
title?: string;
|
|
75
|
+
icon?: string;
|
|
76
|
+
/** Milliseconds on screen. 0 keeps it until it is closed. */
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
/** Reuse an id to replace a standing toast rather than stack another. */
|
|
79
|
+
id?: string;
|
|
80
|
+
}
|
|
81
|
+
export declare function notify(app: TextUIApp, options: NotifyOptions): Disposable;
|
|
82
|
+
export declare function toastPosition(anchor: ToastHostProps['anchor']): LayerPosition;
|
|
83
|
+
export declare const OVERLAY_COMPONENTS: ComponentDefinition[];
|
|
84
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/overlay/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,UAAU,EACV,UAAU,EACV,aAAa,EACb,eAAe,EACf,SAAS,EACV,MAAM,cAAc,CAAC;AAMtB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAKtD;;;;;;GAMG;AACH,cAAc,sBAAsB,CAAC;AACrC,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAE7B,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE;IAAE,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG;QAAE,OAAO,IAAI,IAAI,CAAA;KAAE,CAAA;CAAE,EACxD,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,OAAO,CAAC,CAwClB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,MAAM,CACpB,MAAM,EAAE;IAAE,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG;QAAE,OAAO,IAAI,IAAI,CAAA;KAAE,CAAA;CAAE,EACxD,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6BxB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA8BjF;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAID,wBAAgB,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,GAAG,UAAU,CAQzE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,aAAa,CAO7E;AAED,eAAO,MAAM,kBAAkB,EAAE,mBAAmB,EASnD,CAAC"}
|