@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
package/dist/find.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useStoreValue } from '@textui/core';
|
|
2
|
+
/**
|
|
3
|
+
* Finding things in text.
|
|
4
|
+
*
|
|
5
|
+
* The query lives in the store rather than in whatever component is showing
|
|
6
|
+
* the text, for the usual reason: a search box, a status bar counting matches
|
|
7
|
+
* and the viewer painting them are three things that have to agree, and the
|
|
8
|
+
* only way three things agree is by reading one answer.
|
|
9
|
+
*
|
|
10
|
+
* The matching itself is here and not in the editor because a viewer wants it
|
|
11
|
+
* too, and so will a results panel: given a string and a query, which spans
|
|
12
|
+
* matched. Nothing here knows what a caret is.
|
|
13
|
+
*/
|
|
14
|
+
export const FIND_ROOT = '$/ui/find';
|
|
15
|
+
export const FIND_QUERY = `${FIND_ROOT}/query`;
|
|
16
|
+
export const FIND_CASE = `${FIND_ROOT}/matchCase`;
|
|
17
|
+
/** Bumped to ask whoever is showing the text to move to the next match. */
|
|
18
|
+
export const FIND_STEP = `${FIND_ROOT}/step`;
|
|
19
|
+
/**
|
|
20
|
+
* Every match in a document, in reading order.
|
|
21
|
+
*
|
|
22
|
+
* Overlapping matches are not a thing: a search steps past what it found, so
|
|
23
|
+
* `aa` in `aaaa` is two matches and not three. That is what every editor does
|
|
24
|
+
* and what "next" means when you press it twice.
|
|
25
|
+
*/
|
|
26
|
+
export function findMatches(text, query) {
|
|
27
|
+
const needle = query.matchCase === true ? query.text : query.text.toLowerCase();
|
|
28
|
+
if (needle === '')
|
|
29
|
+
return [];
|
|
30
|
+
const out = [];
|
|
31
|
+
const lines = text.split('\n');
|
|
32
|
+
for (let line = 0; line < lines.length; line++) {
|
|
33
|
+
const raw = lines[line];
|
|
34
|
+
const haystack = query.matchCase === true ? raw : raw.toLowerCase();
|
|
35
|
+
let at = haystack.indexOf(needle);
|
|
36
|
+
while (at !== -1) {
|
|
37
|
+
out.push({ line, start: at, end: at + needle.length });
|
|
38
|
+
at = haystack.indexOf(needle, at + needle.length);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The match a caret should go to next, wrapping at the end.
|
|
45
|
+
*
|
|
46
|
+
* From *after* the caret, not from the caret: pressing next while sitting on a
|
|
47
|
+
* match has to move, or "next" is a key that does nothing. Backwards is the
|
|
48
|
+
* mirror, and both wrap, because a search that stops at the end of the file is
|
|
49
|
+
* a search you have to restart by hand.
|
|
50
|
+
*/
|
|
51
|
+
export function stepMatch(matches, from, direction) {
|
|
52
|
+
if (matches.length === 0)
|
|
53
|
+
return -1;
|
|
54
|
+
if (direction === 1) {
|
|
55
|
+
const found = matches.findIndex((m) => m.line > from.line || (m.line === from.line && m.start > from.column));
|
|
56
|
+
return found === -1 ? 0 : found;
|
|
57
|
+
}
|
|
58
|
+
/*
|
|
59
|
+
* Backwards, from *before* the caret - and the caret is usually sitting at
|
|
60
|
+
* the end of the match it just stepped to, so "before" has to mean the match
|
|
61
|
+
* *ends* before it. Comparing starts instead finds the match the caret is
|
|
62
|
+
* standing in and calls that the previous one, which is a key that does
|
|
63
|
+
* nothing every other press.
|
|
64
|
+
*/
|
|
65
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
66
|
+
const m = matches[i];
|
|
67
|
+
if (m.line < from.line || (m.line === from.line && m.end < from.column))
|
|
68
|
+
return i;
|
|
69
|
+
}
|
|
70
|
+
return matches.length - 1;
|
|
71
|
+
}
|
|
72
|
+
/** Which match the caret is sitting inside, or -1. */
|
|
73
|
+
export function matchAt(matches, at) {
|
|
74
|
+
return matches.findIndex((m) => m.line === at.line && at.column >= m.start && at.column <= m.end);
|
|
75
|
+
}
|
|
76
|
+
export function readQuery(store) {
|
|
77
|
+
return {
|
|
78
|
+
text: store.get(FIND_QUERY) ?? '',
|
|
79
|
+
matchCase: store.get(FIND_CASE) === true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function setQuery(store, text) {
|
|
83
|
+
store.set(FIND_QUERY, text);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Ask whoever is showing the text to move.
|
|
87
|
+
*
|
|
88
|
+
* A counter rather than a command, so the thing that moves is the thing that
|
|
89
|
+
* knows where the caret is - and so two panels showing the same file each move
|
|
90
|
+
* their own.
|
|
91
|
+
*/
|
|
92
|
+
export function stepFind(store, direction) {
|
|
93
|
+
const at = store.get(FIND_STEP);
|
|
94
|
+
store.set(FIND_STEP, { n: (at?.n ?? 0) + 1, direction });
|
|
95
|
+
}
|
|
96
|
+
/** The current query, and the ask to move, for a component showing text. */
|
|
97
|
+
export function useFind() {
|
|
98
|
+
const text = useStoreValue(FIND_QUERY, '') ?? '';
|
|
99
|
+
const matchCase = useStoreValue(FIND_CASE, false) === true;
|
|
100
|
+
const step = useStoreValue(FIND_STEP);
|
|
101
|
+
return { query: { text, matchCase }, step };
|
|
102
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
export interface DangerZoneProps extends BoxProps {
|
|
3
|
+
title?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
actionLabel: string;
|
|
6
|
+
onAction?(): void;
|
|
7
|
+
/** Require typing this exact text before the action is enabled. */
|
|
8
|
+
confirmText?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const DangerZone: (props: DangerZoneProps) => import("@textui/core").RenderOutput;
|
|
11
|
+
//# sourceMappingURL=danger-zone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"danger-zone.d.ts","sourceRoot":"","sources":["../../src/form/danger-zone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAI7C,MAAM,WAAW,eAAgB,SAAQ,QAAQ;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,IAAI,CAAC;IAClB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,UAAU,iEAoBrB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineComponent, h, useTheme } from '@textui/core';
|
|
2
|
+
import { Button } from '../control/index.js';
|
|
3
|
+
export const DangerZone = defineComponent('DangerZone', (props) => {
|
|
4
|
+
const theme = useTheme();
|
|
5
|
+
const { title = 'Danger zone', description, actionLabel, onAction, confirmText, ...rest } = props;
|
|
6
|
+
return h('box', {
|
|
7
|
+
border: { style: theme.border, color: 'danger' },
|
|
8
|
+
padding: [0, 1],
|
|
9
|
+
direction: 'column',
|
|
10
|
+
title: ` ${title} `,
|
|
11
|
+
...rest,
|
|
12
|
+
}, description ? h('text', { content: description, fg: 'muted', wrap: 'word' }) : null, confirmText
|
|
13
|
+
? h('text', { content: `Type "${confirmText}" to confirm.`, fg: 'subtle' })
|
|
14
|
+
: null, h('box', { direction: 'row', justify: 'end' }, h(Button, { label: actionLabel, tone: 'danger', variant: 'outline', onPress: onAction })));
|
|
15
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
export interface FieldProps extends BoxProps {
|
|
3
|
+
name: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
hint?: string;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
/** Cells reserved for the label, so a column of fields lines up. */
|
|
8
|
+
labelWidth?: number;
|
|
9
|
+
/** Label above the control rather than beside it. */
|
|
10
|
+
stacked?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A labelled control with its error.
|
|
14
|
+
*
|
|
15
|
+
* The error line is always reserved when one could appear, so a form does not
|
|
16
|
+
* jump a row every time validation fires.
|
|
17
|
+
*/
|
|
18
|
+
export declare const Field: (props: FieldProps) => import("@textui/core").RenderOutput;
|
|
19
|
+
//# sourceMappingURL=field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/form/field.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAI7C,MAAM,WAAW,UAAW,SAAQ,QAAQ;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,eAAO,MAAM,KAAK,4DAuChB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineComponent, h, useTheme } from '@textui/core';
|
|
2
|
+
import { useFormContext } from './shared.js';
|
|
3
|
+
/**
|
|
4
|
+
* A labelled control with its error.
|
|
5
|
+
*
|
|
6
|
+
* The error line is always reserved when one could appear, so a form does not
|
|
7
|
+
* jump a row every time validation fires.
|
|
8
|
+
*/
|
|
9
|
+
export const Field = defineComponent('Field', (props) => {
|
|
10
|
+
const theme = useTheme();
|
|
11
|
+
const form = useFormContext();
|
|
12
|
+
const { name, label, hint, required, labelWidth, stacked, children, ...rest } = props;
|
|
13
|
+
const error = form.errorFor(name);
|
|
14
|
+
const labelNode = label
|
|
15
|
+
? h('box', { direction: 'row', gap: 0, width: stacked ? undefined : labelWidth }, h('text', { content: label, fg: error ? 'danger' : 'muted' }), required ? h('text', { content: '*', fg: 'danger' }) : null)
|
|
16
|
+
: null;
|
|
17
|
+
return h('box', { direction: 'column', ...rest },
|
|
18
|
+
// Centred, so the label sits on the control's line of text rather than on
|
|
19
|
+
// the top edge of its frame.
|
|
20
|
+
//
|
|
21
|
+
// A row stretches its children, so a one-row label beside a three-row
|
|
22
|
+
// bordered input was drawn at the top of three rows - level with the
|
|
23
|
+
// border, one above the text it names. Borderless controls are one row
|
|
24
|
+
// and were fine, which is why a form mixing the two had half its labels
|
|
25
|
+
// aligned and half of them a row high.
|
|
26
|
+
h('box', {
|
|
27
|
+
direction: stacked ? 'column' : 'row',
|
|
28
|
+
gap: stacked ? 0 : 1,
|
|
29
|
+
...(stacked ? {} : { align: 'center' }),
|
|
30
|
+
}, labelNode, h('box', { flex: 1 }, children)), error
|
|
31
|
+
? h('box', { direction: 'row', gap: 1 }, stacked ? null : h('box', { width: labelWidth }), h('text', { content: theme.glyphs.warning, fg: 'danger' }), h('text', { content: error, fg: 'danger', wrap: 'word' }))
|
|
32
|
+
: hint
|
|
33
|
+
? h('box', { direction: 'row', gap: 1 }, stacked ? null : h('box', { width: labelWidth }), h('text', { content: hint, fg: 'subtle' }))
|
|
34
|
+
: null);
|
|
35
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BoxProps, SemanticVariant } from '@textui/core';
|
|
2
|
+
export interface FormActionsProps extends BoxProps {
|
|
3
|
+
submitLabel?: string;
|
|
4
|
+
cancelLabel?: string;
|
|
5
|
+
onCancel?(): void;
|
|
6
|
+
tone?: SemanticVariant;
|
|
7
|
+
/** Disable submit until something changed. */
|
|
8
|
+
requireDirty?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const FormActions: (props: FormActionsProps) => import("@textui/core").RenderOutput;
|
|
11
|
+
//# sourceMappingURL=form-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-actions.d.ts","sourceRoot":"","sources":["../../src/form/form-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAK9D,MAAM,WAAW,gBAAiB,SAAQ,QAAQ;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,IAAI,CAAC;IAClB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,WAAW,kEAoBtB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineComponent, h, useTheme } from '@textui/core';
|
|
2
|
+
import { Button } from '../control/index.js';
|
|
3
|
+
import { useFormContext } from './shared.js';
|
|
4
|
+
export const FormActions = defineComponent('FormActions', (props) => {
|
|
5
|
+
const theme = useTheme();
|
|
6
|
+
const form = useFormContext();
|
|
7
|
+
const { submitLabel = 'Submit', cancelLabel = 'Cancel', onCancel, tone = 'primary', requireDirty = false, ...rest } = props;
|
|
8
|
+
const blocked = form.submitting || (requireDirty && !form.dirty);
|
|
9
|
+
return h('box', { direction: 'row', gap: 1, justify: 'end', ...rest }, onCancel ? h(Button, { label: cancelLabel, onPress: onCancel }) : null, h(Button, {
|
|
10
|
+
label: form.submitting ? `${submitLabel}${theme.glyphs.ellipsis}` : submitLabel,
|
|
11
|
+
tone,
|
|
12
|
+
variant: 'solid',
|
|
13
|
+
disabled: blocked,
|
|
14
|
+
onPress: () => void form.submit(),
|
|
15
|
+
}));
|
|
16
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
export interface FormSectionProps extends BoxProps {
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const FormSection: (props: FormSectionProps) => import("@textui/core").RenderOutput;
|
|
7
|
+
//# sourceMappingURL=form-section.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-section.d.ts","sourceRoot":"","sources":["../../src/form/form-section.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C,MAAM,WAAW,gBAAiB,SAAQ,QAAQ;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,WAAW,kEAOtB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { defineComponent, h } from '@textui/core';
|
|
2
|
+
export const FormSection = defineComponent('FormSection', (props) => {
|
|
3
|
+
const { title, description, children, ...rest } = props;
|
|
4
|
+
return h('box', { direction: 'column', ...rest }, h('text', { content: title, bold: true }), description ? h('text', { content: description, fg: 'muted', wrap: 'word' }) : null, h('box', { direction: 'column' }, children));
|
|
5
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
import type { FormApi } from './shared.js';
|
|
3
|
+
export interface FormProps extends BoxProps {
|
|
4
|
+
form: FormApi<Record<string, unknown>>;
|
|
5
|
+
}
|
|
6
|
+
export declare const Form: (props: FormProps) => import("@textui/core").RenderOutput;
|
|
7
|
+
//# sourceMappingURL=form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/form/form.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,MAAM,WAAW,SAAU,SAAQ,QAAQ;IACzC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,IAAI,2DAGhB,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { defineComponent, h } from '@textui/core';
|
|
2
|
+
import { FormContext } from './shared.js';
|
|
3
|
+
export const Form = defineComponent('Form', ({ form, children, ...rest }) => h(FormContext.Provider, { value: form }, h('box', { role: 'form', direction: 'column', ...rest }, children)));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ComponentDefinition } from '@textui/core';
|
|
2
|
+
import type { FormApi, FormErrors } from './shared.js';
|
|
3
|
+
/**
|
|
4
|
+
* Forms and validation.
|
|
5
|
+
*
|
|
6
|
+
* Validation runs on a whole values object rather than per field, because the
|
|
7
|
+
* rules people actually need are cross-field - "confirm must match password",
|
|
8
|
+
* "end must be after start" - and a per-field validator cannot see its
|
|
9
|
+
* siblings. Fields subscribe to the errors that name them.
|
|
10
|
+
*/
|
|
11
|
+
export * from './danger-zone.js';
|
|
12
|
+
export * from './field.js';
|
|
13
|
+
export * from './form.js';
|
|
14
|
+
export * from './form-actions.js';
|
|
15
|
+
export * from './form-section.js';
|
|
16
|
+
export type { FormApi, FormErrors, FormState } from './shared.js';
|
|
17
|
+
export { useFormContext } from './shared.js';
|
|
18
|
+
export type Validator<T> = (values: T) => FormErrors<T> | null;
|
|
19
|
+
export interface UseFormOptions<T extends Record<string, unknown>> {
|
|
20
|
+
initialValues: T;
|
|
21
|
+
validate?: Validator<T>;
|
|
22
|
+
onSubmit?(values: T): void | Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export declare function useForm<T extends Record<string, unknown>>(options: UseFormOptions<T>): FormApi<T>;
|
|
25
|
+
/** Small composable validators, because every form needs the same four. */
|
|
26
|
+
export declare const validators: {
|
|
27
|
+
required: (message?: string) => (value: unknown) => string | undefined;
|
|
28
|
+
minLength: (n: number, message?: string) => (value: unknown) => string | undefined;
|
|
29
|
+
maxLength: (n: number, message?: string) => (value: unknown) => string | undefined;
|
|
30
|
+
pattern: (re: RegExp, message?: string) => (value: unknown) => string | undefined;
|
|
31
|
+
range: (min: number, max: number, message?: string) => (value: unknown) => string | undefined;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Build a whole-form validator from per-field rules.
|
|
35
|
+
*
|
|
36
|
+
* Returns the error map rather than `FormErrors | null`, so it composes: a
|
|
37
|
+
* caller can take the result, add a cross-field rule, and hand the whole thing
|
|
38
|
+
* to `useForm` without a null check in between.
|
|
39
|
+
*/
|
|
40
|
+
export declare function fieldValidators<T extends Record<string, unknown>>(rules: Partial<Record<keyof T & string, ((value: unknown) => string | undefined)[]>>): (values: T) => FormErrors<T>;
|
|
41
|
+
export declare const FORM_COMPONENTS: ComponentDefinition[];
|
|
42
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/form/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAOxD,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;GAOG;AACH,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;AAE/D,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC/D,aAAa,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IACxB,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvD,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,CAAC,CAyFZ;AAID,2EAA2E;AAC3E,eAAO,MAAM,UAAU;qCAElB,OAAO,OAAO,KAAG,MAAM,GAAG,SAAS;mBAGvB,MAAM,YAAY,MAAM,MACpC,OAAO,OAAO,KAAG,MAAM,GAAG,SAAS;mBAKvB,MAAM,YAAY,MAAM,MACpC,OAAO,OAAO,KAAG,MAAM,GAAG,SAAS;kBAKxB,MAAM,wBACjB,OAAO,OAAO,KAAG,MAAM,GAAG,SAAS;iBAGzB,MAAM,OAAO,MAAM,YAAY,MAAM,MAC/C,OAAO,OAAO,KAAG,MAAM,GAAG,SAAS;CAMvC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,GACnF,CAAC,MAAM,EAAE,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,CAc9B;AAED,eAAO,MAAM,eAAe,EAAE,mBAAmB,EAMhD,CAAC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from '@textui/core';
|
|
2
|
+
import { DangerZone } from './danger-zone.js';
|
|
3
|
+
import { Field } from './field.js';
|
|
4
|
+
import { FormActions } from './form-actions.js';
|
|
5
|
+
import { FormSection } from './form-section.js';
|
|
6
|
+
import { Form } from './form.js';
|
|
7
|
+
/**
|
|
8
|
+
* Forms and validation.
|
|
9
|
+
*
|
|
10
|
+
* Validation runs on a whole values object rather than per field, because the
|
|
11
|
+
* rules people actually need are cross-field - "confirm must match password",
|
|
12
|
+
* "end must be after start" - and a per-field validator cannot see its
|
|
13
|
+
* siblings. Fields subscribe to the errors that name them.
|
|
14
|
+
*/
|
|
15
|
+
export * from './danger-zone.js';
|
|
16
|
+
export * from './field.js';
|
|
17
|
+
export * from './form.js';
|
|
18
|
+
export * from './form-actions.js';
|
|
19
|
+
export * from './form-section.js';
|
|
20
|
+
export { useFormContext } from './shared.js';
|
|
21
|
+
export function useForm(options) {
|
|
22
|
+
const [values, setValuesState] = useState(options.initialValues);
|
|
23
|
+
// Validate the initial values immediately: a form whose required fields are
|
|
24
|
+
// empty is not valid, and reporting it as valid until the first keystroke
|
|
25
|
+
// makes `form.valid` useless for enabling a submit button.
|
|
26
|
+
const [errors, setErrors] = useState(() => options.validate?.(options.initialValues) ?? {});
|
|
27
|
+
const [touched, setTouched] = useState({});
|
|
28
|
+
const [submitting, setSubmitting] = useState(false);
|
|
29
|
+
const [submitted, setSubmitted] = useState(false);
|
|
30
|
+
const [initial] = useState(options.initialValues);
|
|
31
|
+
const runValidation = useCallback((next) => options.validate?.(next) ?? {}, [options.validate]);
|
|
32
|
+
const setValue = (field, value) => {
|
|
33
|
+
const next = { ...values, [field]: value };
|
|
34
|
+
setValuesState(next);
|
|
35
|
+
// Re-validate as they type, but only surface what they have touched.
|
|
36
|
+
setErrors(runValidation(next));
|
|
37
|
+
};
|
|
38
|
+
const dirty = useMemo(() => JSON.stringify(values) !== JSON.stringify(initial), [values, initial]);
|
|
39
|
+
const api = {
|
|
40
|
+
values,
|
|
41
|
+
errors,
|
|
42
|
+
touched,
|
|
43
|
+
submitting,
|
|
44
|
+
submitted,
|
|
45
|
+
valid: Object.keys(errors).length === 0,
|
|
46
|
+
dirty,
|
|
47
|
+
setValue,
|
|
48
|
+
setValues(patch) {
|
|
49
|
+
const next = { ...values, ...patch };
|
|
50
|
+
setValuesState(next);
|
|
51
|
+
setErrors(runValidation(next));
|
|
52
|
+
},
|
|
53
|
+
setError(field, message) {
|
|
54
|
+
setErrors({ ...errors, [field]: message });
|
|
55
|
+
},
|
|
56
|
+
touch(field) {
|
|
57
|
+
if (touched[field])
|
|
58
|
+
return;
|
|
59
|
+
setTouched({ ...touched, [field]: true });
|
|
60
|
+
},
|
|
61
|
+
reset(next) {
|
|
62
|
+
setValuesState(next ?? initial);
|
|
63
|
+
setErrors({});
|
|
64
|
+
setTouched({});
|
|
65
|
+
setSubmitted(false);
|
|
66
|
+
},
|
|
67
|
+
validate() {
|
|
68
|
+
const found = runValidation(values);
|
|
69
|
+
setErrors(found);
|
|
70
|
+
return Object.keys(found).length === 0;
|
|
71
|
+
},
|
|
72
|
+
async submit() {
|
|
73
|
+
const found = runValidation(values);
|
|
74
|
+
setErrors(found);
|
|
75
|
+
// Submitting reveals every error, touched or not.
|
|
76
|
+
setTouched(Object.keys(values).reduce((acc, key) => ({ ...acc, [key]: true }), {}));
|
|
77
|
+
if (Object.keys(found).length > 0)
|
|
78
|
+
return;
|
|
79
|
+
setSubmitting(true);
|
|
80
|
+
try {
|
|
81
|
+
await options.onSubmit?.(values);
|
|
82
|
+
setSubmitted(true);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
setSubmitting(false);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
errorFor(field) {
|
|
89
|
+
return touched[field] || submitted ? errors[field] : undefined;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
return api;
|
|
93
|
+
}
|
|
94
|
+
// ------------------------------------------------------------- validators
|
|
95
|
+
/** Small composable validators, because every form needs the same four. */
|
|
96
|
+
export const validators = {
|
|
97
|
+
required: (message = 'Required') => (value) => value === undefined || value === null || value === '' ? message : undefined,
|
|
98
|
+
minLength: (n, message) => (value) => typeof value === 'string' && value.length < n
|
|
99
|
+
? message ?? `Must be at least ${n} characters`
|
|
100
|
+
: undefined,
|
|
101
|
+
maxLength: (n, message) => (value) => typeof value === 'string' && value.length > n
|
|
102
|
+
? message ?? `Must be at most ${n} characters`
|
|
103
|
+
: undefined,
|
|
104
|
+
pattern: (re, message = 'Invalid format') => (value) => typeof value === 'string' && value !== '' && !re.test(value) ? message : undefined,
|
|
105
|
+
range: (min, max, message) => (value) => {
|
|
106
|
+
const n = Number(value);
|
|
107
|
+
return Number.isNaN(n) || n < min || n > max
|
|
108
|
+
? message ?? `Must be between ${min} and ${max}`
|
|
109
|
+
: undefined;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Build a whole-form validator from per-field rules.
|
|
114
|
+
*
|
|
115
|
+
* Returns the error map rather than `FormErrors | null`, so it composes: a
|
|
116
|
+
* caller can take the result, add a cross-field rule, and hand the whole thing
|
|
117
|
+
* to `useForm` without a null check in between.
|
|
118
|
+
*/
|
|
119
|
+
export function fieldValidators(rules) {
|
|
120
|
+
return (values) => {
|
|
121
|
+
const errors = {};
|
|
122
|
+
for (const [field, checks] of Object.entries(rules)) {
|
|
123
|
+
for (const check of checks ?? []) {
|
|
124
|
+
const message = check(values[field]);
|
|
125
|
+
if (message) {
|
|
126
|
+
errors[field] = message;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return errors;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export const FORM_COMPONENTS = [
|
|
135
|
+
{ component: 'Form', category: 'form', renderer: { kind: 'function', render: Form }, role: 'form', description: 'Provides form state to its fields.' },
|
|
136
|
+
{ component: 'Field', category: 'form', renderer: { kind: 'function', render: Field }, description: 'Label, control and error, aligned.' },
|
|
137
|
+
{ component: 'FormSection', category: 'form', renderer: { kind: 'function', render: FormSection }, description: 'A titled group of fields.' },
|
|
138
|
+
{ component: 'FormActions', category: 'form', renderer: { kind: 'function', render: FormActions }, description: 'Submit and cancel.' },
|
|
139
|
+
{ component: 'DangerZone', category: 'form', renderer: { kind: 'function', render: DangerZone }, description: 'Destructive action, fenced off.' },
|
|
140
|
+
];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type FormErrors<T> = Partial<Record<keyof T & string, string>>;
|
|
2
|
+
export interface FormState<T extends Record<string, unknown>> {
|
|
3
|
+
values: T;
|
|
4
|
+
errors: FormErrors<T>;
|
|
5
|
+
/** Fields the user has interacted with. Errors show only for these. */
|
|
6
|
+
touched: Partial<Record<keyof T & string, boolean>>;
|
|
7
|
+
submitting: boolean;
|
|
8
|
+
submitted: boolean;
|
|
9
|
+
valid: boolean;
|
|
10
|
+
dirty: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface FormApi<T extends Record<string, unknown>> extends FormState<T> {
|
|
13
|
+
setValue<K extends keyof T & string>(field: K, value: T[K]): void;
|
|
14
|
+
setValues(values: Partial<T>): void;
|
|
15
|
+
setError(field: keyof T & string, message: string | undefined): void;
|
|
16
|
+
touch(field: keyof T & string): void;
|
|
17
|
+
reset(values?: T): void;
|
|
18
|
+
validate(): boolean;
|
|
19
|
+
submit(): Promise<void>;
|
|
20
|
+
/** The error to show for a field: only after it has been touched. */
|
|
21
|
+
errorFor(field: keyof T & string): string | undefined;
|
|
22
|
+
}
|
|
23
|
+
export declare const FormContext: import("@textui/core").Context<FormApi<Record<string, unknown>> | null>;
|
|
24
|
+
export declare function useFormContext<T extends Record<string, unknown>>(): FormApi<T>;
|
|
25
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/form/shared.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEtE,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1D,MAAM,EAAE,CAAC,CAAC;IACV,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACtB,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAClE,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IACrE,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;IACrC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IACxB,QAAQ,IAAI,OAAO,CAAC;IACpB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACvD;AAED,eAAO,MAAM,WAAW,yEAAuE,CAAC;AAEhG,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAM9E"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext, useContext } from '@textui/core';
|
|
2
|
+
export const FormContext = createContext('Form', null);
|
|
3
|
+
export function useFormContext() {
|
|
4
|
+
const form = useContext(FormContext);
|
|
5
|
+
if (!form) {
|
|
6
|
+
throw new Error('[textui] a Field must be rendered inside a Form');
|
|
7
|
+
}
|
|
8
|
+
return form;
|
|
9
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ComponentDefinition, TextUIApp, Disposable } from '@textui/core';
|
|
2
|
+
export * from './layout/index.js';
|
|
3
|
+
export * from './display/index.js';
|
|
4
|
+
export * from './control/index.js';
|
|
5
|
+
export * from './form/index.js';
|
|
6
|
+
export * from './data/index.js';
|
|
7
|
+
export * from './navigation/index.js';
|
|
8
|
+
export * from './overlay/index.js';
|
|
9
|
+
export * from './chart/index.js';
|
|
10
|
+
export * from './surface/index.js';
|
|
11
|
+
export * from './shells/index.js';
|
|
12
|
+
export * from './panel/index.js';
|
|
13
|
+
export * from './decorations.js';
|
|
14
|
+
export * from './find.js';
|
|
15
|
+
export * from './viewport.js';
|
|
16
|
+
export * from './tone.js';
|
|
17
|
+
/** Every component the library ships, in one list. */
|
|
18
|
+
export declare const CATALOG: ComponentDefinition[];
|
|
19
|
+
/**
|
|
20
|
+
* Register the catalog, the surface layouts and the built-in shells.
|
|
21
|
+
*
|
|
22
|
+
* One call, because a half-registered catalog fails at mount time rather than
|
|
23
|
+
* at boot - and a runtime miss is much harder to read than a boot error.
|
|
24
|
+
*/
|
|
25
|
+
export declare function registerBuiltins(app: TextUIApp): Disposable;
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAc/E,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAE1B,sDAAsD;AACtD,eAAO,MAAM,OAAO,EAAE,mBAAmB,EAaxC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,GAAG,UAAU,CAU3D"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createBag, PRIMITIVES } from '@textui/core';
|
|
2
|
+
import { LAYOUT_COMPONENTS } from './layout/index.js';
|
|
3
|
+
import { DISPLAY_COMPONENTS } from './display/index.js';
|
|
4
|
+
import { CONTROL_COMPONENTS } from './control/index.js';
|
|
5
|
+
import { FORM_COMPONENTS } from './form/index.js';
|
|
6
|
+
import { DATA_COMPONENTS } from './data/index.js';
|
|
7
|
+
import { NAVIGATION_COMPONENTS } from './navigation/index.js';
|
|
8
|
+
import { OVERLAY_COMPONENTS } from './overlay/index.js';
|
|
9
|
+
import { CHART_COMPONENTS } from './chart/index.js';
|
|
10
|
+
import { SURFACE_COMPONENTS, BUILTIN_LAYOUTS } from './surface/index.js';
|
|
11
|
+
import { SHELL_COMPONENTS, BUILTIN_SHELLS } from './shells/index.js';
|
|
12
|
+
import { PANEL_COMPONENTS, panelCommands } from './panel/index.js';
|
|
13
|
+
export * from './layout/index.js';
|
|
14
|
+
export * from './display/index.js';
|
|
15
|
+
export * from './control/index.js';
|
|
16
|
+
export * from './form/index.js';
|
|
17
|
+
export * from './data/index.js';
|
|
18
|
+
export * from './navigation/index.js';
|
|
19
|
+
export * from './overlay/index.js';
|
|
20
|
+
export * from './chart/index.js';
|
|
21
|
+
export * from './surface/index.js';
|
|
22
|
+
export * from './shells/index.js';
|
|
23
|
+
export * from './panel/index.js';
|
|
24
|
+
export * from './decorations.js';
|
|
25
|
+
export * from './find.js';
|
|
26
|
+
export * from './viewport.js';
|
|
27
|
+
export * from './tone.js';
|
|
28
|
+
/** Every component the library ships, in one list. */
|
|
29
|
+
export const CATALOG = [
|
|
30
|
+
...PRIMITIVES,
|
|
31
|
+
...LAYOUT_COMPONENTS,
|
|
32
|
+
...DISPLAY_COMPONENTS,
|
|
33
|
+
...CONTROL_COMPONENTS,
|
|
34
|
+
...FORM_COMPONENTS,
|
|
35
|
+
...DATA_COMPONENTS,
|
|
36
|
+
...NAVIGATION_COMPONENTS,
|
|
37
|
+
...OVERLAY_COMPONENTS,
|
|
38
|
+
...CHART_COMPONENTS,
|
|
39
|
+
...SURFACE_COMPONENTS,
|
|
40
|
+
...SHELL_COMPONENTS,
|
|
41
|
+
...PANEL_COMPONENTS,
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Register the catalog, the surface layouts and the built-in shells.
|
|
45
|
+
*
|
|
46
|
+
* One call, because a half-registered catalog fails at mount time rather than
|
|
47
|
+
* at boot - and a runtime miss is much harder to read than a boot error.
|
|
48
|
+
*/
|
|
49
|
+
export function registerBuiltins(app) {
|
|
50
|
+
const bag = createBag();
|
|
51
|
+
bag.add(app.components.registerMany(CATALOG));
|
|
52
|
+
for (const layout of BUILTIN_LAYOUTS)
|
|
53
|
+
bag.add(app.layouts.register(layout));
|
|
54
|
+
for (const shell of BUILTIN_SHELLS)
|
|
55
|
+
bag.add(app.shells.register(shell));
|
|
56
|
+
// The panel commands come with the panel: what they offer is read off the
|
|
57
|
+
// resource registry, so a host that mounts a panel has already said
|
|
58
|
+
// everything "open with" needs. Keys stay the host's choice.
|
|
59
|
+
for (const command of panelCommands(app))
|
|
60
|
+
bag.add(app.commands.register(command));
|
|
61
|
+
return bag;
|
|
62
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
export interface CenterProps extends BoxProps {
|
|
3
|
+
/** Centre horizontally, vertically, or both. */
|
|
4
|
+
axis?: 'both' | 'horizontal' | 'vertical';
|
|
5
|
+
}
|
|
6
|
+
export declare const Center: (props: CenterProps) => import("@textui/core").RenderOutput;
|
|
7
|
+
//# sourceMappingURL=center.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"center.d.ts","sourceRoot":"","sources":["../../src/layout/center.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC3C,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC;CAC3C;AAED,eAAO,MAAM,MAAM,6DAQlB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineComponent, h } from '@textui/core';
|
|
2
|
+
export const Center = defineComponent('Center', ({ axis = 'both', ...props }) => h('box', {
|
|
3
|
+
flex: props.flex ?? 1,
|
|
4
|
+
direction: 'column',
|
|
5
|
+
justify: axis === 'horizontal' ? 'start' : 'center',
|
|
6
|
+
align: axis === 'vertical' ? 'stretch' : 'center',
|
|
7
|
+
...props,
|
|
8
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column.d.ts","sourceRoot":"","sources":["../../src/layout/column.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C,eAAO,MAAM,MAAM,0DAElB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BoxProps } from '@textui/core';
|
|
2
|
+
export interface DividerProps extends Omit<BoxProps, 'direction'> {
|
|
3
|
+
/** A divider runs across the flow, so it names its own axis. */
|
|
4
|
+
direction?: 'horizontal' | 'vertical';
|
|
5
|
+
/** Text set into the rule. */
|
|
6
|
+
label?: string;
|
|
7
|
+
labelAlign?: 'left' | 'center' | 'right';
|
|
8
|
+
char?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const Divider: (props: DividerProps) => import("@textui/core").RenderOutput;
|
|
11
|
+
//# sourceMappingURL=divider.d.ts.map
|