termcast 1.3.9
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/action-utils.d.ts +25 -0
- package/dist/action-utils.d.ts.map +1 -0
- package/dist/action-utils.js +209 -0
- package/dist/action-utils.js.map +1 -0
- 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/ai.d.ts +104 -0
- package/dist/apis/ai.d.ts.map +1 -0
- package/dist/apis/ai.js +135 -0
- package/dist/apis/ai.js.map +1 -0
- package/dist/apis/cache.d.ts +84 -0
- package/dist/apis/cache.d.ts.map +1 -0
- package/dist/apis/cache.js +307 -0
- package/dist/apis/cache.js.map +1 -0
- package/dist/apis/cache.test.d.ts +2 -0
- package/dist/apis/cache.test.d.ts.map +1 -0
- package/dist/apis/cache.test.js +246 -0
- package/dist/apis/cache.test.js.map +1 -0
- package/dist/apis/clipboard.d.ts +36 -0
- package/dist/apis/clipboard.d.ts.map +1 -0
- package/dist/apis/clipboard.js +154 -0
- package/dist/apis/clipboard.js.map +1 -0
- package/dist/apis/environment.d.ts +63 -0
- package/dist/apis/environment.d.ts.map +1 -0
- package/dist/apis/environment.js +189 -0
- package/dist/apis/environment.js.map +1 -0
- package/dist/apis/hud.d.ts +7 -0
- package/dist/apis/hud.d.ts.map +1 -0
- package/dist/apis/hud.js +45 -0
- package/dist/apis/hud.js.map +1 -0
- package/dist/apis/localstorage.d.ts +13 -0
- package/dist/apis/localstorage.d.ts.map +1 -0
- package/dist/apis/localstorage.js +190 -0
- package/dist/apis/localstorage.js.map +1 -0
- package/dist/apis/localstorage.test.d.ts +2 -0
- package/dist/apis/localstorage.test.d.ts.map +1 -0
- package/dist/apis/localstorage.test.js +131 -0
- package/dist/apis/localstorage.test.js.map +1 -0
- package/dist/apis/oauth.d.ts +142 -0
- package/dist/apis/oauth.d.ts.map +1 -0
- package/dist/apis/oauth.js +551 -0
- package/dist/apis/oauth.js.map +1 -0
- package/dist/apis/preferences.d.ts +23 -0
- package/dist/apis/preferences.d.ts.map +1 -0
- package/dist/apis/preferences.js +105 -0
- package/dist/apis/preferences.js.map +1 -0
- package/dist/apis/toast.d.ts +81 -0
- package/dist/apis/toast.d.ts.map +1 -0
- package/dist/apis/toast.js +275 -0
- package/dist/apis/toast.js.map +1 -0
- package/dist/apis/toast.test.d.ts +2 -0
- package/dist/apis/toast.test.d.ts.map +1 -0
- package/dist/apis/toast.test.js +67 -0
- package/dist/apis/toast.test.js.map +1 -0
- package/dist/apis/window.d.ts +12 -0
- package/dist/apis/window.d.ts.map +1 -0
- package/dist/apis/window.js +47 -0
- package/dist/apis/window.js.map +1 -0
- package/dist/build.d.ts +15 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +213 -0
- package/dist/build.js.map +1 -0
- package/dist/build.test.d.ts +2 -0
- package/dist/build.test.d.ts.map +1 -0
- package/dist/build.test.js +73 -0
- package/dist/build.test.js.map +1 -0
- 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/cache.test.d.ts +2 -0
- package/dist/cache.test.d.ts.map +1 -0
- package/dist/cache.test.js +246 -0
- package/dist/cache.test.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +278 -0
- package/dist/cli.js.map +1 -0
- 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/colors.d.ts +15 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +13 -0
- package/dist/colors.js.map +1 -0
- package/dist/components/actions.d.ts +120 -0
- package/dist/components/actions.d.ts.map +1 -0
- package/dist/components/actions.js +371 -0
- package/dist/components/actions.js.map +1 -0
- package/dist/components/alert.d.ts +25 -0
- package/dist/components/alert.d.ts.map +1 -0
- package/dist/components/alert.js +99 -0
- package/dist/components/alert.js.map +1 -0
- package/dist/components/detail.d.ts +65 -0
- package/dist/components/detail.d.ts.map +1 -0
- package/dist/components/detail.js +147 -0
- package/dist/components/detail.js.map +1 -0
- package/dist/components/dropdown.d.ts +40 -0
- package/dist/components/dropdown.d.ts.map +1 -0
- package/dist/components/dropdown.js +202 -0
- package/dist/components/dropdown.js.map +1 -0
- package/dist/components/extension-preferences.d.ts +8 -0
- package/dist/components/extension-preferences.d.ts.map +1 -0
- package/dist/components/extension-preferences.js +139 -0
- package/dist/components/extension-preferences.js.map +1 -0
- package/dist/components/form/assign-components.d.ts +2 -0
- package/dist/components/form/assign-components.d.ts.map +1 -0
- package/dist/components/form/assign-components.js +22 -0
- package/dist/components/form/assign-components.js.map +1 -0
- package/dist/components/form/checkbox.d.ts +7 -0
- package/dist/components/form/checkbox.d.ts.map +1 -0
- package/dist/components/form/checkbox.js +46 -0
- package/dist/components/form/checkbox.js.map +1 -0
- package/dist/components/form/date-picker.d.ts +18 -0
- package/dist/components/form/date-picker.d.ts.map +1 -0
- package/dist/components/form/date-picker.js +69 -0
- package/dist/components/form/date-picker.js.map +1 -0
- package/dist/components/form/description.d.ts +7 -0
- package/dist/components/form/description.d.ts.map +1 -0
- package/dist/components/form/description.js +8 -0
- package/dist/components/form/description.js.map +1 -0
- package/dist/components/form/dropdown.d.ts +25 -0
- package/dist/components/form/dropdown.d.ts.map +1 -0
- package/dist/components/form/dropdown.js +286 -0
- package/dist/components/form/dropdown.js.map +1 -0
- package/dist/components/form/file-autocomplete.d.ts +12 -0
- package/dist/components/form/file-autocomplete.d.ts.map +1 -0
- package/dist/components/form/file-autocomplete.js +84 -0
- package/dist/components/form/file-autocomplete.js.map +1 -0
- package/dist/components/form/file-picker.d.ts +31 -0
- package/dist/components/form/file-picker.d.ts.map +1 -0
- package/dist/components/form/file-picker.js +113 -0
- package/dist/components/form/file-picker.js.map +1 -0
- package/dist/components/form/form-end.d.ts +2 -0
- package/dist/components/form/form-end.d.ts.map +1 -0
- package/dist/components/form/form-end.js +6 -0
- package/dist/components/form/form-end.js.map +1 -0
- 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 +46 -0
- package/dist/components/form/index.d.ts.map +1 -0
- package/dist/components/form/index.js +106 -0
- package/dist/components/form/index.js.map +1 -0
- package/dist/components/form/password-field.d.ts +7 -0
- package/dist/components/form/password-field.d.ts.map +1 -0
- package/dist/components/form/password-field.js +34 -0
- package/dist/components/form/password-field.js.map +1 -0
- package/dist/components/form/separator.d.ts +2 -0
- package/dist/components/form/separator.d.ts.map +1 -0
- package/dist/components/form/separator.js +8 -0
- package/dist/components/form/separator.js.map +1 -0
- package/dist/components/form/tagpicker.d.ts +134 -0
- package/dist/components/form/tagpicker.d.ts.map +1 -0
- package/dist/components/form/tagpicker.js +79 -0
- package/dist/components/form/tagpicker.js.map +1 -0
- package/dist/components/form/text-area.d.ts +8 -0
- package/dist/components/form/text-area.d.ts.map +1 -0
- package/dist/components/form/text-area.js +26 -0
- package/dist/components/form/text-area.js.map +1 -0
- package/dist/components/form/text-field.d.ts +7 -0
- package/dist/components/form/text-field.d.ts.map +1 -0
- package/dist/components/form/text-field.js +28 -0
- package/dist/components/form/text-field.js.map +1 -0
- package/dist/components/form/types.d.ts +43 -0
- package/dist/components/form/types.d.ts.map +1 -0
- package/dist/components/form/types.js +2 -0
- package/dist/components/form/types.js.map +1 -0
- 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/use-form-navigation.d.ts +8 -0
- package/dist/components/form/use-form-navigation.d.ts.map +1 -0
- package/dist/components/form/use-form-navigation.js +58 -0
- package/dist/components/form/use-form-navigation.js.map +1 -0
- package/dist/components/form/with-left-border.d.ts +17 -0
- package/dist/components/form/with-left-border.d.ts.map +1 -0
- package/dist/components/form/with-left-border.js +10 -0
- package/dist/components/form/with-left-border.js.map +1 -0
- package/dist/components/icon.d.ts +9 -0
- package/dist/components/icon.d.ts.map +1 -0
- package/dist/components/icon.js +485 -0
- package/dist/components/icon.js.map +1 -0
- package/dist/components/image.d.ts +19 -0
- package/dist/components/image.d.ts.map +1 -0
- package/dist/components/image.js +32 -0
- package/dist/components/image.js.map +1 -0
- package/dist/components/list.d.ts +234 -0
- package/dist/components/list.d.ts.map +1 -0
- package/dist/components/list.js +667 -0
- package/dist/components/list.js.map +1 -0
- package/dist/components/loading-bar.d.ts +8 -0
- package/dist/components/loading-bar.d.ts.map +1 -0
- package/dist/components/loading-bar.js +107 -0
- package/dist/components/loading-bar.js.map +1 -0
- package/dist/components/menubar-extra.d.ts +68 -0
- package/dist/components/menubar-extra.d.ts.map +1 -0
- package/dist/components/menubar-extra.js +39 -0
- package/dist/components/menubar-extra.js.map +1 -0
- package/dist/descendants.d.ts +25 -0
- package/dist/descendants.d.ts.map +1 -0
- package/dist/descendants.js +81 -0
- package/dist/descendants.js.map +1 -0
- 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/e2e-node.d.ts +63 -0
- package/dist/e2e-node.d.ts.map +1 -0
- package/dist/e2e-node.js +255 -0
- package/dist/e2e-node.js.map +1 -0
- package/dist/e2e.d.ts +39 -0
- package/dist/e2e.d.ts.map +1 -0
- package/dist/e2e.js +127 -0
- package/dist/e2e.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/action-show-in-finder.d.ts +2 -0
- package/dist/examples/action-show-in-finder.d.ts.map +1 -0
- package/dist/examples/action-show-in-finder.js +13 -0
- package/dist/examples/action-show-in-finder.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/environment-test.d.ts +2 -0
- package/dist/examples/environment-test.d.ts.map +1 -0
- package/dist/examples/environment-test.js +28 -0
- package/dist/examples/environment-test.js.map +1 -0
- package/dist/examples/error-boundary.d.ts +6 -0
- package/dist/examples/error-boundary.d.ts.map +1 -0
- package/dist/examples/error-boundary.js +67 -0
- package/dist/examples/error-boundary.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.d.ts +2 -0
- package/dist/examples/form-basic.d.ts.map +1 -0
- package/dist/examples/form-basic.js +21 -0
- package/dist/examples/form-basic.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 +995 -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.d.ts +2 -0
- package/dist/examples/form-dropdown.d.ts.map +1 -0
- package/dist/examples/form-dropdown.js +13 -0
- package/dist/examples/form-dropdown.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 +722 -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-tagpicker.d.ts +2 -0
- package/dist/examples/form-tagpicker.d.ts.map +1 -0
- package/dist/examples/form-tagpicker.js +13 -0
- package/dist/examples/form-tagpicker.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 +491 -0
- package/dist/examples/form-tagpicker.vitest.js.map +1 -0
- package/dist/examples/internal/descendants-filtering.d.ts +2 -0
- package/dist/examples/internal/descendants-filtering.d.ts.map +1 -0
- package/dist/examples/internal/descendants-filtering.js +144 -0
- package/dist/examples/internal/descendants-filtering.js.map +1 -0
- package/dist/examples/internal/descendants.d.ts +2 -0
- package/dist/examples/internal/descendants.d.ts.map +1 -0
- package/dist/examples/internal/descendants.js +134 -0
- package/dist/examples/internal/descendants.js.map +1 -0
- 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/scrollbox-demo.d.ts +2 -0
- package/dist/examples/internal/scrollbox-demo.d.ts.map +1 -0
- package/dist/examples/internal/scrollbox-demo.js +104 -0
- package/dist/examples/internal/scrollbox-demo.js.map +1 -0
- package/dist/examples/internal/simple-dialog.d.ts +2 -0
- package/dist/examples/internal/simple-dialog.d.ts.map +1 -0
- package/dist/examples/internal/simple-dialog.js +43 -0
- package/dist/examples/internal/simple-dialog.js.map +1 -0
- package/dist/examples/internal/text-stacking.d.ts +2 -0
- package/dist/examples/internal/text-stacking.d.ts.map +1 -0
- package/dist/examples/internal/text-stacking.js +53 -0
- package/dist/examples/internal/text-stacking.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-dropdown-default.d.ts +2 -0
- package/dist/examples/list-dropdown-default.d.ts.map +1 -0
- package/dist/examples/list-dropdown-default.js +14 -0
- package/dist/examples/list-dropdown-default.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 +164 -0
- package/dist/examples/list-dropdown-default.vitest.js.map +1 -0
- package/dist/examples/list-fetch-data.d.ts +2 -0
- package/dist/examples/list-fetch-data.d.ts.map +1 -0
- package/dist/examples/list-fetch-data.js +87 -0
- package/dist/examples/list-fetch-data.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 +103 -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-with-detail.d.ts +2 -0
- package/dist/examples/list-with-detail.d.ts.map +1 -0
- package/dist/examples/list-with-detail.js +94 -0
- package/dist/examples/list-with-detail.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 +438 -0
- package/dist/examples/list-with-detail.vitest.js.map +1 -0
- package/dist/examples/list-with-dropdown.d.ts +2 -0
- package/dist/examples/list-with-dropdown.d.ts.map +1 -0
- package/dist/examples/list-with-dropdown.js +43 -0
- package/dist/examples/list-with-dropdown.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 +297 -0
- package/dist/examples/list-with-dropdown.vitest.js.map +1 -0
- package/dist/examples/list-with-sections.d.ts +2 -0
- package/dist/examples/list-with-sections.d.ts.map +1 -0
- package/dist/examples/list-with-sections.js +67 -0
- package/dist/examples/list-with-sections.js.map +1 -0
- 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 +441 -0
- package/dist/examples/list-with-sections.vitest.js.map +1 -0
- package/dist/examples/miscellaneous.d.ts +2 -0
- package/dist/examples/miscellaneous.d.ts.map +1 -0
- package/dist/examples/miscellaneous.js +313 -0
- package/dist/examples/miscellaneous.js.map +1 -0
- package/dist/examples/nested-navigation.d.ts +2 -0
- package/dist/examples/nested-navigation.d.ts.map +1 -0
- package/dist/examples/nested-navigation.js +35 -0
- package/dist/examples/nested-navigation.js.map +1 -0
- package/dist/examples/preferences-test.d.ts +2 -0
- package/dist/examples/preferences-test.d.ts.map +1 -0
- package/dist/examples/preferences-test.js +43 -0
- package/dist/examples/preferences-test.js.map +1 -0
- package/dist/examples/simple-dropdown.d.ts +2 -0
- package/dist/examples/simple-dropdown.d.ts.map +1 -0
- package/dist/examples/simple-dropdown.js +15 -0
- package/dist/examples/simple-dropdown.js.map +1 -0
- package/dist/examples/simple-file-picker.d.ts +2 -0
- package/dist/examples/simple-file-picker.d.ts.map +1 -0
- package/dist/examples/simple-file-picker.js +21 -0
- package/dist/examples/simple-file-picker.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 +277 -0
- package/dist/examples/simple-file-picker.vitest.js.map +1 -0
- package/dist/examples/simple-grid.d.ts +2 -0
- package/dist/examples/simple-grid.d.ts.map +1 -0
- package/dist/examples/simple-grid.js +51 -0
- package/dist/examples/simple-grid.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 +498 -0
- package/dist/examples/simple-grid.vitest.js.map +1 -0
- package/dist/examples/simple-hud.d.ts +2 -0
- package/dist/examples/simple-hud.d.ts.map +1 -0
- package/dist/examples/simple-hud.js +18 -0
- package/dist/examples/simple-hud.js.map +1 -0
- package/dist/examples/simple-list-search.d.ts +2 -0
- package/dist/examples/simple-list-search.d.ts.map +1 -0
- package/dist/examples/simple-list-search.js +26 -0
- package/dist/examples/simple-list-search.js.map +1 -0
- package/dist/examples/simple-list.d.ts +2 -0
- package/dist/examples/simple-list.d.ts.map +1 -0
- package/dist/examples/simple-list.js +50 -0
- package/dist/examples/simple-list.js.map +1 -0
- package/dist/examples/simple-navigation.d.ts +2 -0
- package/dist/examples/simple-navigation.d.ts.map +1 -0
- package/dist/examples/simple-navigation.js +36 -0
- package/dist/examples/simple-navigation.js.map +1 -0
- 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 +522 -0
- package/dist/examples/simple-navigation.vitest.js.map +1 -0
- package/dist/examples/store.d.ts +2 -0
- package/dist/examples/store.d.ts.map +1 -0
- package/dist/examples/store.js +5 -0
- package/dist/examples/store.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 +52 -0
- package/dist/examples/store.vitest.js.map +1 -0
- package/dist/examples/tanstack-demo.d.ts +2 -0
- package/dist/examples/tanstack-demo.d.ts.map +1 -0
- package/dist/examples/tanstack-demo.js +51 -0
- package/dist/examples/tanstack-demo.js.map +1 -0
- package/dist/examples/use-promise-demo.d.ts +2 -0
- package/dist/examples/use-promise-demo.d.ts.map +1 -0
- package/dist/examples/use-promise-demo.js +45 -0
- package/dist/examples/use-promise-demo.js.map +1 -0
- package/dist/extensions/dev.d.ts +7 -0
- package/dist/extensions/dev.d.ts.map +1 -0
- package/dist/extensions/dev.js +124 -0
- package/dist/extensions/dev.js.map +1 -0
- package/dist/extensions/home.d.ts +8 -0
- package/dist/extensions/home.d.ts.map +1 -0
- package/dist/extensions/home.js +184 -0
- package/dist/extensions/home.js.map +1 -0
- package/dist/extensions/store.d.ts +6 -0
- package/dist/extensions/store.d.ts.map +1 -0
- package/dist/extensions/store.js +203 -0
- package/dist/extensions/store.js.map +1 -0
- package/dist/globals.d.ts +9 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +21 -0
- package/dist/globals.js.map +1 -0
- 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/hooks/hooks.test.d.ts +2 -0
- package/dist/hooks/hooks.test.d.ts.map +1 -0
- package/dist/hooks/hooks.test.js +37 -0
- package/dist/hooks/hooks.test.js.map +1 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/use-action-panel.d.ts +16 -0
- package/dist/hooks/use-action-panel.d.ts.map +1 -0
- package/dist/hooks/use-action-panel.js +19 -0
- package/dist/hooks/use-action-panel.js.map +1 -0
- package/dist/hooks/use-id.d.ts +9 -0
- package/dist/hooks/use-id.d.ts.map +1 -0
- package/dist/hooks/use-id.js +17 -0
- package/dist/hooks/use-id.js.map +1 -0
- package/dist/hooks/use-unstable-ai.d.ts +9 -0
- package/dist/hooks/use-unstable-ai.d.ts.map +1 -0
- package/dist/hooks/use-unstable-ai.js +13 -0
- package/dist/hooks/use-unstable-ai.js.map +1 -0
- package/dist/hooks.d.ts +10 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +20 -0
- package/dist/hooks.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 +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/date-picker-widget.d.ts +9 -0
- package/dist/internal/date-picker-widget.d.ts.map +1 -0
- package/dist/internal/date-picker-widget.js +380 -0
- package/dist/internal/date-picker-widget.js.map +1 -0
- package/dist/internal/dialog.d.ts +21 -0
- package/dist/internal/dialog.d.ts.map +1 -0
- package/dist/internal/dialog.js +122 -0
- package/dist/internal/dialog.js.map +1 -0
- package/dist/internal/error-handler.d.ts +2 -0
- package/dist/internal/error-handler.d.ts.map +1 -0
- package/dist/internal/error-handler.js +29 -0
- package/dist/internal/error-handler.js.map +1 -0
- package/dist/internal/focus-context.d.ts +10 -0
- package/dist/internal/focus-context.d.ts.map +1 -0
- package/dist/internal/focus-context.js +12 -0
- package/dist/internal/focus-context.js.map +1 -0
- package/dist/internal/navigation.d.ts +18 -0
- package/dist/internal/navigation.d.ts.map +1 -0
- package/dist/internal/navigation.js +83 -0
- package/dist/internal/navigation.js.map +1 -0
- package/dist/internal/providers.d.ts +8 -0
- package/dist/internal/providers.d.ts.map +1 -0
- package/dist/internal/providers.js +262 -0
- package/dist/internal/providers.js.map +1 -0
- 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/localstorage.test.d.ts +2 -0
- package/dist/localstorage.test.d.ts.map +1 -0
- package/dist/localstorage.test.js +131 -0
- package/dist/localstorage.test.js.map +1 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +70 -0
- package/dist/logger.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/package-json.d.ts +84 -0
- package/dist/package-json.d.ts.map +1 -0
- package/dist/package-json.js +77 -0
- package/dist/package-json.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/preload.d.ts +2 -0
- package/dist/preload.d.ts.map +1 -0
- package/dist/preload.js +28 -0
- package/dist/preload.js.map +1 -0
- package/dist/state.d.ts +26 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +18 -0
- package/dist/state.js.map +1 -0
- package/dist/store-api/download.d.ts +8 -0
- package/dist/store-api/download.d.ts.map +1 -0
- package/dist/store-api/download.js +37 -0
- package/dist/store-api/download.js.map +1 -0
- package/dist/store-api/download.test.d.ts +2 -0
- package/dist/store-api/download.test.d.ts.map +1 -0
- package/dist/store-api/download.test.js +36 -0
- package/dist/store-api/download.test.js.map +1 -0
- package/dist/store-api/extension.d.ts +87 -0
- package/dist/store-api/extension.d.ts.map +1 -0
- package/dist/store-api/extension.js +23 -0
- package/dist/store-api/extension.js.map +1 -0
- package/dist/store-api/extension.test.d.ts +2 -0
- package/dist/store-api/extension.test.d.ts.map +1 -0
- package/dist/store-api/extension.test.js +22 -0
- package/dist/store-api/extension.test.js.map +1 -0
- package/dist/store-api/search.d.ts +101 -0
- package/dist/store-api/search.d.ts.map +1 -0
- package/dist/store-api/search.js +29 -0
- package/dist/store-api/search.js.map +1 -0
- package/dist/store-api/search.test.d.ts +2 -0
- package/dist/store-api/search.test.d.ts.map +1 -0
- package/dist/store-api/search.test.js +45 -0
- package/dist/store-api/search.test.js.map +1 -0
- 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/theme.d.ts +20 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +26 -0
- package/dist/theme.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 +11 -0
- package/dist/utils/file-system.d.ts.map +1 -0
- package/dist/utils/file-system.js +66 -0
- package/dist/utils/file-system.js.map +1 -0
- package/dist/utils.d.ts +234 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +473 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.test.d.ts +2 -0
- package/dist/utils.test.d.ts.map +1 -0
- package/dist/utils.test.js +152 -0
- package/dist/utils.test.js.map +1 -0
- 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 +56 -0
- package/src/action-utils.tsx +207 -0
- package/src/apis/ai.tsx +177 -0
- package/src/apis/cache.test.ts +311 -0
- package/src/apis/cache.tsx +394 -0
- package/src/apis/clipboard.tsx +200 -0
- package/src/apis/environment.tsx +239 -0
- package/src/apis/hud.tsx +86 -0
- package/src/apis/localstorage.test.ts +164 -0
- package/src/apis/localstorage.tsx +215 -0
- package/src/apis/oauth.tsx +744 -0
- package/src/apis/preferences.tsx +140 -0
- package/src/apis/toast.tsx +388 -0
- package/src/apis/window.tsx +61 -0
- package/src/build.test.tsx +78 -0
- package/src/build.tsx +273 -0
- package/src/cli.tsx +328 -0
- package/src/colors.tsx +15 -0
- package/src/components/actions.tsx +718 -0
- package/src/components/alert.tsx +205 -0
- package/src/components/detail.tsx +359 -0
- package/src/components/dropdown.tsx +438 -0
- package/src/components/extension-preferences.tsx +269 -0
- package/src/components/form/assign-components.tsx +22 -0
- package/src/components/form/checkbox.tsx +96 -0
- package/src/components/form/date-picker.tsx +125 -0
- package/src/components/form/description.tsx +30 -0
- package/src/components/form/dropdown.tsx +512 -0
- package/src/components/form/file-autocomplete.tsx +141 -0
- package/src/components/form/file-picker.tsx +233 -0
- package/src/components/form/form-end.tsx +6 -0
- package/src/components/form/index.tsx +242 -0
- package/src/components/form/password-field.tsx +87 -0
- package/src/components/form/separator.tsx +15 -0
- package/src/components/form/tagpicker.tsx +245 -0
- package/src/components/form/text-area.tsx +82 -0
- package/src/components/form/text-field.tsx +82 -0
- package/src/components/form/types.tsx +49 -0
- package/src/components/form/use-form-navigation.tsx +65 -0
- package/src/components/form/with-left-border.tsx +53 -0
- package/src/components/icon.tsx +496 -0
- package/src/components/image.tsx +54 -0
- package/src/components/list.tsx +1564 -0
- package/src/components/loading-bar.tsx +141 -0
- package/src/components/menubar-extra.tsx +181 -0
- package/src/descendants.tsx +134 -0
- package/src/e2e-node.tsx +303 -0
- package/src/e2e.tsx +147 -0
- package/src/examples/action-show-in-finder.tsx +80 -0
- package/src/examples/environment-test.tsx +50 -0
- package/src/examples/error-boundary.tsx +217 -0
- package/src/examples/form-basic.tsx +122 -0
- package/src/examples/form-basic.vitest.tsx +1035 -0
- package/src/examples/form-dropdown.tsx +102 -0
- package/src/examples/form-dropdown.vitest.tsx +758 -0
- package/src/examples/form-tagpicker.tsx +66 -0
- package/src/examples/form-tagpicker.vitest.tsx +523 -0
- package/src/examples/internal/descendants-filtering.tsx +223 -0
- package/src/examples/internal/descendants.tsx +208 -0
- package/src/examples/internal/scrollbox-demo.tsx +149 -0
- package/src/examples/internal/simple-dialog.tsx +124 -0
- package/src/examples/internal/text-stacking.tsx +90 -0
- package/src/examples/list-dropdown-default.tsx +69 -0
- package/src/examples/list-dropdown-default.vitest.tsx +187 -0
- package/src/examples/list-fetch-data.tsx +123 -0
- package/src/examples/list-fetch-data.vitest.tsx +110 -0
- package/src/examples/list-with-detail.tsx +161 -0
- package/src/examples/list-with-detail.vitest.tsx +468 -0
- package/src/examples/list-with-dropdown.tsx +97 -0
- package/src/examples/list-with-dropdown.vitest.tsx +324 -0
- package/src/examples/list-with-sections.tsx +196 -0
- package/src/examples/list-with-sections.vitest.tsx +479 -0
- package/src/examples/miscellaneous.tsx +780 -0
- package/src/examples/nested-navigation.tsx +118 -0
- package/src/examples/preferences-test.tsx +82 -0
- package/src/examples/simple-dropdown.tsx +95 -0
- package/src/examples/simple-file-picker.tsx +75 -0
- package/src/examples/simple-file-picker.vitest.tsx +306 -0
- package/src/examples/simple-grid.tsx +149 -0
- package/src/examples/simple-grid.vitest.tsx +535 -0
- package/src/examples/simple-hud.tsx +60 -0
- package/src/examples/simple-list-search.tsx +93 -0
- package/src/examples/simple-list.tsx +149 -0
- package/src/examples/simple-navigation.tsx +89 -0
- package/src/examples/simple-navigation.vitest.tsx +571 -0
- package/src/examples/store.tsx +4 -0
- package/src/examples/store.vitest.tsx +59 -0
- package/src/examples/tanstack-demo.tsx +104 -0
- package/src/examples/use-promise-demo.tsx +96 -0
- package/src/extensions/dev.tsx +215 -0
- package/src/extensions/home.tsx +332 -0
- package/src/extensions/store.tsx +375 -0
- package/src/globals.ts +34 -0
- package/src/hooks/index.tsx +8 -0
- package/src/hooks/use-action-panel.tsx +28 -0
- package/src/hooks/use-id.tsx +19 -0
- package/src/hooks/use-unstable-ai.tsx +15 -0
- package/src/hooks.tsx +22 -0
- package/src/index.tsx +240 -0
- package/src/internal/date-picker-widget.tsx +500 -0
- package/src/internal/dialog.tsx +202 -0
- package/src/internal/error-handler.tsx +32 -0
- package/src/internal/focus-context.tsx +23 -0
- package/src/internal/navigation.tsx +149 -0
- package/src/internal/providers.tsx +430 -0
- package/src/logger.tsx +84 -0
- package/src/package-json.tsx +197 -0
- package/src/preload.tsx +32 -0
- package/src/state.tsx +49 -0
- package/src/store-api/download.test.tsx +38 -0
- package/src/store-api/download.tsx +52 -0
- package/src/store-api/extension.test.tsx +24 -0
- package/src/store-api/extension.tsx +123 -0
- package/src/store-api/search.test.tsx +50 -0
- package/src/store-api/search.tsx +146 -0
- package/src/theme.tsx +31 -0
- package/src/utils/file-system.ts +87 -0
- package/src/utils.test.tsx +204 -0
- package/src/utils.tsx +657 -0
|
@@ -0,0 +1,1564 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
ReactNode,
|
|
3
|
+
ReactElement,
|
|
4
|
+
Children,
|
|
5
|
+
isValidElement,
|
|
6
|
+
useState,
|
|
7
|
+
useEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
Fragment,
|
|
10
|
+
useMemo,
|
|
11
|
+
useLayoutEffect,
|
|
12
|
+
createContext,
|
|
13
|
+
useContext,
|
|
14
|
+
} from 'react'
|
|
15
|
+
import { TextAttributes } from '@opentui/core'
|
|
16
|
+
import { useKeyboard } from '@opentui/react'
|
|
17
|
+
import { logger } from 'termcast/src/logger'
|
|
18
|
+
import { Theme } from 'termcast/src/theme'
|
|
19
|
+
import { Action, ActionPanel } from 'termcast/src/components/actions'
|
|
20
|
+
import { InFocus, useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
21
|
+
import { CommonProps } from 'termcast/src/utils'
|
|
22
|
+
import { useStore } from 'termcast/src/state'
|
|
23
|
+
import { useDialog } from 'termcast/src/internal/dialog'
|
|
24
|
+
import { createDescendants } from 'termcast/src/descendants'
|
|
25
|
+
import { LoadingBar } from 'termcast/src/components/loading-bar'
|
|
26
|
+
import { useNavigationPending } from 'termcast/src/internal/navigation'
|
|
27
|
+
|
|
28
|
+
interface ActionsInterface {
|
|
29
|
+
actions?: ReactNode
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function ListFooter(): any {
|
|
33
|
+
const toast = useStore((state) => state.toast)
|
|
34
|
+
|
|
35
|
+
if (toast) {
|
|
36
|
+
return (
|
|
37
|
+
<box
|
|
38
|
+
border={false}
|
|
39
|
+
style={{
|
|
40
|
+
paddingLeft: 1,
|
|
41
|
+
paddingRight: 1,
|
|
42
|
+
paddingTop: 1,
|
|
43
|
+
marginTop: 1,
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
{toast}
|
|
47
|
+
</box>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<box
|
|
53
|
+
border={false}
|
|
54
|
+
style={{
|
|
55
|
+
paddingLeft: 1,
|
|
56
|
+
paddingRight: 1,
|
|
57
|
+
paddingTop: 1,
|
|
58
|
+
marginTop: 1,
|
|
59
|
+
flexDirection: 'row',
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<text fg={Theme.text} attributes={TextAttributes.BOLD}>
|
|
63
|
+
↵
|
|
64
|
+
</text>
|
|
65
|
+
<text fg={Theme.textMuted}> select</text>
|
|
66
|
+
<text fg={Theme.text} attributes={TextAttributes.BOLD}>
|
|
67
|
+
{' '}↑↓
|
|
68
|
+
</text>
|
|
69
|
+
<text fg={Theme.textMuted}> navigate</text>
|
|
70
|
+
<text fg={Theme.text} attributes={TextAttributes.BOLD}>
|
|
71
|
+
{' '}^k
|
|
72
|
+
</text>
|
|
73
|
+
<text fg={Theme.textMuted}> actions</text>
|
|
74
|
+
</box>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface NavigationChildInterface {
|
|
79
|
+
navigationTitle?: string
|
|
80
|
+
isLoading?: boolean
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface SearchBarInterface {
|
|
84
|
+
filtering?: boolean | { keepSectionOrder: boolean }
|
|
85
|
+
isLoading?: boolean
|
|
86
|
+
onSearchTextChange?: (newValue: string) => void
|
|
87
|
+
searchBarPlaceholder?: string
|
|
88
|
+
throttle?: boolean
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface PaginationInterface {
|
|
92
|
+
pagination?: {
|
|
93
|
+
pageSize: number
|
|
94
|
+
hasMore: boolean
|
|
95
|
+
onLoadMore: () => void
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type Color = string
|
|
100
|
+
|
|
101
|
+
export namespace Image {
|
|
102
|
+
export type ImageLike = string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type ItemAccessory =
|
|
106
|
+
| {
|
|
107
|
+
text?:
|
|
108
|
+
| string
|
|
109
|
+
| null
|
|
110
|
+
| {
|
|
111
|
+
value: string | null
|
|
112
|
+
color?: Color
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
| {
|
|
116
|
+
date?:
|
|
117
|
+
| Date
|
|
118
|
+
| null
|
|
119
|
+
| {
|
|
120
|
+
value: Date | null
|
|
121
|
+
color?: Color
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
| {
|
|
125
|
+
tag?:
|
|
126
|
+
| string
|
|
127
|
+
| {
|
|
128
|
+
value: string
|
|
129
|
+
color?: Color
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
| {
|
|
133
|
+
icon?: Image.ImageLike | null
|
|
134
|
+
text?: string | null
|
|
135
|
+
tooltip?: string | null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface ItemProps extends ActionsInterface, CommonProps {
|
|
139
|
+
id?: string
|
|
140
|
+
title:
|
|
141
|
+
| string
|
|
142
|
+
| {
|
|
143
|
+
value: string
|
|
144
|
+
tooltip?: string | null
|
|
145
|
+
}
|
|
146
|
+
subtitle?:
|
|
147
|
+
| string
|
|
148
|
+
| {
|
|
149
|
+
value?: string | null
|
|
150
|
+
tooltip?: string | null
|
|
151
|
+
}
|
|
152
|
+
keywords?: string[]
|
|
153
|
+
icon?:
|
|
154
|
+
| Image.ImageLike
|
|
155
|
+
| {
|
|
156
|
+
value: Image.ImageLike | null
|
|
157
|
+
tooltip: string
|
|
158
|
+
}
|
|
159
|
+
accessories?: ItemAccessory[]
|
|
160
|
+
detail?: ReactElement<DetailProps>
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface DetailProps extends CommonProps {
|
|
164
|
+
isLoading?: boolean
|
|
165
|
+
markdown?: string
|
|
166
|
+
metadata?: ReactElement<MetadataProps> | null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface MetadataProps extends CommonProps {
|
|
170
|
+
children?: ReactNode
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface DropdownItemProps extends CommonProps {
|
|
174
|
+
value: string
|
|
175
|
+
title: string
|
|
176
|
+
icon?: Image.ImageLike | null
|
|
177
|
+
keywords?: string[]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface DropdownSectionProps extends CommonProps {
|
|
181
|
+
children?: ReactNode
|
|
182
|
+
title?: string
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface DropdownProps extends SearchBarInterface, CommonProps {
|
|
186
|
+
id?: string
|
|
187
|
+
tooltip: string
|
|
188
|
+
placeholder?: string
|
|
189
|
+
storeValue?: boolean
|
|
190
|
+
value?: string
|
|
191
|
+
defaultValue?: string
|
|
192
|
+
onChange?: (newValue: string) => void
|
|
193
|
+
children?: ReactNode
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface SectionProps extends CommonProps {
|
|
197
|
+
children?: ReactNode
|
|
198
|
+
id?: string
|
|
199
|
+
title?: string
|
|
200
|
+
subtitle?: string
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface ListProps
|
|
204
|
+
extends ActionsInterface,
|
|
205
|
+
NavigationChildInterface,
|
|
206
|
+
SearchBarInterface,
|
|
207
|
+
PaginationInterface,
|
|
208
|
+
CommonProps {
|
|
209
|
+
actions?: ReactNode
|
|
210
|
+
children?: ReactNode
|
|
211
|
+
onSelectionChange?: (id: string | null) => void
|
|
212
|
+
searchBarAccessory?: ReactElement<DropdownProps> | null
|
|
213
|
+
searchText?: string
|
|
214
|
+
enableFiltering?: boolean
|
|
215
|
+
searchBarPlaceholder?: string
|
|
216
|
+
selectedItemId?: string
|
|
217
|
+
isShowingDetail?: boolean
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
interface ListType {
|
|
221
|
+
(props: ListProps): any
|
|
222
|
+
Item: ListItemType
|
|
223
|
+
Section: (props: SectionProps) => any
|
|
224
|
+
Dropdown: ListDropdownType
|
|
225
|
+
EmptyView: (props: EmptyViewProps) => any
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface ListItemType {
|
|
229
|
+
(props: ItemProps): any
|
|
230
|
+
Detail: ListItemDetailType
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface ListItemDetailType {
|
|
234
|
+
(props: DetailProps): any
|
|
235
|
+
Metadata: ListItemDetailMetadataType
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
interface ListItemDetailMetadataType {
|
|
239
|
+
(props: MetadataProps): any
|
|
240
|
+
Label: (props: { title: string; text?: string; icon?: Image.ImageLike }) => any
|
|
241
|
+
Separator: () => any
|
|
242
|
+
Link: (props: { title: string; target: string; text: string }) => any
|
|
243
|
+
TagList: ListItemDetailMetadataTagListType
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
interface ListItemDetailMetadataTagListType {
|
|
247
|
+
(props: { title: string; children: ReactNode }): any
|
|
248
|
+
Item: (props: { text?: string; color?: Color; icon?: Image.ImageLike; onAction?: () => void }) => any
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
interface ListDropdownType {
|
|
252
|
+
(props: DropdownProps): any
|
|
253
|
+
Item: (props: DropdownItemProps) => any
|
|
254
|
+
Section: (props: DropdownSectionProps) => any
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
interface EmptyViewProps extends ActionsInterface, CommonProps {
|
|
258
|
+
icon?: Image.ImageLike
|
|
259
|
+
title?: string
|
|
260
|
+
description?: string
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// List context for passing data to dropdown
|
|
264
|
+
interface ListContextValue {
|
|
265
|
+
isDropdownOpen: boolean
|
|
266
|
+
setIsDropdownOpen: (value: boolean) => void
|
|
267
|
+
openDropdown: () => void
|
|
268
|
+
selectedIndex: number
|
|
269
|
+
setSelectedIndex?: (index: number) => void
|
|
270
|
+
searchText: string
|
|
271
|
+
isFiltering: boolean
|
|
272
|
+
setCurrentDetail?: (detail: ReactNode) => void
|
|
273
|
+
isShowingDetail?: boolean
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const ListContext = createContext<ListContextValue | undefined>(undefined)
|
|
277
|
+
|
|
278
|
+
// Helper function to determine if an item should be visible based on search
|
|
279
|
+
function shouldItemBeVisible(
|
|
280
|
+
searchQuery: string,
|
|
281
|
+
props: {
|
|
282
|
+
title: string
|
|
283
|
+
subtitle?: string
|
|
284
|
+
keywords?: string[]
|
|
285
|
+
},
|
|
286
|
+
): boolean {
|
|
287
|
+
// If no search query, show all items
|
|
288
|
+
if (!searchQuery.trim()) return true
|
|
289
|
+
|
|
290
|
+
const needle = searchQuery.toLowerCase().trim()
|
|
291
|
+
const searchableText = [
|
|
292
|
+
props.title,
|
|
293
|
+
props.subtitle,
|
|
294
|
+
...(props.keywords || []),
|
|
295
|
+
]
|
|
296
|
+
.filter(Boolean)
|
|
297
|
+
.join(' ')
|
|
298
|
+
.toLowerCase()
|
|
299
|
+
|
|
300
|
+
return searchableText.includes(needle)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Create descendants for List items
|
|
304
|
+
interface ListItemDescendant {
|
|
305
|
+
id?: string
|
|
306
|
+
title: string
|
|
307
|
+
subtitle?: string
|
|
308
|
+
keywords?: string[]
|
|
309
|
+
actions?: ReactNode
|
|
310
|
+
visible?: boolean
|
|
311
|
+
detail?: ReactNode
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const {
|
|
315
|
+
DescendantsProvider: ListDescendantsProvider,
|
|
316
|
+
useDescendants: useListDescendants,
|
|
317
|
+
useDescendant: useListItemDescendant,
|
|
318
|
+
} = createDescendants<ListItemDescendant>()
|
|
319
|
+
|
|
320
|
+
// Create descendants for Dropdown items
|
|
321
|
+
interface DropdownItemDescendant {
|
|
322
|
+
value: string
|
|
323
|
+
title: string
|
|
324
|
+
section?: string
|
|
325
|
+
visible?: boolean
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const {
|
|
329
|
+
DescendantsProvider: DropdownDescendantsProvider,
|
|
330
|
+
useDescendants: useDropdownDescendants,
|
|
331
|
+
useDescendant: useDropdownItemDescendant,
|
|
332
|
+
} = createDescendants<DropdownItemDescendant>()
|
|
333
|
+
|
|
334
|
+
// Dropdown context for passing data to dropdown items
|
|
335
|
+
interface DropdownContextValue {
|
|
336
|
+
currentSection?: string
|
|
337
|
+
selectedIndex?: number
|
|
338
|
+
setSelectedIndex?: (index: number) => void
|
|
339
|
+
currentValue?: string
|
|
340
|
+
searchText?: string
|
|
341
|
+
onChange?: (value: string) => void
|
|
342
|
+
isFiltering?: boolean
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const DropdownContext = createContext<DropdownContextValue | undefined>(
|
|
346
|
+
undefined,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
// Dropdown dialog component
|
|
350
|
+
interface ListDropdownDialogProps extends DropdownProps {
|
|
351
|
+
onCancel: () => void
|
|
352
|
+
children?: ReactNode
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function ListDropdownDialog(props: ListDropdownDialogProps): any {
|
|
356
|
+
const [searchText, setSearchTextRaw] = useState('')
|
|
357
|
+
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
358
|
+
const inputRef = useRef<any>(null)
|
|
359
|
+
const descendantsContext = useDropdownDescendants()
|
|
360
|
+
|
|
361
|
+
// Wrapper function that updates search text
|
|
362
|
+
const setSearchText = (value: string) => {
|
|
363
|
+
setSearchTextRaw(value)
|
|
364
|
+
setSelectedIndex(0) // Reset selection when search changes
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const move = (direction: -1 | 1) => {
|
|
368
|
+
// Get all visible items
|
|
369
|
+
const items = Object.values(descendantsContext.map.current)
|
|
370
|
+
.filter((item) => item.index !== -1 && item.props?.visible !== false)
|
|
371
|
+
.sort((a, b) => a.index - b.index)
|
|
372
|
+
|
|
373
|
+
if (items.length === 0) return
|
|
374
|
+
|
|
375
|
+
// Find currently selected item's position in visible items
|
|
376
|
+
let currentVisibleIndex = items.findIndex(
|
|
377
|
+
(item) => item.index === selectedIndex,
|
|
378
|
+
)
|
|
379
|
+
if (currentVisibleIndex === -1) {
|
|
380
|
+
// If current selection is not visible, select first visible item
|
|
381
|
+
if (items[0]) {
|
|
382
|
+
setSelectedIndex(items[0].index)
|
|
383
|
+
}
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Calculate next visible index
|
|
388
|
+
let nextVisibleIndex = currentVisibleIndex + direction
|
|
389
|
+
if (nextVisibleIndex < 0) nextVisibleIndex = items.length - 1
|
|
390
|
+
if (nextVisibleIndex >= items.length) nextVisibleIndex = 0
|
|
391
|
+
|
|
392
|
+
const nextItem = items[nextVisibleIndex]
|
|
393
|
+
if (nextItem) {
|
|
394
|
+
setSelectedIndex(nextItem.index)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const inFocus = useIsInFocus()
|
|
399
|
+
|
|
400
|
+
useKeyboard((evt) => {
|
|
401
|
+
if (!inFocus) return
|
|
402
|
+
|
|
403
|
+
if (evt.name === 'escape') {
|
|
404
|
+
props.onCancel()
|
|
405
|
+
}
|
|
406
|
+
if (evt.name === 'up') move(-1)
|
|
407
|
+
if (evt.name === 'down') move(1)
|
|
408
|
+
if (evt.name === 'return') {
|
|
409
|
+
const items = Object.values(descendantsContext.map.current)
|
|
410
|
+
.filter((item) => item.index !== -1)
|
|
411
|
+
.sort((a, b) => a.index - b.index)
|
|
412
|
+
|
|
413
|
+
const currentItem = items.find((item) => item.index === selectedIndex)
|
|
414
|
+
if (currentItem?.props) {
|
|
415
|
+
props.onChange?.((currentItem.props as DropdownItemDescendant).value)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
return (
|
|
421
|
+
<DropdownDescendantsProvider value={descendantsContext}>
|
|
422
|
+
<box>
|
|
423
|
+
<box style={{ paddingLeft: 2, paddingRight: 2 }}>
|
|
424
|
+
<box style={{ paddingLeft: 1, paddingRight: 1 }}>
|
|
425
|
+
{/* Header */}
|
|
426
|
+
<box
|
|
427
|
+
style={{
|
|
428
|
+
flexDirection: 'row',
|
|
429
|
+
justifyContent: 'space-between',
|
|
430
|
+
}}
|
|
431
|
+
>
|
|
432
|
+
<text attributes={TextAttributes.BOLD}>{props.tooltip}</text>
|
|
433
|
+
<text fg={Theme.textMuted}>esc</text>
|
|
434
|
+
</box>
|
|
435
|
+
<box style={{ paddingTop: 1, paddingBottom: 1 }}>
|
|
436
|
+
<input
|
|
437
|
+
ref={inputRef}
|
|
438
|
+
onInput={setSearchText}
|
|
439
|
+
placeholder={props.placeholder || 'Search...'}
|
|
440
|
+
focused={inFocus}
|
|
441
|
+
value={searchText}
|
|
442
|
+
focusedBackgroundColor={Theme.backgroundPanel}
|
|
443
|
+
cursorColor={Theme.primary}
|
|
444
|
+
focusedTextColor={Theme.textMuted}
|
|
445
|
+
/>
|
|
446
|
+
</box>
|
|
447
|
+
</box>
|
|
448
|
+
|
|
449
|
+
{/* Items list - children will render themselves */}
|
|
450
|
+
<box style={{ paddingBottom: 1 }}>
|
|
451
|
+
<DropdownContext.Provider
|
|
452
|
+
value={{
|
|
453
|
+
currentSection: undefined,
|
|
454
|
+
selectedIndex,
|
|
455
|
+
setSelectedIndex,
|
|
456
|
+
currentValue: props.value,
|
|
457
|
+
searchText,
|
|
458
|
+
isFiltering: true, // Dropdown always has filtering enabled
|
|
459
|
+
onChange: (value: string) => {
|
|
460
|
+
props.onChange?.(value)
|
|
461
|
+
},
|
|
462
|
+
}}
|
|
463
|
+
>
|
|
464
|
+
{props.children}
|
|
465
|
+
</DropdownContext.Provider>
|
|
466
|
+
</box>
|
|
467
|
+
{props.isLoading && (
|
|
468
|
+
<box style={{ paddingLeft: 1 }}>
|
|
469
|
+
<text fg={Theme.textMuted}>Loading...</text>
|
|
470
|
+
</box>
|
|
471
|
+
)}
|
|
472
|
+
</box>
|
|
473
|
+
|
|
474
|
+
<box
|
|
475
|
+
border={false}
|
|
476
|
+
style={{
|
|
477
|
+
paddingRight: 2,
|
|
478
|
+
paddingLeft: 3,
|
|
479
|
+
paddingBottom: 1,
|
|
480
|
+
paddingTop: 1,
|
|
481
|
+
flexDirection: 'row',
|
|
482
|
+
}}
|
|
483
|
+
>
|
|
484
|
+
<text fg={Theme.text} attributes={TextAttributes.BOLD}>
|
|
485
|
+
↵
|
|
486
|
+
</text>
|
|
487
|
+
<text fg={Theme.textMuted}> select</text>
|
|
488
|
+
<text fg={Theme.text} attributes={TextAttributes.BOLD}>
|
|
489
|
+
{' '}↑↓
|
|
490
|
+
</text>
|
|
491
|
+
<text fg={Theme.textMuted}> navigate</text>
|
|
492
|
+
</box>
|
|
493
|
+
</box>
|
|
494
|
+
</DropdownDescendantsProvider>
|
|
495
|
+
)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Render a single list item row
|
|
499
|
+
function ListItemRow(props: {
|
|
500
|
+
title: string
|
|
501
|
+
subtitle?: string
|
|
502
|
+
accessories?: ItemAccessory[]
|
|
503
|
+
active?: boolean
|
|
504
|
+
isShowingDetail?: boolean
|
|
505
|
+
onMouseDown?: () => void
|
|
506
|
+
index?: number
|
|
507
|
+
}) {
|
|
508
|
+
const { title, subtitle, accessories, active } = props
|
|
509
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
510
|
+
|
|
511
|
+
// Format accessories for display
|
|
512
|
+
const accessoryElements: ReactNode[] = []
|
|
513
|
+
if (accessories) {
|
|
514
|
+
accessories.forEach((accessory) => {
|
|
515
|
+
if ('text' in accessory && accessory.text) {
|
|
516
|
+
const textValue =
|
|
517
|
+
typeof accessory.text === 'string'
|
|
518
|
+
? accessory.text
|
|
519
|
+
: accessory.text?.value
|
|
520
|
+
if (textValue) {
|
|
521
|
+
accessoryElements.push(
|
|
522
|
+
<text
|
|
523
|
+
key={`text-${textValue}`}
|
|
524
|
+
fg={active ? Theme.background : Theme.info}
|
|
525
|
+
>
|
|
526
|
+
{textValue}
|
|
527
|
+
</text>,
|
|
528
|
+
)
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if ('tag' in accessory && accessory.tag) {
|
|
532
|
+
const tagValue =
|
|
533
|
+
typeof accessory.tag === 'string'
|
|
534
|
+
? accessory.tag
|
|
535
|
+
: accessory.tag?.value
|
|
536
|
+
if (tagValue) {
|
|
537
|
+
accessoryElements.push(
|
|
538
|
+
<text
|
|
539
|
+
key={`tag-${tagValue}`}
|
|
540
|
+
fg={active ? Theme.background : Theme.warning}
|
|
541
|
+
>
|
|
542
|
+
[{tagValue}]
|
|
543
|
+
</text>,
|
|
544
|
+
)
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
})
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return (
|
|
551
|
+
<box
|
|
552
|
+
style={{
|
|
553
|
+
flexDirection: 'row',
|
|
554
|
+
justifyContent: 'space-between',
|
|
555
|
+
backgroundColor: active
|
|
556
|
+
? Theme.primary
|
|
557
|
+
: isHovered
|
|
558
|
+
? Theme.backgroundPanel
|
|
559
|
+
: undefined,
|
|
560
|
+
paddingLeft: active ? 0 : 1,
|
|
561
|
+
paddingRight: 1,
|
|
562
|
+
}}
|
|
563
|
+
border={false}
|
|
564
|
+
onMouseMove={() => setIsHovered(true)}
|
|
565
|
+
onMouseOut={() => setIsHovered(false)}
|
|
566
|
+
onMouseDown={props.onMouseDown}
|
|
567
|
+
>
|
|
568
|
+
<box style={{ flexDirection: 'row', flexGrow: 1, flexShrink: 1 }}>
|
|
569
|
+
{active && (
|
|
570
|
+
<text fg={Theme.textMuted} selectable={false}>
|
|
571
|
+
›
|
|
572
|
+
</text>
|
|
573
|
+
)}
|
|
574
|
+
<text
|
|
575
|
+
fg={active ? Theme.background : Theme.text}
|
|
576
|
+
attributes={active ? TextAttributes.BOLD : undefined}
|
|
577
|
+
selectable={false}
|
|
578
|
+
>
|
|
579
|
+
{title}
|
|
580
|
+
</text>
|
|
581
|
+
{subtitle && (
|
|
582
|
+
<text
|
|
583
|
+
fg={active ? Theme.background : Theme.textMuted}
|
|
584
|
+
selectable={false}
|
|
585
|
+
>
|
|
586
|
+
{' '}
|
|
587
|
+
{subtitle}
|
|
588
|
+
</text>
|
|
589
|
+
)}
|
|
590
|
+
</box>
|
|
591
|
+
{accessoryElements.length > 0 && (
|
|
592
|
+
<box style={{ flexDirection: 'row' }}>
|
|
593
|
+
{accessoryElements.map((elem, i) => (
|
|
594
|
+
<box key={i} style={{ flexDirection: 'row' }}>
|
|
595
|
+
{i > 0 && <text> </text>}
|
|
596
|
+
{elem}
|
|
597
|
+
</box>
|
|
598
|
+
))}
|
|
599
|
+
</box>
|
|
600
|
+
)}
|
|
601
|
+
{/*{active && <text fg={Theme.textMuted}>‹</text>}*/}
|
|
602
|
+
</box>
|
|
603
|
+
)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export const List: ListType = (props) => {
|
|
607
|
+
const {
|
|
608
|
+
children,
|
|
609
|
+
onSelectionChange,
|
|
610
|
+
filtering = true,
|
|
611
|
+
searchText: controlledSearchText,
|
|
612
|
+
onSearchTextChange,
|
|
613
|
+
searchBarPlaceholder = 'Search...',
|
|
614
|
+
isLoading,
|
|
615
|
+
navigationTitle,
|
|
616
|
+
isShowingDetail,
|
|
617
|
+
selectedItemId,
|
|
618
|
+
searchBarAccessory,
|
|
619
|
+
...otherProps
|
|
620
|
+
} = props
|
|
621
|
+
|
|
622
|
+
const [internalSearchText, setInternalSearchTextRaw] = useState('')
|
|
623
|
+
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
624
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState(false)
|
|
625
|
+
const [currentDetail, setCurrentDetail] = useState<ReactNode>(null)
|
|
626
|
+
const inputRef = useRef<any>(null)
|
|
627
|
+
const descendantsContext = useListDescendants()
|
|
628
|
+
const navigationPending = useNavigationPending()
|
|
629
|
+
|
|
630
|
+
const searchText =
|
|
631
|
+
controlledSearchText !== undefined
|
|
632
|
+
? controlledSearchText
|
|
633
|
+
: internalSearchText
|
|
634
|
+
|
|
635
|
+
// Determine if filtering is enabled
|
|
636
|
+
// List filters automatically when:
|
|
637
|
+
// - filtering is not specified (defaults to true) OR filtering is explicitly true
|
|
638
|
+
// List does NOT filter automatically when:
|
|
639
|
+
// - When filtering={false}
|
|
640
|
+
// - When onSearchTextChange is provided (implicitly sets filtering to false)
|
|
641
|
+
// - Unless you explicitly set filtering={true} alongside onSearchTextChange
|
|
642
|
+
const isFilteringEnabled = (() => {
|
|
643
|
+
if (filtering === false) return false
|
|
644
|
+
if (filtering === true) return true
|
|
645
|
+
// filtering is undefined/not specified
|
|
646
|
+
return !onSearchTextChange // defaults to true unless onSearchTextChange is provided
|
|
647
|
+
})()
|
|
648
|
+
|
|
649
|
+
const openDropdown = () => {
|
|
650
|
+
setIsDropdownOpen(true)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Wrapper function that updates search text
|
|
654
|
+
const setInternalSearchText = (value: string) => {
|
|
655
|
+
setInternalSearchTextRaw(value)
|
|
656
|
+
// Reset to 0 when search changes - this is expected UX behavior
|
|
657
|
+
setSelectedIndex(0)
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const listContextValue = useMemo<ListContextValue>(
|
|
661
|
+
() => ({
|
|
662
|
+
isDropdownOpen,
|
|
663
|
+
setIsDropdownOpen,
|
|
664
|
+
openDropdown,
|
|
665
|
+
selectedIndex,
|
|
666
|
+
setSelectedIndex,
|
|
667
|
+
searchText,
|
|
668
|
+
isFiltering: isFilteringEnabled,
|
|
669
|
+
setCurrentDetail,
|
|
670
|
+
isShowingDetail,
|
|
671
|
+
}),
|
|
672
|
+
[isDropdownOpen, selectedIndex, searchText, isFilteringEnabled, isShowingDetail],
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
// Clear detail when detail view is hidden
|
|
676
|
+
useEffect(() => {
|
|
677
|
+
if (!isShowingDetail) {
|
|
678
|
+
setCurrentDetail(null)
|
|
679
|
+
}
|
|
680
|
+
}, [isShowingDetail])
|
|
681
|
+
|
|
682
|
+
// Handle selectedItemId prop changes
|
|
683
|
+
useEffect(() => {
|
|
684
|
+
// Only update selection if selectedItemId is explicitly provided
|
|
685
|
+
if (selectedItemId !== undefined) {
|
|
686
|
+
const items = Object.values(descendantsContext.map.current)
|
|
687
|
+
.filter((item) => item.index !== -1)
|
|
688
|
+
.sort((a, b) => a.index - b.index)
|
|
689
|
+
|
|
690
|
+
const index = items.findIndex((item) => item.props?.id === selectedItemId)
|
|
691
|
+
if (index !== -1) {
|
|
692
|
+
setSelectedIndex(index)
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}, [selectedItemId])
|
|
696
|
+
|
|
697
|
+
const move = (direction: -1 | 1) => {
|
|
698
|
+
// Get all visible items
|
|
699
|
+
const items = Object.values(descendantsContext.map.current)
|
|
700
|
+
.filter((item) => item.index !== -1 && item.props?.visible !== false)
|
|
701
|
+
.sort((a, b) => a.index - b.index)
|
|
702
|
+
|
|
703
|
+
if (items.length === 0) return
|
|
704
|
+
|
|
705
|
+
// Find currently selected item's position in visible items
|
|
706
|
+
let currentVisibleIndex = items.findIndex(
|
|
707
|
+
(item) => item.index === selectedIndex,
|
|
708
|
+
)
|
|
709
|
+
if (currentVisibleIndex === -1) {
|
|
710
|
+
// If current selection is not visible, select first visible item
|
|
711
|
+
if (items[0]) {
|
|
712
|
+
setSelectedIndex(items[0].index)
|
|
713
|
+
}
|
|
714
|
+
return
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Calculate next visible index
|
|
718
|
+
let nextVisibleIndex = currentVisibleIndex + direction
|
|
719
|
+
if (nextVisibleIndex < 0) nextVisibleIndex = items.length - 1
|
|
720
|
+
if (nextVisibleIndex >= items.length) nextVisibleIndex = 0
|
|
721
|
+
|
|
722
|
+
const nextItem = items[nextVisibleIndex]
|
|
723
|
+
if (nextItem) {
|
|
724
|
+
setSelectedIndex(nextItem.index)
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Handle keyboard navigation
|
|
729
|
+
const inFocus = useIsInFocus()
|
|
730
|
+
const dialog = useDialog()
|
|
731
|
+
|
|
732
|
+
useKeyboard((evt) => {
|
|
733
|
+
if (!inFocus) return
|
|
734
|
+
|
|
735
|
+
// Handle Ctrl+P for dropdown
|
|
736
|
+
if (evt.ctrl && evt.name === 'p' && searchBarAccessory && !isDropdownOpen) {
|
|
737
|
+
openDropdown()
|
|
738
|
+
return
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Handle Ctrl+K to show actions
|
|
742
|
+
if (evt.name === 'k' && evt.ctrl) {
|
|
743
|
+
const items = Object.values(descendantsContext.map.current)
|
|
744
|
+
.filter((item) => item.index !== -1)
|
|
745
|
+
.sort((a, b) => a.index - b.index)
|
|
746
|
+
|
|
747
|
+
const currentItem = items.find((item) => item.index === selectedIndex)
|
|
748
|
+
|
|
749
|
+
// Show current item's actions if available
|
|
750
|
+
if (currentItem?.props?.actions) {
|
|
751
|
+
dialog.push(currentItem.props.actions, 'bottom-right')
|
|
752
|
+
}
|
|
753
|
+
// Otherwise show List's own actions
|
|
754
|
+
else if (props.actions) {
|
|
755
|
+
dialog.push(props.actions, 'bottom-right')
|
|
756
|
+
}
|
|
757
|
+
return
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (evt.name === 'up') move(-1)
|
|
761
|
+
if (evt.name === 'down') move(1)
|
|
762
|
+
if (evt.name === 'return') {
|
|
763
|
+
const items = Object.values(descendantsContext.map.current)
|
|
764
|
+
.filter((item) => item.index !== -1)
|
|
765
|
+
.sort((a, b) => a.index - b.index)
|
|
766
|
+
|
|
767
|
+
const currentItem = items.find((item) => item.index === selectedIndex)
|
|
768
|
+
if (!currentItem?.props) return
|
|
769
|
+
|
|
770
|
+
if (currentItem.props.actions) {
|
|
771
|
+
dialog.push(currentItem.props.actions, 'bottom-right')
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
const handleSearchChange = (newValue: string) => {
|
|
777
|
+
if (!inFocus) return
|
|
778
|
+
|
|
779
|
+
// Always call onSearchTextChange if provided
|
|
780
|
+
if (onSearchTextChange) {
|
|
781
|
+
onSearchTextChange(newValue)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (controlledSearchText === undefined) {
|
|
785
|
+
setInternalSearchText(newValue)
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return (
|
|
790
|
+
<ListContext.Provider value={listContextValue}>
|
|
791
|
+
<ListDescendantsProvider value={descendantsContext}>
|
|
792
|
+
<box style={{ flexDirection: 'column', flexGrow: 1 }}>
|
|
793
|
+
{/* Cannot mount focused actions here - would need to be handled differently */}
|
|
794
|
+
|
|
795
|
+
{navigationTitle && (
|
|
796
|
+
<box
|
|
797
|
+
border={false}
|
|
798
|
+
style={{
|
|
799
|
+
paddingBottom: 0,
|
|
800
|
+
flexGrow: 1,
|
|
801
|
+
}}
|
|
802
|
+
>
|
|
803
|
+
<LoadingBar
|
|
804
|
+
title={navigationTitle}
|
|
805
|
+
isLoading={isLoading || navigationPending}
|
|
806
|
+
/>
|
|
807
|
+
</box>
|
|
808
|
+
)}
|
|
809
|
+
|
|
810
|
+
{/* Search bar with optional dropdown accessory */}
|
|
811
|
+
<box>
|
|
812
|
+
<box
|
|
813
|
+
border={false}
|
|
814
|
+
style={{
|
|
815
|
+
paddingLeft: 1,
|
|
816
|
+
paddingRight: 1,
|
|
817
|
+
marginTop: 1,
|
|
818
|
+
marginBottom: 1,
|
|
819
|
+
flexDirection: 'row',
|
|
820
|
+
justifyContent: 'space-between',
|
|
821
|
+
|
|
822
|
+
alignItems: 'center',
|
|
823
|
+
}}
|
|
824
|
+
>
|
|
825
|
+
<box
|
|
826
|
+
style={{
|
|
827
|
+
flexGrow: 1,
|
|
828
|
+
flexDirection: 'column',
|
|
829
|
+
flexShrink: 1,
|
|
830
|
+
}}
|
|
831
|
+
>
|
|
832
|
+
<input
|
|
833
|
+
ref={inputRef}
|
|
834
|
+
placeholder={searchBarPlaceholder}
|
|
835
|
+
focused={inFocus && !isDropdownOpen}
|
|
836
|
+
value={searchText}
|
|
837
|
+
onInput={handleSearchChange}
|
|
838
|
+
focusedBackgroundColor={Theme.backgroundPanel}
|
|
839
|
+
cursorColor={Theme.primary}
|
|
840
|
+
focusedTextColor={Theme.text}
|
|
841
|
+
/>
|
|
842
|
+
</box>
|
|
843
|
+
{searchBarAccessory}
|
|
844
|
+
</box>
|
|
845
|
+
</box>
|
|
846
|
+
|
|
847
|
+
{/* Main content area with optional detail view */}
|
|
848
|
+
<box style={{ flexDirection: 'row', flexGrow: 1 }}>
|
|
849
|
+
{/* List content - render children which will register themselves */}
|
|
850
|
+
<box style={{ marginTop: 1, width: isShowingDetail ? '50%' : '100%', flexGrow: isShowingDetail ? 0 : 1 }}>
|
|
851
|
+
<>
|
|
852
|
+
{/* Render children - they will register as descendants */}
|
|
853
|
+
<ListItemsRenderer>{children}</ListItemsRenderer>
|
|
854
|
+
|
|
855
|
+
{/* Footer with keyboard shortcuts or toast */}
|
|
856
|
+
<ListFooter />
|
|
857
|
+
</>
|
|
858
|
+
</box>
|
|
859
|
+
|
|
860
|
+
{/* Detail panel on the right */}
|
|
861
|
+
{isShowingDetail && currentDetail && (
|
|
862
|
+
<box
|
|
863
|
+
style={{
|
|
864
|
+
marginTop: 1,
|
|
865
|
+
width: '50%',
|
|
866
|
+
paddingLeft: 1,
|
|
867
|
+
paddingRight: 1,
|
|
868
|
+
}}
|
|
869
|
+
border={['left']}
|
|
870
|
+
borderStyle='single'
|
|
871
|
+
borderColor={Theme.border}
|
|
872
|
+
>
|
|
873
|
+
{currentDetail}
|
|
874
|
+
</box>
|
|
875
|
+
)}
|
|
876
|
+
</box>
|
|
877
|
+
</box>
|
|
878
|
+
</ListDescendantsProvider>
|
|
879
|
+
</ListContext.Provider>
|
|
880
|
+
)
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Component to render list items and sections
|
|
884
|
+
function ListItemsRenderer(props: { children?: ReactNode }): any {
|
|
885
|
+
const { children } = props
|
|
886
|
+
const listContext = useContext(ListContext)
|
|
887
|
+
const searchText = listContext?.searchText || ''
|
|
888
|
+
|
|
889
|
+
// Pass search text down via context
|
|
890
|
+
return (
|
|
891
|
+
<ListSectionContext.Provider value={{ searchText }}>
|
|
892
|
+
{children}
|
|
893
|
+
</ListSectionContext.Provider>
|
|
894
|
+
)
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Context for passing section state to items
|
|
898
|
+
interface ListSectionContextValue {
|
|
899
|
+
sectionTitle?: string
|
|
900
|
+
searchText?: string
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const ListSectionContext = createContext<ListSectionContextValue>({})
|
|
904
|
+
|
|
905
|
+
const ListItem: ListItemType = (props) => {
|
|
906
|
+
const listSectionContext = useContext(ListSectionContext)
|
|
907
|
+
const { sectionTitle } = listSectionContext
|
|
908
|
+
const listContext = useContext(ListContext)
|
|
909
|
+
const dialog = useDialog()
|
|
910
|
+
|
|
911
|
+
// Extract text values for descendant registration
|
|
912
|
+
const titleText =
|
|
913
|
+
typeof props.title === 'string' ? props.title : props.title.value
|
|
914
|
+
const subtitleText = props.subtitle
|
|
915
|
+
? typeof props.subtitle === 'string'
|
|
916
|
+
? props.subtitle
|
|
917
|
+
: props.subtitle.value || ''
|
|
918
|
+
: undefined
|
|
919
|
+
|
|
920
|
+
// Check if this item is visible based on search
|
|
921
|
+
const isFiltering = listContext?.isFiltering ?? false
|
|
922
|
+
const searchText = listContext?.searchText ?? ''
|
|
923
|
+
|
|
924
|
+
const isVisible =
|
|
925
|
+
!isFiltering ||
|
|
926
|
+
shouldItemBeVisible(searchText, {
|
|
927
|
+
title: titleText,
|
|
928
|
+
subtitle: subtitleText,
|
|
929
|
+
keywords: [...(props.keywords || []), sectionTitle].filter(
|
|
930
|
+
Boolean,
|
|
931
|
+
) as string[],
|
|
932
|
+
})
|
|
933
|
+
|
|
934
|
+
// Register as descendant with all searchable data
|
|
935
|
+
const { index } = useListItemDescendant({
|
|
936
|
+
id: props.id,
|
|
937
|
+
title: titleText,
|
|
938
|
+
subtitle: subtitleText,
|
|
939
|
+
keywords: [...(props.keywords || []), sectionTitle].filter(
|
|
940
|
+
Boolean,
|
|
941
|
+
) as string[],
|
|
942
|
+
actions: props.actions,
|
|
943
|
+
visible: isVisible,
|
|
944
|
+
detail: props.detail,
|
|
945
|
+
})
|
|
946
|
+
|
|
947
|
+
// Get selected index from parent List context
|
|
948
|
+
const selectedIndex = listContext?.selectedIndex ?? 0
|
|
949
|
+
const isActive = index === selectedIndex
|
|
950
|
+
|
|
951
|
+
// Update detail when this item becomes active or detail prop changes
|
|
952
|
+
useEffect(() => {
|
|
953
|
+
if (isActive && listContext?.isShowingDetail && listContext?.setCurrentDetail) {
|
|
954
|
+
listContext.setCurrentDetail(props.detail || null)
|
|
955
|
+
}
|
|
956
|
+
}, [isActive, props.detail, listContext?.isShowingDetail, listContext?.setCurrentDetail])
|
|
957
|
+
|
|
958
|
+
// Don't render if not visible
|
|
959
|
+
if (!isVisible) return null
|
|
960
|
+
|
|
961
|
+
// Handle mouse click on item
|
|
962
|
+
const handleMouseDown = () => {
|
|
963
|
+
if (listContext && index !== -1) {
|
|
964
|
+
// If clicking on already selected item, show actions (like pressing Enter)
|
|
965
|
+
if (isActive && props.actions) {
|
|
966
|
+
dialog.push(props.actions, 'bottom-right')
|
|
967
|
+
} else if (listContext.setSelectedIndex) {
|
|
968
|
+
// Otherwise just select the item
|
|
969
|
+
listContext.setSelectedIndex(index)
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Don't show accessories if we're showing detail
|
|
975
|
+
const showAccessories = !props.detail && props.accessories
|
|
976
|
+
|
|
977
|
+
// Render the item row directly
|
|
978
|
+
return (
|
|
979
|
+
<ListItemRow
|
|
980
|
+
title={titleText}
|
|
981
|
+
subtitle={subtitleText}
|
|
982
|
+
accessories={showAccessories ? props.accessories : undefined}
|
|
983
|
+
active={isActive}
|
|
984
|
+
isShowingDetail={props.detail !== undefined}
|
|
985
|
+
onMouseDown={handleMouseDown}
|
|
986
|
+
index={index}
|
|
987
|
+
/>
|
|
988
|
+
)
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const ListItemDetail: ListItemDetailType = (props) => {
|
|
992
|
+
const { isLoading, markdown, metadata } = props
|
|
993
|
+
|
|
994
|
+
return (
|
|
995
|
+
<box style={{ flexDirection: 'column', flexGrow: 1 }}>
|
|
996
|
+
{isLoading && (
|
|
997
|
+
<box style={{ paddingBottom: 1 }}>
|
|
998
|
+
<text fg={Theme.textMuted}>Loading...</text>
|
|
999
|
+
</box>
|
|
1000
|
+
)}
|
|
1001
|
+
|
|
1002
|
+
{markdown && (
|
|
1003
|
+
<box style={{ flexGrow: 1, flexShrink: 1, overflow: 'scroll' }}>
|
|
1004
|
+
<text>{markdown}</text>
|
|
1005
|
+
</box>
|
|
1006
|
+
)}
|
|
1007
|
+
|
|
1008
|
+
{metadata && (
|
|
1009
|
+
<box
|
|
1010
|
+
style={{ paddingTop: 1 }}
|
|
1011
|
+
border={['top']}
|
|
1012
|
+
borderStyle='single'
|
|
1013
|
+
borderColor={Theme.border}
|
|
1014
|
+
>
|
|
1015
|
+
{metadata}
|
|
1016
|
+
</box>
|
|
1017
|
+
)}
|
|
1018
|
+
</box>
|
|
1019
|
+
)
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const ListItemDetailMetadata = (props: MetadataProps) => {
|
|
1023
|
+
return (
|
|
1024
|
+
<box style={{ flexDirection: 'column' }}>
|
|
1025
|
+
{props.children}
|
|
1026
|
+
</box>
|
|
1027
|
+
)
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const ListItemDetailMetadataLabel = (props: { title: string; text?: string; icon?: Image.ImageLike }) => {
|
|
1031
|
+
return (
|
|
1032
|
+
<box style={{ flexDirection: 'row', paddingBottom: 0.5 }}>
|
|
1033
|
+
<text fg={Theme.textMuted} style={{ minWidth: 15 }}>{props.title}:</text>
|
|
1034
|
+
{props.text && <text fg={Theme.text}>{props.text}</text>}
|
|
1035
|
+
</box>
|
|
1036
|
+
)
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const ListItemDetailMetadataSeparator = () => {
|
|
1040
|
+
return (
|
|
1041
|
+
<box style={{ paddingBottom: 0.5 }}>
|
|
1042
|
+
<text fg={Theme.border}>─────────────────</text>
|
|
1043
|
+
</box>
|
|
1044
|
+
)
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const ListItemDetailMetadataLink = (props: { title: string; target: string; text: string }) => {
|
|
1048
|
+
return (
|
|
1049
|
+
<box style={{ flexDirection: 'row', paddingBottom: 0.5 }}>
|
|
1050
|
+
<text fg={Theme.textMuted} style={{ minWidth: 15 }}>{props.title}:</text>
|
|
1051
|
+
<text fg={Theme.link}>{props.text}</text>
|
|
1052
|
+
</box>
|
|
1053
|
+
)
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
const ListItemDetailMetadataTagList = (props: { title: string; children: ReactNode }) => {
|
|
1057
|
+
return (
|
|
1058
|
+
<box style={{ flexDirection: 'column', paddingBottom: 0.5 }}>
|
|
1059
|
+
<text fg={Theme.textMuted}>{props.title}:</text>
|
|
1060
|
+
<box style={{ flexDirection: 'row', paddingLeft: 1 }}>
|
|
1061
|
+
{props.children}
|
|
1062
|
+
</box>
|
|
1063
|
+
</box>
|
|
1064
|
+
)
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const ListItemDetailMetadataTagListItem = (props: { text?: string; color?: Color; icon?: Image.ImageLike; onAction?: () => void }) => {
|
|
1068
|
+
return (
|
|
1069
|
+
<box style={{ paddingRight: 1 }}>
|
|
1070
|
+
<text fg={props.color || Theme.accent}>[{props.text}]</text>
|
|
1071
|
+
</box>
|
|
1072
|
+
)
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
ListItemDetail.Metadata = ListItemDetailMetadata as any
|
|
1076
|
+
ListItemDetailMetadata.Label = ListItemDetailMetadataLabel as any
|
|
1077
|
+
ListItemDetailMetadata.Separator = ListItemDetailMetadataSeparator as any
|
|
1078
|
+
ListItemDetailMetadata.Link = ListItemDetailMetadataLink as any
|
|
1079
|
+
ListItemDetailMetadata.TagList = ListItemDetailMetadataTagList as any
|
|
1080
|
+
ListItemDetailMetadataTagList.Item = ListItemDetailMetadataTagListItem as any
|
|
1081
|
+
|
|
1082
|
+
ListItem.Detail = ListItemDetail
|
|
1083
|
+
|
|
1084
|
+
const ListDropdown: ListDropdownType = (props) => {
|
|
1085
|
+
const listContext = useContext(ListContext)
|
|
1086
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
1087
|
+
|
|
1088
|
+
// If not inside a List, just render nothing (for type safety)
|
|
1089
|
+
if (!listContext) {
|
|
1090
|
+
return null
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const { isDropdownOpen, setIsDropdownOpen } = listContext
|
|
1094
|
+
// Store both value and title together
|
|
1095
|
+
const [dropdownState, setDropdownState] = useState<{
|
|
1096
|
+
value: string
|
|
1097
|
+
title: string
|
|
1098
|
+
}>(() => {
|
|
1099
|
+
const initialValue = props.value || props.defaultValue || ''
|
|
1100
|
+
return { value: initialValue, title: initialValue || 'All' }
|
|
1101
|
+
})
|
|
1102
|
+
const descendantsContext = useDropdownDescendants()
|
|
1103
|
+
const dialog = useDialog()
|
|
1104
|
+
const inFocus = useIsInFocus()
|
|
1105
|
+
|
|
1106
|
+
// Update value and find its title
|
|
1107
|
+
useLayoutEffect(() => {
|
|
1108
|
+
const valueToUse =
|
|
1109
|
+
props.value !== undefined ? props.value : dropdownState.value
|
|
1110
|
+
|
|
1111
|
+
// If no value is set and we have descendants, use the first item
|
|
1112
|
+
if (!valueToUse && !props.value && !props.defaultValue) {
|
|
1113
|
+
const items = Object.values(descendantsContext.map.current)
|
|
1114
|
+
.filter((item) => item.index !== -1)
|
|
1115
|
+
.sort((a, b) => a.index - b.index)
|
|
1116
|
+
|
|
1117
|
+
if (items.length > 0) {
|
|
1118
|
+
const firstItem = items[0].props as DropdownItemDescendant
|
|
1119
|
+
setDropdownState({ value: firstItem.value, title: firstItem.title })
|
|
1120
|
+
return
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (!valueToUse) return
|
|
1125
|
+
|
|
1126
|
+
// Try to find the title for this value
|
|
1127
|
+
let title = valueToUse
|
|
1128
|
+
for (const item of Object.values(descendantsContext.map.current)) {
|
|
1129
|
+
const itemProps = item.props as DropdownItemDescendant
|
|
1130
|
+
if (itemProps.value === valueToUse) {
|
|
1131
|
+
title = itemProps.title
|
|
1132
|
+
break
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Only update if something changed
|
|
1137
|
+
if (dropdownState.value !== valueToUse || dropdownState.title !== title) {
|
|
1138
|
+
setDropdownState({ value: valueToUse, title })
|
|
1139
|
+
}
|
|
1140
|
+
}, [props.value]) // Run when props.value changes and on mount
|
|
1141
|
+
|
|
1142
|
+
const dropdownContextValue = useMemo<DropdownContextValue>(
|
|
1143
|
+
() => ({
|
|
1144
|
+
currentSection: undefined,
|
|
1145
|
+
}),
|
|
1146
|
+
[],
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1149
|
+
// Open dropdown dialog when triggered
|
|
1150
|
+
useEffect(() => {
|
|
1151
|
+
if (isDropdownOpen && !dialog.stack.length) {
|
|
1152
|
+
// Pass the children to the dialog to render them there
|
|
1153
|
+
dialog.push(
|
|
1154
|
+
<ListDropdownDialog
|
|
1155
|
+
{...props}
|
|
1156
|
+
value={dropdownState.value}
|
|
1157
|
+
onChange={(newValue) => {
|
|
1158
|
+
// Find the title for this value
|
|
1159
|
+
let title = newValue
|
|
1160
|
+
for (const item of Object.values(descendantsContext.map.current)) {
|
|
1161
|
+
const itemProps = item.props as DropdownItemDescendant
|
|
1162
|
+
if (itemProps.value === newValue) {
|
|
1163
|
+
title = itemProps.title
|
|
1164
|
+
break
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
setDropdownState({ value: newValue, title })
|
|
1168
|
+
setIsDropdownOpen(false)
|
|
1169
|
+
dialog.clear()
|
|
1170
|
+
if (props.onChange) {
|
|
1171
|
+
props.onChange(newValue)
|
|
1172
|
+
}
|
|
1173
|
+
// TODO: Handle storeValue to persist the value
|
|
1174
|
+
}}
|
|
1175
|
+
onCancel={() => {
|
|
1176
|
+
setIsDropdownOpen(false)
|
|
1177
|
+
dialog.clear()
|
|
1178
|
+
}}
|
|
1179
|
+
>
|
|
1180
|
+
{props.children}
|
|
1181
|
+
</ListDropdownDialog>,
|
|
1182
|
+
'top-right',
|
|
1183
|
+
)
|
|
1184
|
+
}
|
|
1185
|
+
}, [isDropdownOpen, props.children])
|
|
1186
|
+
|
|
1187
|
+
// Display the title from our state
|
|
1188
|
+
const displayValue = dropdownState.title || 'All'
|
|
1189
|
+
|
|
1190
|
+
return (
|
|
1191
|
+
<DropdownDescendantsProvider value={descendantsContext}>
|
|
1192
|
+
<DropdownContext.Provider value={dropdownContextValue}>
|
|
1193
|
+
{/* Render children to collect items - they return null anyway */}
|
|
1194
|
+
{props.children}
|
|
1195
|
+
{/* Render dropdown UI */}
|
|
1196
|
+
<box
|
|
1197
|
+
key={dropdownState.value}
|
|
1198
|
+
style={{
|
|
1199
|
+
paddingTop: 1,
|
|
1200
|
+
paddingLeft: 2,
|
|
1201
|
+
// minWidth: value.length + 4,
|
|
1202
|
+
flexDirection: 'row',
|
|
1203
|
+
flexShrink: 0,
|
|
1204
|
+
backgroundColor: isHovered ? Theme.backgroundPanel : undefined,
|
|
1205
|
+
}}
|
|
1206
|
+
onMouseMove={() => setIsHovered(true)}
|
|
1207
|
+
onMouseOut={() => setIsHovered(false)}
|
|
1208
|
+
onMouseDown={() => {
|
|
1209
|
+
// Open dropdown when clicked
|
|
1210
|
+
if (!isDropdownOpen) {
|
|
1211
|
+
listContext.openDropdown()
|
|
1212
|
+
}
|
|
1213
|
+
}}
|
|
1214
|
+
>
|
|
1215
|
+
{/*<text >^p </text>*/}
|
|
1216
|
+
<text
|
|
1217
|
+
fg={isHovered ? Theme.text : Theme.textMuted}
|
|
1218
|
+
selectable={false}
|
|
1219
|
+
>
|
|
1220
|
+
{displayValue}
|
|
1221
|
+
</text>
|
|
1222
|
+
<text
|
|
1223
|
+
fg={isHovered ? Theme.text : Theme.textMuted}
|
|
1224
|
+
selectable={false}
|
|
1225
|
+
>
|
|
1226
|
+
{' '}
|
|
1227
|
+
▾
|
|
1228
|
+
</text>
|
|
1229
|
+
</box>
|
|
1230
|
+
</DropdownContext.Provider>
|
|
1231
|
+
</DropdownDescendantsProvider>
|
|
1232
|
+
)
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
ListDropdown.Item = (props) => {
|
|
1236
|
+
const dropdownContext = useContext(DropdownContext)
|
|
1237
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
1238
|
+
|
|
1239
|
+
// If not inside a Dropdown, just render nothing
|
|
1240
|
+
if (!dropdownContext) {
|
|
1241
|
+
return null
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
const {
|
|
1245
|
+
currentSection,
|
|
1246
|
+
selectedIndex,
|
|
1247
|
+
currentValue,
|
|
1248
|
+
setSelectedIndex,
|
|
1249
|
+
onChange,
|
|
1250
|
+
searchText,
|
|
1251
|
+
isFiltering,
|
|
1252
|
+
} = dropdownContext
|
|
1253
|
+
|
|
1254
|
+
// Check if this item is visible based on search
|
|
1255
|
+
const isVisible =
|
|
1256
|
+
!isFiltering ||
|
|
1257
|
+
!searchText ||
|
|
1258
|
+
shouldItemBeVisible(searchText, {
|
|
1259
|
+
title: props.title,
|
|
1260
|
+
keywords: currentSection ? [currentSection] : [],
|
|
1261
|
+
})
|
|
1262
|
+
|
|
1263
|
+
// Register as descendant
|
|
1264
|
+
const { index } = useDropdownItemDescendant({
|
|
1265
|
+
value: props.value,
|
|
1266
|
+
title: props.title,
|
|
1267
|
+
section: currentSection,
|
|
1268
|
+
visible: isVisible,
|
|
1269
|
+
})
|
|
1270
|
+
|
|
1271
|
+
// Don't render if not visible
|
|
1272
|
+
if (!isVisible) return null
|
|
1273
|
+
|
|
1274
|
+
// If we're in the dialog, render the item
|
|
1275
|
+
if (selectedIndex !== undefined) {
|
|
1276
|
+
const isActive = selectedIndex === index
|
|
1277
|
+
const isCurrent = props.value === currentValue
|
|
1278
|
+
|
|
1279
|
+
const handleMouseMove = () => {
|
|
1280
|
+
setIsHovered(true)
|
|
1281
|
+
// Update selected index on hover
|
|
1282
|
+
if (setSelectedIndex && index !== selectedIndex) {
|
|
1283
|
+
setSelectedIndex(index)
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
const handleMouseDown = () => {
|
|
1288
|
+
// Trigger selection on click
|
|
1289
|
+
if (onChange) {
|
|
1290
|
+
onChange(props.value)
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
return (
|
|
1295
|
+
<box
|
|
1296
|
+
style={{
|
|
1297
|
+
flexDirection: 'row',
|
|
1298
|
+
backgroundColor: isActive
|
|
1299
|
+
? Theme.primary
|
|
1300
|
+
: isHovered
|
|
1301
|
+
? Theme.backgroundPanel
|
|
1302
|
+
: undefined,
|
|
1303
|
+
paddingLeft: isActive ? 0 : 1,
|
|
1304
|
+
paddingRight: 1,
|
|
1305
|
+
justifyContent: 'space-between',
|
|
1306
|
+
}}
|
|
1307
|
+
border={false}
|
|
1308
|
+
onMouseMove={handleMouseMove}
|
|
1309
|
+
onMouseOut={() => setIsHovered(false)}
|
|
1310
|
+
onMouseDown={handleMouseDown}
|
|
1311
|
+
>
|
|
1312
|
+
<box style={{ flexDirection: 'row' }}>
|
|
1313
|
+
{isActive && (
|
|
1314
|
+
<text fg={Theme.background} selectable={false}>
|
|
1315
|
+
›{''}
|
|
1316
|
+
</text>
|
|
1317
|
+
)}
|
|
1318
|
+
<text
|
|
1319
|
+
fg={
|
|
1320
|
+
isActive
|
|
1321
|
+
? Theme.background
|
|
1322
|
+
: isCurrent
|
|
1323
|
+
? Theme.primary
|
|
1324
|
+
: Theme.text
|
|
1325
|
+
}
|
|
1326
|
+
attributes={isActive ? TextAttributes.BOLD : undefined}
|
|
1327
|
+
selectable={false}
|
|
1328
|
+
>
|
|
1329
|
+
{props.title}
|
|
1330
|
+
</text>
|
|
1331
|
+
</box>
|
|
1332
|
+
</box>
|
|
1333
|
+
)
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
return null
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
ListDropdown.Section = (props) => {
|
|
1340
|
+
const parentContext = useContext(DropdownContext)
|
|
1341
|
+
|
|
1342
|
+
// If not inside a Dropdown, just render nothing
|
|
1343
|
+
if (!parentContext) {
|
|
1344
|
+
return null
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// Create a new context with the section name
|
|
1348
|
+
const sectionContextValue = useMemo<DropdownContextValue>(
|
|
1349
|
+
() => ({
|
|
1350
|
+
...parentContext,
|
|
1351
|
+
currentSection: props.title,
|
|
1352
|
+
}),
|
|
1353
|
+
[parentContext, props.title],
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1356
|
+
// Hide section title when searching
|
|
1357
|
+
const showTitle =
|
|
1358
|
+
parentContext.selectedIndex !== undefined &&
|
|
1359
|
+
props.title &&
|
|
1360
|
+
!parentContext.searchText?.trim()
|
|
1361
|
+
|
|
1362
|
+
return (
|
|
1363
|
+
<>
|
|
1364
|
+
{/* Render section title if we're in the dialog and not searching */}
|
|
1365
|
+
{showTitle && (
|
|
1366
|
+
<box style={{ paddingTop: 1, paddingLeft: 1 }}>
|
|
1367
|
+
<text fg={Theme.accent} attributes={TextAttributes.BOLD}>
|
|
1368
|
+
{props.title}
|
|
1369
|
+
</text>
|
|
1370
|
+
</box>
|
|
1371
|
+
)}
|
|
1372
|
+
<DropdownContext.Provider value={sectionContextValue}>
|
|
1373
|
+
{props.children}
|
|
1374
|
+
</DropdownContext.Provider>
|
|
1375
|
+
</>
|
|
1376
|
+
)
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
List.Item = ListItem
|
|
1380
|
+
const ListSection = (props: SectionProps) => {
|
|
1381
|
+
const parentContext = useContext(ListSectionContext)
|
|
1382
|
+
const listContext = useContext(ListContext)
|
|
1383
|
+
const searchText = listContext?.searchText || ''
|
|
1384
|
+
|
|
1385
|
+
// Create new context with section title and search text
|
|
1386
|
+
const sectionContextValue = useMemo(
|
|
1387
|
+
() => ({
|
|
1388
|
+
...parentContext,
|
|
1389
|
+
sectionTitle: props.title,
|
|
1390
|
+
searchText,
|
|
1391
|
+
}),
|
|
1392
|
+
[parentContext, props.title, searchText],
|
|
1393
|
+
)
|
|
1394
|
+
|
|
1395
|
+
// Hide section title when searching
|
|
1396
|
+
const showTitle = props.title && !searchText.trim()
|
|
1397
|
+
|
|
1398
|
+
return (
|
|
1399
|
+
<>
|
|
1400
|
+
{/* Render section title if provided and not searching */}
|
|
1401
|
+
{showTitle && (
|
|
1402
|
+
<box
|
|
1403
|
+
border={false}
|
|
1404
|
+
style={{
|
|
1405
|
+
paddingLeft: 1,
|
|
1406
|
+
paddingTop: 1,
|
|
1407
|
+
}}
|
|
1408
|
+
>
|
|
1409
|
+
<text fg={Theme.accent} attributes={TextAttributes.BOLD}>
|
|
1410
|
+
{props.title}
|
|
1411
|
+
</text>
|
|
1412
|
+
</box>
|
|
1413
|
+
)}
|
|
1414
|
+
{/* Render children with section context */}
|
|
1415
|
+
<ListSectionContext.Provider value={sectionContextValue}>
|
|
1416
|
+
{props.children}
|
|
1417
|
+
</ListSectionContext.Provider>
|
|
1418
|
+
</>
|
|
1419
|
+
)
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
List.Section = ListSection
|
|
1423
|
+
List.Dropdown = ListDropdown
|
|
1424
|
+
List.EmptyView = (props) => {
|
|
1425
|
+
return null
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
export default List
|
|
1429
|
+
|
|
1430
|
+
// Grid Component Implementation
|
|
1431
|
+
export interface GridInset {
|
|
1432
|
+
bottom?: number
|
|
1433
|
+
left?: number
|
|
1434
|
+
right?: number
|
|
1435
|
+
top?: number
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
export interface GridItemProps extends ActionsInterface, CommonProps {
|
|
1439
|
+
id?: string
|
|
1440
|
+
content:
|
|
1441
|
+
| Image.ImageLike
|
|
1442
|
+
| {
|
|
1443
|
+
value: Image.ImageLike
|
|
1444
|
+
tooltip?: string | null
|
|
1445
|
+
}
|
|
1446
|
+
title:
|
|
1447
|
+
| string
|
|
1448
|
+
| {
|
|
1449
|
+
value: string
|
|
1450
|
+
tooltip?: string | null
|
|
1451
|
+
}
|
|
1452
|
+
subtitle?:
|
|
1453
|
+
| string
|
|
1454
|
+
| {
|
|
1455
|
+
value?: string | null
|
|
1456
|
+
tooltip?: string | null
|
|
1457
|
+
}
|
|
1458
|
+
keywords?: string[]
|
|
1459
|
+
getDetailMarkdown?: () =>
|
|
1460
|
+
| { markdown: string; metadata?: ReactElement<MetadataProps> }
|
|
1461
|
+
| Promise<{ markdown: string; metadata?: ReactElement<MetadataProps> }>
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
export interface GridProps
|
|
1465
|
+
extends ActionsInterface,
|
|
1466
|
+
NavigationChildInterface,
|
|
1467
|
+
SearchBarInterface,
|
|
1468
|
+
PaginationInterface,
|
|
1469
|
+
CommonProps {
|
|
1470
|
+
actions?: ReactNode
|
|
1471
|
+
aspectRatio?: '1' | '3/2' | '2/3' | '4/3' | '3/4' | '16/9' | '9/16'
|
|
1472
|
+
children?: ReactNode
|
|
1473
|
+
columns?: number
|
|
1474
|
+
fit?: 'contain' | 'fill'
|
|
1475
|
+
inset?: GridInset
|
|
1476
|
+
navigationTitle?: string
|
|
1477
|
+
onSelectionChange?: (id: string | null) => void
|
|
1478
|
+
searchBarAccessory?: ReactElement<DropdownProps> | null
|
|
1479
|
+
searchText?: string
|
|
1480
|
+
enableFiltering?: boolean
|
|
1481
|
+
searchBarPlaceholder?: string
|
|
1482
|
+
selectedItemId?: string
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
export interface GridSectionProps extends CommonProps {
|
|
1486
|
+
children?: ReactNode
|
|
1487
|
+
id?: string
|
|
1488
|
+
title?: string
|
|
1489
|
+
subtitle?: string
|
|
1490
|
+
aspectRatio?: '1' | '3/2' | '2/3' | '4/3' | '3/4' | '16/9' | '9/16'
|
|
1491
|
+
columns?: number
|
|
1492
|
+
fit?: 'contain' | 'fill'
|
|
1493
|
+
inset?: GridInset
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
interface GridType {
|
|
1497
|
+
(props: GridProps): any
|
|
1498
|
+
Item: (props: GridItemProps) => any
|
|
1499
|
+
Section: (props: GridSectionProps) => any
|
|
1500
|
+
Dropdown: ListDropdownType
|
|
1501
|
+
EmptyView: (props: EmptyViewProps) => any
|
|
1502
|
+
Inset: {
|
|
1503
|
+
Small: GridInset
|
|
1504
|
+
Medium: GridInset
|
|
1505
|
+
Large: GridInset
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// Grid uses List internally with a different visual representation
|
|
1510
|
+
export const Grid: GridType = (props) => {
|
|
1511
|
+
// Grid is essentially List with grid layout
|
|
1512
|
+
// We'll reuse the List component but with grid-specific styling
|
|
1513
|
+
const {
|
|
1514
|
+
columns = 5,
|
|
1515
|
+
aspectRatio = '1',
|
|
1516
|
+
fit = 'contain',
|
|
1517
|
+
inset,
|
|
1518
|
+
...listProps
|
|
1519
|
+
} = props
|
|
1520
|
+
|
|
1521
|
+
return <List {...listProps} />
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// Grid.Item maps to List.Item but with content instead of icon
|
|
1525
|
+
Grid.Item = (props: GridItemProps) => {
|
|
1526
|
+
const { content, getDetailMarkdown, ...itemProps } = props
|
|
1527
|
+
|
|
1528
|
+
// Extract image value and tooltip
|
|
1529
|
+
const imageValue = typeof content === 'string' ? content : content?.value
|
|
1530
|
+
const imageTooltip =
|
|
1531
|
+
typeof content === 'object' ? content?.tooltip : undefined
|
|
1532
|
+
|
|
1533
|
+
// Convert Grid.Item props to List.Item props
|
|
1534
|
+
const listItemProps: ItemProps = {
|
|
1535
|
+
...itemProps,
|
|
1536
|
+
// Grid items don't have accessories in Raycast
|
|
1537
|
+
accessories: undefined,
|
|
1538
|
+
// Use content as icon for now (in a real implementation, this would be rendered differently)
|
|
1539
|
+
icon: imageValue,
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
return <List.Item {...listItemProps} />
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// Grid.Section maps to List.Section with grid-specific props
|
|
1546
|
+
Grid.Section = (props: GridSectionProps) => {
|
|
1547
|
+
const { columns, aspectRatio, fit, inset, ...sectionProps } = props
|
|
1548
|
+
|
|
1549
|
+
// Pass through to List.Section
|
|
1550
|
+
return <List.Section {...sectionProps} />
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// Reuse List's Dropdown
|
|
1554
|
+
Grid.Dropdown = List.Dropdown
|
|
1555
|
+
|
|
1556
|
+
// Reuse List's EmptyView
|
|
1557
|
+
Grid.EmptyView = List.EmptyView
|
|
1558
|
+
|
|
1559
|
+
// Grid Inset presets
|
|
1560
|
+
Grid.Inset = {
|
|
1561
|
+
Small: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
1562
|
+
Medium: { top: 8, right: 8, bottom: 8, left: 8 },
|
|
1563
|
+
Large: { top: 16, right: 16, bottom: 16, left: 16 },
|
|
1564
|
+
}
|