termcast 1.3.19 → 1.3.24
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/dist/ai.d.ts +104 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +135 -0
- package/dist/ai.js.map +1 -0
- package/dist/apis/browser-extension.d.ts +18 -0
- package/dist/apis/browser-extension.d.ts.map +1 -0
- package/dist/apis/browser-extension.js +14 -0
- package/dist/apis/browser-extension.js.map +1 -0
- package/dist/apis/localstorage.d.ts.map +1 -1
- package/dist/apis/localstorage.js +4 -7
- package/dist/apis/localstorage.js.map +1 -1
- package/dist/apis/oauth.d.ts.map +1 -1
- package/dist/apis/oauth.js +5 -1
- package/dist/apis/oauth.js.map +1 -1
- package/dist/apis/preferences.d.ts.map +1 -1
- package/dist/apis/preferences.js +38 -19
- package/dist/apis/preferences.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +2 -1
- package/dist/build.js.map +1 -1
- package/dist/cache.d.ts +32 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +205 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +69 -33
- package/dist/cli.js.map +1 -1
- package/dist/clipboard.d.ts +36 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +154 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +24 -6
- package/dist/compile.js.map +1 -1
- package/dist/components/actions.d.ts.map +1 -1
- package/dist/components/actions.js +56 -30
- package/dist/components/actions.js.map +1 -1
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +4 -0
- package/dist/components/detail.js.map +1 -1
- package/dist/components/dropdown.d.ts.map +1 -1
- package/dist/components/dropdown.js +38 -15
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/extension-preferences.d.ts.map +1 -1
- package/dist/components/extension-preferences.js +40 -13
- package/dist/components/extension-preferences.js.map +1 -1
- package/dist/components/form/checkbox.d.ts.map +1 -1
- package/dist/components/form/checkbox.js +5 -3
- package/dist/components/form/checkbox.js.map +1 -1
- package/dist/components/form/date-picker.d.ts.map +1 -1
- package/dist/components/form/date-picker.js +5 -3
- package/dist/components/form/date-picker.js.map +1 -1
- package/dist/components/form/description.d.ts.map +1 -1
- package/dist/components/form/description.js +2 -2
- package/dist/components/form/description.js.map +1 -1
- package/dist/components/form/dropdown.d.ts.map +1 -1
- package/dist/components/form/dropdown.js +84 -80
- package/dist/components/form/dropdown.js.map +1 -1
- package/dist/components/form/file-autocomplete.d.ts +3 -6
- package/dist/components/form/file-autocomplete.d.ts.map +1 -1
- package/dist/components/form/file-autocomplete.js +61 -66
- package/dist/components/form/file-autocomplete.js.map +1 -1
- package/dist/components/form/file-picker.d.ts.map +1 -1
- package/dist/components/form/file-picker.js +33 -30
- package/dist/components/form/file-picker.js.map +1 -1
- package/dist/components/form/form-end.d.ts.map +1 -1
- package/dist/components/form/form-end.js +21 -1
- package/dist/components/form/form-end.js.map +1 -1
- package/dist/components/form/form-type-only.d.ts +174 -0
- package/dist/components/form/form-type-only.d.ts.map +1 -0
- package/dist/components/form/form-type-only.js +2 -0
- package/dist/components/form/form-type-only.js.map +1 -0
- package/dist/components/form/index.d.ts +3 -1
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +100 -28
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/form/password-field.d.ts.map +1 -1
- package/dist/components/form/password-field.js +5 -3
- package/dist/components/form/password-field.js.map +1 -1
- package/dist/components/form/text-area.d.ts.map +1 -1
- package/dist/components/form/text-area.js +5 -3
- package/dist/components/form/text-area.js.map +1 -1
- package/dist/components/form/text-field.d.ts.map +1 -1
- package/dist/components/form/text-field.js +6 -4
- package/dist/components/form/text-field.js.map +1 -1
- package/dist/components/form/types.d.ts +5 -0
- package/dist/components/form/types.d.ts.map +1 -1
- package/dist/components/form/use-form-handling.d.ts +4 -0
- package/dist/components/form/use-form-handling.d.ts.map +1 -0
- package/dist/components/form/use-form-handling.js +37 -0
- package/dist/components/form/use-form-handling.js.map +1 -0
- package/dist/components/form/with-left-border.d.ts +2 -1
- package/dist/components/form/with-left-border.d.ts.map +1 -1
- package/dist/components/form/with-left-border.js +27 -3
- package/dist/components/form/with-left-border.js.map +1 -1
- package/dist/components/icon.d.ts +1 -0
- package/dist/components/icon.d.ts.map +1 -1
- package/dist/components/icon.js +24 -8
- package/dist/components/icon.js.map +1 -1
- package/dist/components/list.d.ts +2 -2
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +140 -62
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-bar.d.ts.map +1 -1
- package/dist/components/loading-bar.js +2 -2
- package/dist/components/loading-bar.js.map +1 -1
- package/dist/components/loading-text.d.ts +8 -0
- package/dist/components/loading-text.d.ts.map +1 -0
- package/dist/components/loading-text.js +58 -0
- package/dist/components/loading-text.js.map +1 -0
- package/dist/descendants.js +1 -1
- package/dist/descendants.js.map +1 -1
- package/dist/dev-ui.d.ts +7 -0
- package/dist/dev-ui.d.ts.map +1 -0
- package/dist/dev-ui.js +118 -0
- package/dist/dev-ui.js.map +1 -0
- package/dist/environment.d.ts +63 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +189 -0
- package/dist/environment.js.map +1 -0
- package/dist/examples/datepicker.d.ts +2 -0
- package/dist/examples/datepicker.d.ts.map +1 -0
- package/dist/examples/datepicker.js +344 -0
- package/dist/examples/datepicker.js.map +1 -0
- package/dist/examples/file-autocomplete.vitest.d.ts +2 -0
- package/dist/examples/file-autocomplete.vitest.d.ts.map +1 -0
- package/dist/examples/file-autocomplete.vitest.js +223 -0
- package/dist/examples/file-autocomplete.vitest.js.map +1 -0
- package/dist/examples/form-basic-arrow-keys.vitest.d.ts +2 -0
- package/dist/examples/form-basic-arrow-keys.vitest.d.ts.map +1 -0
- package/dist/examples/form-basic-arrow-keys.vitest.js +46 -0
- package/dist/examples/form-basic-arrow-keys.vitest.js.map +1 -0
- package/dist/examples/form-basic.vitest.d.ts +2 -0
- package/dist/examples/form-basic.vitest.d.ts.map +1 -0
- package/dist/examples/form-basic.vitest.js +630 -0
- package/dist/examples/form-basic.vitest.js.map +1 -0
- package/dist/examples/form-dropdown-with-sections.d.ts +2 -0
- package/dist/examples/form-dropdown-with-sections.d.ts.map +1 -0
- package/dist/examples/form-dropdown-with-sections.js +13 -0
- package/dist/examples/form-dropdown-with-sections.js.map +1 -0
- package/dist/examples/form-dropdown-with-sections.vitest.d.ts +2 -0
- package/dist/examples/form-dropdown-with-sections.vitest.d.ts.map +1 -0
- package/dist/examples/form-dropdown-with-sections.vitest.js +75 -0
- package/dist/examples/form-dropdown-with-sections.vitest.js.map +1 -0
- package/dist/examples/form-dropdown.vitest.d.ts +2 -0
- package/dist/examples/form-dropdown.vitest.d.ts.map +1 -0
- package/dist/examples/form-dropdown.vitest.js +854 -0
- package/dist/examples/form-dropdown.vitest.js.map +1 -0
- package/dist/examples/form-multiselect-dropdown.d.ts +2 -0
- package/dist/examples/form-multiselect-dropdown.d.ts.map +1 -0
- package/dist/examples/form-multiselect-dropdown.js +13 -0
- package/dist/examples/form-multiselect-dropdown.js.map +1 -0
- package/dist/examples/form-scroll.d.ts.map +1 -1
- package/dist/examples/form-scroll.js +7 -1
- package/dist/examples/form-scroll.js.map +1 -1
- package/dist/examples/form-scroll.vitest.d.ts +2 -0
- package/dist/examples/form-scroll.vitest.d.ts.map +1 -0
- package/dist/examples/form-scroll.vitest.js +211 -0
- package/dist/examples/form-scroll.vitest.js.map +1 -0
- package/dist/examples/form-tagpicker.vitest.d.ts +2 -0
- package/dist/examples/form-tagpicker.vitest.d.ts.map +1 -0
- package/dist/examples/form-tagpicker.vitest.js +736 -0
- package/dist/examples/form-tagpicker.vitest.js.map +1 -0
- package/dist/examples/internal/descendants-filtering.js +1 -1
- package/dist/examples/internal/descendants-filtering.js.map +1 -1
- package/dist/examples/internal/descendants.js +1 -1
- package/dist/examples/internal/descendants.js.map +1 -1
- package/dist/examples/internal/nested-boxes.d.ts +2 -0
- package/dist/examples/internal/nested-boxes.d.ts.map +1 -0
- package/dist/examples/internal/nested-boxes.js +7 -0
- package/dist/examples/internal/nested-boxes.js.map +1 -0
- package/dist/examples/internal/rhf-custom-ref.js +2 -2
- package/dist/examples/internal/rhf-custom-ref.js.map +1 -1
- package/dist/examples/internal/scrollbox-demo.js +3 -22
- package/dist/examples/internal/scrollbox-demo.js.map +1 -1
- package/dist/examples/internal/scrollbox-descendants.d.ts +2 -0
- package/dist/examples/internal/scrollbox-descendants.d.ts.map +1 -0
- package/dist/examples/internal/scrollbox-descendants.js +83 -0
- package/dist/examples/internal/scrollbox-descendants.js.map +1 -0
- package/dist/examples/internal/scrollbox-with-descendants.js +4 -8
- package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -1
- package/dist/examples/internal/simple-scrollbox.vitest.d.ts +2 -0
- package/dist/examples/internal/simple-scrollbox.vitest.d.ts.map +1 -0
- package/dist/examples/internal/simple-scrollbox.vitest.js +96 -0
- package/dist/examples/internal/simple-scrollbox.vitest.js.map +1 -0
- package/dist/examples/internal/unicode-square-repro.d.ts +2 -0
- package/dist/examples/internal/unicode-square-repro.d.ts.map +1 -0
- package/dist/examples/internal/unicode-square-repro.js +7 -0
- package/dist/examples/internal/unicode-square-repro.js.map +1 -0
- package/dist/examples/list-detail-metadata.d.ts +2 -0
- package/dist/examples/list-detail-metadata.d.ts.map +1 -0
- package/dist/examples/list-detail-metadata.js +8 -0
- package/dist/examples/list-detail-metadata.js.map +1 -0
- package/dist/examples/list-dropdown-default.vitest.d.ts +2 -0
- package/dist/examples/list-dropdown-default.vitest.d.ts.map +1 -0
- package/dist/examples/list-dropdown-default.vitest.js +234 -0
- package/dist/examples/list-dropdown-default.vitest.js.map +1 -0
- package/dist/examples/list-fetch-data.vitest.d.ts +2 -0
- package/dist/examples/list-fetch-data.vitest.d.ts.map +1 -0
- package/dist/examples/list-fetch-data.vitest.js +111 -0
- package/dist/examples/list-fetch-data.vitest.js.map +1 -0
- package/dist/examples/list-filter-navigation.d.ts +2 -0
- package/dist/examples/list-filter-navigation.d.ts.map +1 -0
- package/dist/examples/list-filter-navigation.js +8 -0
- package/dist/examples/list-filter-navigation.js.map +1 -0
- package/dist/examples/list-scrollbox.vitest.d.ts +2 -0
- package/dist/examples/list-scrollbox.vitest.d.ts.map +1 -0
- package/dist/examples/list-scrollbox.vitest.js +93 -0
- package/dist/examples/list-scrollbox.vitest.js.map +1 -0
- package/dist/examples/list-with-detail-long.d.ts +2 -0
- package/dist/examples/list-with-detail-long.d.ts.map +1 -0
- package/dist/examples/list-with-detail-long.js +53 -0
- package/dist/examples/list-with-detail-long.js.map +1 -0
- package/dist/examples/list-with-detail.vitest.d.ts +2 -0
- package/dist/examples/list-with-detail.vitest.d.ts.map +1 -0
- package/dist/examples/list-with-detail.vitest.js +434 -0
- package/dist/examples/list-with-detail.vitest.js.map +1 -0
- package/dist/examples/list-with-dropdown.vitest.d.ts +2 -0
- package/dist/examples/list-with-dropdown.vitest.d.ts.map +1 -0
- package/dist/examples/list-with-dropdown.vitest.js +337 -0
- package/dist/examples/list-with-dropdown.vitest.js.map +1 -0
- package/dist/examples/list-with-sections.js +5 -1
- package/dist/examples/list-with-sections.js.map +1 -1
- package/dist/examples/list-with-sections.vitest.d.ts +2 -0
- package/dist/examples/list-with-sections.vitest.d.ts.map +1 -0
- package/dist/examples/list-with-sections.vitest.js +601 -0
- package/dist/examples/list-with-sections.vitest.js.map +1 -0
- package/dist/examples/scrollbox-vertical-centering.d.ts +6 -0
- package/dist/examples/scrollbox-vertical-centering.d.ts.map +1 -0
- package/dist/examples/scrollbox-vertical-centering.js +17 -0
- package/dist/examples/scrollbox-vertical-centering.js.map +1 -0
- package/dist/examples/simple-file-picker.vitest.d.ts +2 -0
- package/dist/examples/simple-file-picker.vitest.d.ts.map +1 -0
- package/dist/examples/simple-file-picker.vitest.js +678 -0
- package/dist/examples/simple-file-picker.vitest.js.map +1 -0
- package/dist/examples/simple-grid.vitest.d.ts +2 -0
- package/dist/examples/simple-grid.vitest.d.ts.map +1 -0
- package/dist/examples/simple-grid.vitest.js +521 -0
- package/dist/examples/simple-grid.vitest.js.map +1 -0
- package/dist/examples/simple-navigation.js +10 -4
- package/dist/examples/simple-navigation.js.map +1 -1
- package/dist/examples/simple-navigation.vitest.d.ts +2 -0
- package/dist/examples/simple-navigation.vitest.d.ts.map +1 -0
- package/dist/examples/simple-navigation.vitest.js +718 -0
- package/dist/examples/simple-navigation.vitest.js.map +1 -0
- package/dist/examples/store.vitest.d.ts +2 -0
- package/dist/examples/store.vitest.d.ts.map +1 -0
- package/dist/examples/store.vitest.js +69 -0
- package/dist/examples/store.vitest.js.map +1 -0
- package/dist/extensions/dev.d.ts +4 -2
- package/dist/extensions/dev.d.ts.map +1 -1
- package/dist/extensions/dev.js +61 -10
- package/dist/extensions/dev.js.map +1 -1
- package/dist/extensions/dev.vitest.d.ts +2 -0
- package/dist/extensions/dev.vitest.d.ts.map +1 -0
- package/dist/extensions/dev.vitest.js +197 -0
- package/dist/extensions/dev.vitest.js.map +1 -0
- package/dist/extensions/home.d.ts.map +1 -1
- package/dist/extensions/home.js +3 -0
- package/dist/extensions/home.js.map +1 -1
- package/dist/home-command.d.ts +8 -0
- package/dist/home-command.d.ts.map +1 -0
- package/dist/home-command.js +181 -0
- package/dist/home-command.js.map +1 -0
- package/dist/hover-repro.d.ts +2 -0
- package/dist/hover-repro.d.ts.map +1 -0
- package/dist/hover-repro.js +20 -0
- package/dist/hover-repro.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/dialog.d.ts +1 -0
- package/dist/internal/dialog.d.ts.map +1 -1
- package/dist/internal/dialog.js +27 -18
- package/dist/internal/dialog.js.map +1 -1
- package/dist/internal/navigation.d.ts +9 -1
- package/dist/internal/navigation.d.ts.map +1 -1
- package/dist/internal/navigation.js +5 -5
- package/dist/internal/navigation.js.map +1 -1
- package/dist/internal/offscreen.d.ts +6 -0
- package/dist/internal/offscreen.d.ts.map +1 -0
- package/dist/internal/offscreen.js +10 -0
- package/dist/internal/offscreen.js.map +1 -0
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +2 -2
- package/dist/internal/providers.js.map +1 -1
- package/dist/internal/scrollbox.d.ts +1 -10
- package/dist/internal/scrollbox.d.ts.map +1 -1
- package/dist/internal/scrollbox.js +2 -1
- package/dist/internal/scrollbox.js.map +1 -1
- package/dist/localstorage.d.ts +13 -0
- package/dist/localstorage.d.ts.map +1 -0
- package/dist/localstorage.js +190 -0
- package/dist/localstorage.js.map +1 -0
- package/dist/oauth.d.ts +142 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +551 -0
- package/dist/oauth.js.map +1 -0
- package/dist/preferences.d.ts +23 -0
- package/dist/preferences.d.ts.map +1 -0
- package/dist/preferences.js +105 -0
- package/dist/preferences.js.map +1 -0
- package/dist/state.d.ts +2 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +3 -0
- package/dist/state.js.map +1 -1
- package/dist/store.d.ts +21 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +84 -0
- package/dist/store.js.map +1 -0
- package/dist/swift-loader.d.ts +3 -0
- package/dist/swift-loader.d.ts.map +1 -0
- package/dist/swift-loader.js +193 -0
- package/dist/swift-loader.js.map +1 -0
- package/dist/swift-runtime.d.ts +2 -0
- package/dist/swift-runtime.d.ts.map +1 -0
- package/dist/swift-runtime.js +27 -0
- package/dist/swift-runtime.js.map +1 -0
- package/dist/toast.d.ts +44 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +221 -0
- package/dist/toast.js.map +1 -0
- package/dist/utils/file-system.d.ts +9 -0
- package/dist/utils/file-system.d.ts.map +1 -1
- package/dist/utils/file-system.js +49 -0
- package/dist/utils/file-system.js.map +1 -1
- package/dist/utils/run-command.d.ts +25 -1
- package/dist/utils/run-command.d.ts.map +1 -1
- package/dist/utils/run-command.js +47 -4
- package/dist/utils/run-command.js.map +1 -1
- package/dist/window.d.ts +12 -0
- package/dist/window.d.ts.map +1 -0
- package/dist/window.js +48 -0
- package/dist/window.js.map +1 -0
- package/package.json +13 -11
- package/src/apis/browser-extension.tsx +29 -0
- package/src/apis/localstorage.test.ts +14 -6
- package/src/apis/localstorage.tsx +8 -5
- package/src/apis/oauth.tsx +5 -1
- package/src/apis/preferences.tsx +48 -22
- package/src/build.test.tsx +52 -0
- package/src/build.tsx +2 -1
- package/src/cli.tsx +80 -39
- package/src/compile.tsx +24 -6
- package/src/components/actions.tsx +94 -32
- package/src/components/detail.tsx +4 -0
- package/src/components/dropdown.tsx +47 -14
- package/src/components/extension-preferences.tsx +44 -16
- package/src/components/form/checkbox.tsx +12 -6
- package/src/components/form/date-picker.tsx +12 -6
- package/src/components/form/description.tsx +7 -2
- package/src/components/form/dropdown.tsx +131 -119
- package/src/components/form/file-autocomplete.tsx +90 -108
- package/src/components/form/file-picker.tsx +54 -43
- package/src/components/form/form-end.tsx +23 -2
- package/src/components/form/index.tsx +152 -34
- package/src/components/form/password-field.tsx +12 -6
- package/src/components/form/text-area.tsx +13 -6
- package/src/components/form/text-field.tsx +13 -6
- package/src/components/form/types.tsx +6 -0
- package/src/components/form/with-left-border.tsx +41 -8
- package/src/components/icon.tsx +27 -8
- package/src/components/list.tsx +193 -74
- package/src/components/loading-bar.tsx +3 -2
- package/src/components/loading-text.tsx +79 -0
- package/src/descendants.tsx +1 -0
- package/src/examples/file-autocomplete.vitest.tsx +130 -125
- package/src/examples/form-basic.vitest.tsx +376 -176
- package/src/examples/form-dropdown.vitest.tsx +126 -126
- package/src/examples/form-scroll.tsx +2 -0
- package/src/examples/form-scroll.vitest.tsx +58 -58
- package/src/examples/form-tagpicker.vitest.tsx +99 -99
- package/src/examples/internal/descendants-filtering.tsx +1 -0
- package/src/examples/internal/descendants.tsx +1 -0
- package/src/examples/internal/rhf-custom-ref.tsx +2 -0
- package/src/examples/internal/scrollbox-demo.tsx +3 -27
- package/src/examples/internal/scrollbox-with-descendants.tsx +4 -7
- package/src/examples/internal/simple-scrollbox.vitest.tsx +7 -5
- package/src/examples/list-detail-metadata.tsx +49 -0
- package/src/examples/list-detail-metadata.vitest.tsx +88 -0
- package/src/examples/list-dropdown-default.vitest.tsx +51 -51
- package/src/examples/list-fetch-data.vitest.tsx +4 -4
- package/src/examples/list-scrollbox.vitest.tsx +73 -14
- package/src/examples/list-with-detail-long.tsx +70 -0
- package/src/examples/list-with-detail.vitest.tsx +198 -92
- package/src/examples/list-with-dropdown.vitest.tsx +53 -53
- package/src/examples/list-with-sections.tsx +1 -0
- package/src/examples/list-with-sections.vitest.tsx +213 -89
- package/src/examples/list-with-toast.vitest.tsx +4 -4
- package/src/examples/simple-file-picker.vitest.tsx +61 -471
- package/src/examples/simple-grid.vitest.tsx +238 -233
- package/src/examples/simple-navigation.tsx +15 -7
- package/src/examples/simple-navigation.vitest.tsx +121 -210
- package/src/examples/store.vitest.tsx +1 -1
- package/src/examples/swift-extension.vitest.tsx +148 -0
- package/src/examples/synonyms.vitest.tsx +159 -0
- package/src/extensions/dev.tsx +74 -7
- package/src/extensions/dev.vitest.tsx +97 -31
- package/src/extensions/home.tsx +6 -0
- package/src/index.tsx +3 -0
- package/src/internal/dialog.tsx +43 -30
- package/src/internal/navigation.tsx +3 -1
- package/src/internal/offscreen.tsx +15 -0
- package/src/internal/providers.tsx +4 -2
- package/src/internal/scrollbox.tsx +4 -8
- package/src/keyboard.test.tsx +69 -0
- package/src/state.tsx +7 -0
- package/src/swift-loader.tsx +239 -0
- package/src/swift-runtime.tsx +36 -0
- package/src/utils/file-system.ts +61 -0
- package/src/utils/run-command.tsx +75 -6
|
@@ -6,7 +6,7 @@ import React, {
|
|
|
6
6
|
useRef,
|
|
7
7
|
useLayoutEffect,
|
|
8
8
|
} from 'react'
|
|
9
|
-
import { BoxRenderable } from '@opentui/core'
|
|
9
|
+
import { BoxRenderable, ScrollBoxRenderable } from '@opentui/core'
|
|
10
10
|
import { useKeyboard } from '@opentui/react'
|
|
11
11
|
import {
|
|
12
12
|
useFormContext,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { WithLeftBorder } from './with-left-border'
|
|
26
26
|
import { useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
27
27
|
import { useFormNavigationHelpers } from './use-form-navigation'
|
|
28
|
+
import { LoadingText } from 'termcast/src/components/loading-text'
|
|
28
29
|
|
|
29
30
|
export interface DropdownProps extends FormItemProps<string | string[]> {
|
|
30
31
|
placeholder?: string
|
|
@@ -62,6 +63,7 @@ interface FormDropdownItemDescendant {
|
|
|
62
63
|
title: string
|
|
63
64
|
icon?: string
|
|
64
65
|
section?: string // Track which section this item belongs to
|
|
66
|
+
elementRef?: BoxRenderable | null
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
const {
|
|
@@ -73,13 +75,12 @@ const {
|
|
|
73
75
|
// Context for dropdown state
|
|
74
76
|
interface FormDropdownContextValue {
|
|
75
77
|
focusedIndex: number
|
|
76
|
-
offset: number
|
|
77
|
-
itemsPerPage: number
|
|
78
78
|
isFocused: boolean
|
|
79
79
|
handleSelect: (descendantId: string) => void
|
|
80
80
|
field: DropdownFieldType
|
|
81
81
|
props: DropdownProps
|
|
82
82
|
descendantsContext: DescendantContextType<FormDropdownItemDescendant>
|
|
83
|
+
scrollBoxRef: React.RefObject<ScrollBoxRenderable | null>
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
// Separate context for section information
|
|
@@ -91,21 +92,20 @@ const SectionContext = createContext<SectionContextValue>({
|
|
|
91
92
|
currentSection: undefined,
|
|
92
93
|
})
|
|
93
94
|
|
|
94
|
-
const itemsPerPage = 4
|
|
95
95
|
const FormDropdownContext = createContext<FormDropdownContextValue>({
|
|
96
96
|
focusedIndex: 0,
|
|
97
|
-
offset: 0,
|
|
98
|
-
itemsPerPage,
|
|
99
97
|
isFocused: false,
|
|
100
98
|
handleSelect: () => {},
|
|
101
99
|
descendantsContext: {} as any,
|
|
102
100
|
field: {} as DropdownFieldType,
|
|
103
101
|
props: {} as DropdownProps,
|
|
102
|
+
scrollBoxRef: { current: null },
|
|
104
103
|
})
|
|
105
104
|
|
|
106
105
|
const DropdownItem = (props: DropdownItemProps) => {
|
|
107
106
|
const context = useContext(FormDropdownContext)
|
|
108
107
|
const sectionContext = useContext(SectionContext)
|
|
108
|
+
const elementRef = useRef<BoxRenderable | null>(null)
|
|
109
109
|
|
|
110
110
|
// Register as descendant
|
|
111
111
|
const descendant = useFormDropdownDescendant({
|
|
@@ -113,16 +113,9 @@ const DropdownItem = (props: DropdownItemProps) => {
|
|
|
113
113
|
title: props.title,
|
|
114
114
|
icon: props.icon,
|
|
115
115
|
section: sectionContext.currentSection,
|
|
116
|
+
elementRef: elementRef.current,
|
|
116
117
|
})
|
|
117
118
|
|
|
118
|
-
// Hide items that are outside the visible range
|
|
119
|
-
if (
|
|
120
|
-
descendant.index < context.offset ||
|
|
121
|
-
descendant.index >= context.offset + context.itemsPerPage
|
|
122
|
-
) {
|
|
123
|
-
return null
|
|
124
|
-
}
|
|
125
|
-
|
|
126
119
|
const isFocused = descendant.index === context.focusedIndex
|
|
127
120
|
|
|
128
121
|
// Check if this item is selected based on field value
|
|
@@ -144,54 +137,34 @@ const DropdownItem = (props: DropdownItemProps) => {
|
|
|
144
137
|
})()
|
|
145
138
|
|
|
146
139
|
return (
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
>
|
|
153
|
-
<text
|
|
154
|
-
fg={
|
|
155
|
-
context.isFocused && isFocused
|
|
156
|
-
? Theme.accent
|
|
157
|
-
: context.isFocused
|
|
158
|
-
? Theme.text
|
|
159
|
-
: Theme.textMuted
|
|
160
|
-
}
|
|
161
|
-
onMouseDown={() => {
|
|
162
|
-
context.handleSelect(descendant.descendantId)
|
|
163
|
-
}}
|
|
140
|
+
<box ref={elementRef} key={props.value}>
|
|
141
|
+
<WithLeftBorder
|
|
142
|
+
isFocused={context.isFocused}
|
|
143
|
+
paddingLeft={0}
|
|
144
|
+
paddingBottom={0}
|
|
164
145
|
>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
146
|
+
<text
|
|
147
|
+
fg={
|
|
148
|
+
context.isFocused && isFocused
|
|
149
|
+
? Theme.accent
|
|
150
|
+
: context.isFocused
|
|
151
|
+
? Theme.text
|
|
152
|
+
: Theme.textMuted
|
|
153
|
+
}
|
|
154
|
+
onMouseDown={() => {
|
|
155
|
+
context.handleSelect(descendant.descendantId)
|
|
156
|
+
}}
|
|
157
|
+
>
|
|
158
|
+
{context.isFocused && isFocused ? '› ' : ' '}
|
|
159
|
+
{isSelected ? '●' : '○'} {props.title}
|
|
160
|
+
</text>
|
|
161
|
+
</WithLeftBorder>
|
|
162
|
+
</box>
|
|
169
163
|
)
|
|
170
164
|
}
|
|
171
165
|
|
|
172
166
|
const DropdownSection = (props: DropdownSectionProps) => {
|
|
173
167
|
const parentContext = useContext(FormDropdownContext)
|
|
174
|
-
const [isVisible, setIsVisible] = useState(false)
|
|
175
|
-
|
|
176
|
-
// Update visibility when offset changes
|
|
177
|
-
useLayoutEffect(() => {
|
|
178
|
-
if (!props.title) {
|
|
179
|
-
setIsVisible(true)
|
|
180
|
-
return
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const items = Object.values(parentContext.descendantsContext.map.current)
|
|
184
|
-
logger.log('DropdownSection: items', items)
|
|
185
|
-
const hasVisibleItems = items.some((item) => {
|
|
186
|
-
return (
|
|
187
|
-
item.props?.section === props.title &&
|
|
188
|
-
item.index >= parentContext.offset &&
|
|
189
|
-
item.index < parentContext.offset + parentContext.itemsPerPage
|
|
190
|
-
)
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
setIsVisible(hasVisibleItems)
|
|
194
|
-
}, [parentContext.offset, props.title, parentContext.itemsPerPage])
|
|
195
168
|
|
|
196
169
|
// Create section context value
|
|
197
170
|
const sectionContextValue = useMemo(
|
|
@@ -204,7 +177,7 @@ const DropdownSection = (props: DropdownSectionProps) => {
|
|
|
204
177
|
return (
|
|
205
178
|
<SectionContext.Provider value={sectionContextValue}>
|
|
206
179
|
<box flexDirection='column'>
|
|
207
|
-
{props.title &&
|
|
180
|
+
{props.title && (
|
|
208
181
|
<WithLeftBorder
|
|
209
182
|
paddingTop={0}
|
|
210
183
|
paddingBottom={0}
|
|
@@ -232,12 +205,16 @@ const DropdownContent = ({
|
|
|
232
205
|
}: DropdownContentProps) => {
|
|
233
206
|
const descendantsContext = useFormDropdownDescendants()
|
|
234
207
|
const isInFocus = useIsInFocus()
|
|
235
|
-
const
|
|
208
|
+
const focusContext = useFocusContext()
|
|
209
|
+
const { focusedField, setFocusedField } = focusContext
|
|
236
210
|
const isFocused = focusedField === props.id
|
|
237
211
|
const [focusedIndex, setFocusedIndex] = useState(0)
|
|
238
|
-
const [offset, setOffset] = useState(0)
|
|
239
212
|
|
|
240
213
|
const elementRef = useRef<BoxRenderable>(null)
|
|
214
|
+
const scrollBoxRef = useRef<ScrollBoxRenderable>(null)
|
|
215
|
+
|
|
216
|
+
const typeAheadTextRef = useRef('')
|
|
217
|
+
const typeAheadTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
241
218
|
|
|
242
219
|
// Register as form field descendant for scroll support
|
|
243
220
|
useFormFieldDescendant({
|
|
@@ -246,9 +223,26 @@ const DropdownContent = ({
|
|
|
246
223
|
})
|
|
247
224
|
|
|
248
225
|
const [selectedTitles, setSelectedTitles] = useState<string[]>([])
|
|
249
|
-
const [itemsCount, setItemsCount] = useState(0)
|
|
250
226
|
|
|
251
|
-
const { navigateToPrevious, navigateToNext } = useFormNavigationHelpers(
|
|
227
|
+
const { navigateToPrevious, navigateToNext } = useFormNavigationHelpers(
|
|
228
|
+
props.id,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
const scrollToItem = (item: { props?: FormDropdownItemDescendant }) => {
|
|
232
|
+
const scrollBox = scrollBoxRef.current
|
|
233
|
+
const itemElementRef = item.props?.elementRef
|
|
234
|
+
if (!scrollBox || !itemElementRef) return
|
|
235
|
+
|
|
236
|
+
const contentY = scrollBox.content?.y || 0
|
|
237
|
+
const viewportHeight = scrollBox.viewport?.height || 5
|
|
238
|
+
|
|
239
|
+
// Calculate item position relative to content
|
|
240
|
+
const itemTop = itemElementRef.y - contentY
|
|
241
|
+
|
|
242
|
+
// Scroll so the top of the item is centered in the viewport
|
|
243
|
+
const targetScrollTop = itemTop - viewportHeight / 2
|
|
244
|
+
scrollBox.scrollTo(Math.max(0, targetScrollTop))
|
|
245
|
+
}
|
|
252
246
|
|
|
253
247
|
// Helper to get value for a descendantId
|
|
254
248
|
const getValueForDescendantId = (
|
|
@@ -296,46 +290,26 @@ const DropdownContent = ({
|
|
|
296
290
|
useKeyboard((evt) => {
|
|
297
291
|
if (!isFocused || !isInFocus) return
|
|
298
292
|
|
|
299
|
-
const items = Object.values(descendantsContext.map.current)
|
|
300
|
-
(item) => item.index !== -1
|
|
301
|
-
|
|
293
|
+
const items = Object.values(descendantsContext.map.current)
|
|
294
|
+
.filter((item) => item.index !== -1)
|
|
295
|
+
.sort((a, b) => a.index - b.index)
|
|
302
296
|
const itemCount = items.length
|
|
303
297
|
|
|
304
298
|
if (itemCount > 0) {
|
|
305
299
|
if (evt.name === 'down') {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
prev === visibleEnd &&
|
|
313
|
-
nextIndex < itemCount &&
|
|
314
|
-
nextIndex > prev
|
|
315
|
-
) {
|
|
316
|
-
// Scroll down by one when at the last visible item
|
|
317
|
-
setOffset(offset + 1)
|
|
318
|
-
} else if (nextIndex < prev) {
|
|
319
|
-
// Wrapped to beginning
|
|
320
|
-
setOffset(0)
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return nextIndex
|
|
324
|
-
})
|
|
300
|
+
const nextIndex = (focusedIndex + 1) % itemCount
|
|
301
|
+
setFocusedIndex(nextIndex)
|
|
302
|
+
const nextItem = items[nextIndex]
|
|
303
|
+
if (nextItem) {
|
|
304
|
+
scrollToItem(nextItem)
|
|
305
|
+
}
|
|
325
306
|
} else if (evt.name === 'up') {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
} else if (nextIndex >= offset + itemsPerPage) {
|
|
333
|
-
// Wrapped to end
|
|
334
|
-
setOffset(Math.max(0, itemCount - itemsPerPage))
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return nextIndex
|
|
338
|
-
})
|
|
307
|
+
const nextIndex = (focusedIndex - 1 + itemCount) % itemCount
|
|
308
|
+
setFocusedIndex(nextIndex)
|
|
309
|
+
const nextItem = items[nextIndex]
|
|
310
|
+
if (nextItem) {
|
|
311
|
+
scrollToItem(nextItem)
|
|
312
|
+
}
|
|
339
313
|
} else if (evt.name === 'return' || evt.name === 'space') {
|
|
340
314
|
// Toggle selection of current focused item
|
|
341
315
|
const entries = Object.entries(descendantsContext.map.current)
|
|
@@ -357,21 +331,41 @@ const DropdownContent = ({
|
|
|
357
331
|
} else {
|
|
358
332
|
navigateToNext()
|
|
359
333
|
}
|
|
334
|
+
return
|
|
360
335
|
}
|
|
361
|
-
})
|
|
362
336
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
337
|
+
// Type-ahead search
|
|
338
|
+
if (
|
|
339
|
+
evt.name.length === 1 &&
|
|
340
|
+
/^[a-zA-Z0-9]$/.test(evt.name) &&
|
|
341
|
+
!evt.ctrl &&
|
|
342
|
+
!evt.meta &&
|
|
343
|
+
!evt.option
|
|
344
|
+
) {
|
|
345
|
+
if (typeAheadTimeoutRef.current) {
|
|
346
|
+
clearTimeout(typeAheadTimeoutRef.current)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
typeAheadTextRef.current += evt.name.toLowerCase()
|
|
350
|
+
const searchText = typeAheadTextRef.current
|
|
367
351
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
352
|
+
const matchingItem =
|
|
353
|
+
items.find((item) => {
|
|
354
|
+
return item.props?.title?.toLowerCase().startsWith(searchText)
|
|
355
|
+
}) ||
|
|
356
|
+
items.find((item) => {
|
|
357
|
+
return item.props?.title?.toLowerCase().includes(searchText)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
if (matchingItem) {
|
|
361
|
+
setFocusedIndex(matchingItem.index)
|
|
362
|
+
scrollToItem(matchingItem)
|
|
371
363
|
}
|
|
372
|
-
})
|
|
373
364
|
|
|
374
|
-
|
|
365
|
+
typeAheadTimeoutRef.current = setTimeout(() => {
|
|
366
|
+
typeAheadTextRef.current = ''
|
|
367
|
+
}, 300)
|
|
368
|
+
}
|
|
375
369
|
})
|
|
376
370
|
|
|
377
371
|
// Initialize selected titles from field value only once when descendants are loaded
|
|
@@ -405,27 +399,30 @@ const DropdownContent = ({
|
|
|
405
399
|
const contextValue: FormDropdownContextValue = {
|
|
406
400
|
focusedIndex,
|
|
407
401
|
descendantsContext,
|
|
408
|
-
offset,
|
|
409
|
-
itemsPerPage,
|
|
410
402
|
isFocused,
|
|
411
403
|
handleSelect,
|
|
412
404
|
field,
|
|
413
405
|
props,
|
|
406
|
+
scrollBoxRef,
|
|
414
407
|
}
|
|
415
408
|
|
|
416
409
|
return (
|
|
417
410
|
<FormDropdownDescendantsProvider value={descendantsContext}>
|
|
418
411
|
<FormDropdownContext.Provider value={contextValue}>
|
|
419
412
|
<box ref={elementRef} flexDirection='column'>
|
|
420
|
-
<WithLeftBorder withDiamond isFocused={isFocused}>
|
|
421
|
-
<
|
|
422
|
-
fg={isFocused ? Theme.primary : Theme.text}
|
|
413
|
+
<WithLeftBorder withDiamond isFocused={isFocused} isLoading={focusContext.isLoading}>
|
|
414
|
+
<box
|
|
423
415
|
onMouseDown={() => {
|
|
424
416
|
setFocusedField(props.id)
|
|
425
417
|
}}
|
|
426
418
|
>
|
|
427
|
-
|
|
428
|
-
|
|
419
|
+
<LoadingText
|
|
420
|
+
isLoading={isFocused && focusContext.isLoading}
|
|
421
|
+
color={isFocused ? Theme.primary : Theme.text}
|
|
422
|
+
>
|
|
423
|
+
{props.title || ''}
|
|
424
|
+
</LoadingText>
|
|
425
|
+
</box>
|
|
429
426
|
</WithLeftBorder>
|
|
430
427
|
<WithLeftBorder isFocused={isFocused}>
|
|
431
428
|
<text
|
|
@@ -440,12 +437,27 @@ const DropdownContent = ({
|
|
|
440
437
|
: props.placeholder || 'Select...'}
|
|
441
438
|
</text>
|
|
442
439
|
</WithLeftBorder>
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
440
|
+
|
|
441
|
+
<scrollbox
|
|
442
|
+
ref={scrollBoxRef}
|
|
443
|
+
maxHeight={5}
|
|
444
|
+
flexShrink={1}
|
|
445
|
+
style={{
|
|
446
|
+
rootOptions: {
|
|
447
|
+
flexShrink: 1,
|
|
448
|
+
},
|
|
449
|
+
viewportOptions: {
|
|
450
|
+
flexShrink: 1,
|
|
451
|
+
},
|
|
452
|
+
scrollbarOptions: {
|
|
453
|
+
visible: false,
|
|
454
|
+
},
|
|
455
|
+
}}
|
|
456
|
+
>
|
|
457
|
+
{props.children}
|
|
458
|
+
</scrollbox>
|
|
459
|
+
<WithLeftBorder children={<box />} isFocused={isFocused} />
|
|
460
|
+
|
|
449
461
|
{(fieldState.error || props.error) && (
|
|
450
462
|
<WithLeftBorder isFocused={isFocused}>
|
|
451
463
|
<text fg={Theme.error}>
|
|
@@ -1,151 +1,133 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { useQuery } from '@tanstack/react-query'
|
|
3
3
|
import { Theme } from 'termcast/src/theme'
|
|
4
|
-
import { TextareaRenderable
|
|
5
|
-
import {
|
|
4
|
+
import { TextareaRenderable } from '@opentui/core'
|
|
5
|
+
import { listAllFiles } from '../../utils/file-system'
|
|
6
6
|
import { useKeyboard } from '@opentui/react'
|
|
7
7
|
import { useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
const primary = RGBA.fromHex(Theme.primary)
|
|
11
|
-
const border = RGBA.fromHex(Theme.border)
|
|
12
|
-
|
|
13
|
-
export interface FileAutocompleteProps {
|
|
9
|
+
export interface FileAutocompleteDialogProps {
|
|
14
10
|
onSelect: (path: string) => void
|
|
15
|
-
|
|
16
|
-
onVisibilityChange: (visible: boolean) => void
|
|
11
|
+
onClose: () => void
|
|
17
12
|
inputRef: React.RefObject<TextareaRenderable | null>
|
|
18
|
-
anchorRef: React.RefObject<any>
|
|
19
|
-
searchTrigger: number
|
|
20
13
|
canChooseFiles?: boolean
|
|
21
14
|
canChooseDirectories?: boolean
|
|
22
15
|
initialDirectory?: string
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
|
|
18
|
+
function fuzzyMatch(text: string, query: string): boolean {
|
|
19
|
+
if (!query) return true
|
|
20
|
+
const lowerText = text.toLowerCase()
|
|
21
|
+
const lowerQuery = query.toLowerCase()
|
|
22
|
+
|
|
23
|
+
let queryIndex = 0
|
|
24
|
+
for (let i = 0; i < lowerText.length && queryIndex < lowerQuery.length; i++) {
|
|
25
|
+
if (lowerText[i] === lowerQuery[queryIndex]) {
|
|
26
|
+
queryIndex++
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return queryIndex === lowerQuery.length
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const FileAutocompleteDialog = ({
|
|
26
33
|
onSelect,
|
|
27
|
-
|
|
28
|
-
onVisibilityChange,
|
|
34
|
+
onClose,
|
|
29
35
|
inputRef,
|
|
30
|
-
anchorRef,
|
|
31
|
-
searchTrigger,
|
|
32
36
|
canChooseFiles = true,
|
|
33
37
|
canChooseDirectories = false,
|
|
34
38
|
initialDirectory,
|
|
35
|
-
}:
|
|
39
|
+
}: FileAutocompleteDialogProps): any => {
|
|
36
40
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
41
|
+
const [filter, setFilter] = useState(() => inputRef.current?.plainText || '')
|
|
37
42
|
const isInFocus = useIsInFocus()
|
|
38
43
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
const { basePath, prefix } = inputValue
|
|
42
|
-
? parsePath(inputValue)
|
|
43
|
-
: { basePath: defaultBasePath, prefix: '' }
|
|
44
|
-
|
|
45
|
-
const { data: items = [], isLoading: loading } = useQuery({
|
|
46
|
-
queryKey: ['file-autocomplete', basePath, prefix, searchTrigger, canChooseFiles, canChooseDirectories],
|
|
44
|
+
const { data: allFiles = [], isLoading } = useQuery({
|
|
45
|
+
queryKey: ['file-list', initialDirectory, canChooseFiles, canChooseDirectories],
|
|
47
46
|
queryFn: async () => {
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
47
|
+
// Always include directories in the listing, filter afterwards
|
|
48
|
+
const files = await listAllFiles({
|
|
49
|
+
basePath: initialDirectory || '.',
|
|
50
|
+
maxDepth: 4,
|
|
51
|
+
maxFiles: 500,
|
|
52
|
+
includeDirectories: true,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// Filter based on canChooseFiles/canChooseDirectories
|
|
56
|
+
return files.filter((f) => {
|
|
57
|
+
const isDir = f.endsWith('/')
|
|
58
|
+
if (isDir) {
|
|
59
|
+
return canChooseDirectories
|
|
60
|
+
}
|
|
51
61
|
return canChooseFiles
|
|
52
62
|
})
|
|
53
|
-
setSelectedIndex(0)
|
|
54
|
-
return filtered
|
|
55
63
|
},
|
|
56
|
-
enabled: visible,
|
|
57
64
|
})
|
|
58
65
|
|
|
59
|
-
|
|
66
|
+
// Filter files based on current input
|
|
67
|
+
const filteredFiles = allFiles.filter((file) => fuzzyMatch(file, filter))
|
|
68
|
+
const visibleFiles = filteredFiles.slice(0, 10)
|
|
60
69
|
|
|
61
70
|
useKeyboard((evt) => {
|
|
62
|
-
if (!
|
|
71
|
+
if (!isInFocus) return
|
|
63
72
|
|
|
64
73
|
if (evt.name === 'up') {
|
|
65
|
-
setSelectedIndex((prev) => (prev > 0 ? prev - 1 :
|
|
74
|
+
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : visibleFiles.length - 1))
|
|
66
75
|
} else if (evt.name === 'down') {
|
|
67
|
-
setSelectedIndex((prev) => (prev <
|
|
68
|
-
} else if (evt.name === '
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
value.substring(0, value.lastIndexOf('/') + 1) + selectedItem.name
|
|
76
|
-
|
|
77
|
-
if (selectedItem.isDirectory) {
|
|
78
|
-
const fullPath = newValue + '/'
|
|
79
|
-
inputRef.current?.setText(fullPath)
|
|
80
|
-
inputRef.current!.cursorOffset = fullPath.length
|
|
81
|
-
} else {
|
|
82
|
-
onSelect(newValue)
|
|
83
|
-
onVisibilityChange(false)
|
|
84
|
-
}
|
|
76
|
+
setSelectedIndex((prev) => (prev < visibleFiles.length - 1 ? prev + 1 : 0))
|
|
77
|
+
} else if (evt.name === 'return' || evt.name === 'tab') {
|
|
78
|
+
const selected = visibleFiles[selectedIndex]
|
|
79
|
+
if (selected) {
|
|
80
|
+
const path = selected.endsWith('/') ? selected.slice(0, -1) : selected
|
|
81
|
+
inputRef.current?.setText(path)
|
|
82
|
+
onSelect(path)
|
|
83
|
+
onClose()
|
|
85
84
|
}
|
|
86
|
-
} else if (evt.name === '
|
|
87
|
-
if (
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
value.substring(0, value.lastIndexOf('/') + 1) + selectedItem.name
|
|
92
|
-
onSelect(newValue)
|
|
93
|
-
onVisibilityChange(false)
|
|
85
|
+
} else if (evt.name === 'backspace') {
|
|
86
|
+
if (filter.length > 0) {
|
|
87
|
+
const newFilter = filter.slice(0, -1)
|
|
88
|
+
setFilter(newFilter)
|
|
89
|
+
setSelectedIndex(0)
|
|
94
90
|
}
|
|
91
|
+
} else if (evt.name && evt.name.length === 1 && !evt.ctrl && !evt.meta) {
|
|
92
|
+
// Single character input
|
|
93
|
+
const newFilter = filter + evt.name
|
|
94
|
+
setFilter(newFilter)
|
|
95
|
+
setSelectedIndex(0)
|
|
95
96
|
}
|
|
96
97
|
})
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const anchorElement = anchorRef.current
|
|
101
|
-
if (!anchorElement) return null
|
|
102
|
-
|
|
103
|
-
const maxVisible = 8
|
|
104
|
-
const visibleItems = items.slice(0, maxVisible)
|
|
105
|
-
const displayHeight = visibleItems.length + 1
|
|
106
|
-
|
|
107
|
-
const contentWidth = anchorElement.width - 2
|
|
108
|
-
|
|
109
|
-
const hintText = directoryOnlyMode
|
|
110
|
-
? '↑↓ navigate ⏎ open folder → select folder esc close'
|
|
111
|
-
: '↑↓ navigate ⏎ open/select → select esc close'
|
|
99
|
+
const hintText = '↑↓ navigate ⏎/tab select esc close'
|
|
112
100
|
|
|
113
101
|
return (
|
|
114
|
-
<box
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
width={anchorElement.width}
|
|
119
|
-
height={displayHeight + 2}
|
|
120
|
-
zIndex={1000}
|
|
121
|
-
borderStyle='single'
|
|
122
|
-
borderColor={border}
|
|
123
|
-
backgroundColor={backgroundPanel}
|
|
124
|
-
shouldFill
|
|
125
|
-
>
|
|
126
|
-
<box flexDirection='column' backgroundColor={backgroundPanel} shouldFill>
|
|
127
|
-
{loading ? (
|
|
128
|
-
<text fg={Theme.textMuted}> Loading...</text>
|
|
129
|
-
) : (
|
|
130
|
-
<>
|
|
131
|
-
{visibleItems.map((item, index) => {
|
|
132
|
-
const icon = item.isDirectory ? '📁 ' : '📄 '
|
|
133
|
-
const text = ' ' + icon + item.name
|
|
134
|
-
const padded = text.padEnd(contentWidth, ' ')
|
|
135
|
-
return (
|
|
136
|
-
<text
|
|
137
|
-
key={item.path}
|
|
138
|
-
fg={index === selectedIndex ? Theme.background : Theme.text}
|
|
139
|
-
bg={index === selectedIndex ? Theme.primary : Theme.backgroundPanel}
|
|
140
|
-
>
|
|
141
|
-
{padded}
|
|
142
|
-
</text>
|
|
143
|
-
)
|
|
144
|
-
})}
|
|
145
|
-
<text fg={Theme.textMuted}> {hintText}</text>
|
|
146
|
-
</>
|
|
147
|
-
)}
|
|
102
|
+
<box flexDirection='column' paddingLeft={1} paddingRight={1}>
|
|
103
|
+
<box flexDirection='row'>
|
|
104
|
+
<text fg={Theme.textMuted}>Filter: </text>
|
|
105
|
+
<text fg={Theme.primary}>{filter || '(type to filter)'}</text>
|
|
148
106
|
</box>
|
|
107
|
+
<box height={1} />
|
|
108
|
+
{isLoading ? (
|
|
109
|
+
<text fg={Theme.textMuted}>Loading files...</text>
|
|
110
|
+
) : visibleFiles.length === 0 ? (
|
|
111
|
+
<text fg={Theme.textMuted}>No files found</text>
|
|
112
|
+
) : (
|
|
113
|
+
<>
|
|
114
|
+
{visibleFiles.map((file, index) => {
|
|
115
|
+
const isDir = file.endsWith('/')
|
|
116
|
+
const icon = isDir ? '📁 ' : '📄 '
|
|
117
|
+
return (
|
|
118
|
+
<text
|
|
119
|
+
key={file}
|
|
120
|
+
fg={index === selectedIndex ? Theme.background : Theme.text}
|
|
121
|
+
bg={index === selectedIndex ? Theme.primary : Theme.backgroundPanel}
|
|
122
|
+
>
|
|
123
|
+
{' '}{icon}{file}
|
|
124
|
+
</text>
|
|
125
|
+
)
|
|
126
|
+
})}
|
|
127
|
+
</>
|
|
128
|
+
)}
|
|
129
|
+
<box height={1} />
|
|
130
|
+
<text fg={Theme.textMuted}>{hintText}</text>
|
|
149
131
|
</box>
|
|
150
132
|
)
|
|
151
133
|
}
|