termcast 1.3.32 → 1.3.34
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.map +1 -1
- package/dist/action-utils.js +8 -0
- package/dist/action-utils.js.map +1 -1
- package/dist/apis/cache.d.ts +1 -2
- package/dist/apis/cache.d.ts.map +1 -1
- package/dist/apis/cache.js +138 -54
- package/dist/apis/cache.js.map +1 -1
- package/dist/apis/clipboard.d.ts.map +1 -1
- package/dist/apis/clipboard.js +4 -0
- package/dist/apis/clipboard.js.map +1 -1
- package/dist/apis/oauth.d.ts.map +1 -1
- package/dist/apis/oauth.js +31 -4
- package/dist/apis/oauth.js.map +1 -1
- package/dist/build.d.ts +0 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +30 -51
- package/dist/build.js.map +1 -1
- package/dist/cli.js +31 -14
- package/dist/cli.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +5 -1
- package/dist/compile.js.map +1 -1
- package/dist/components/actions.d.ts +14 -0
- package/dist/components/actions.d.ts.map +1 -1
- package/dist/components/actions.js +151 -59
- package/dist/components/actions.js.map +1 -1
- package/dist/components/alert.d.ts.map +1 -1
- package/dist/components/alert.js +6 -5
- package/dist/components/alert.js.map +1 -1
- package/dist/components/animation-tick.d.ts +1 -1
- package/dist/components/animation-tick.js +1 -1
- package/dist/components/animation-tick.js.map +1 -1
- package/dist/components/detail.d.ts +5 -31
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +36 -52
- package/dist/components/detail.js.map +1 -1
- package/dist/components/dropdown.d.ts +1 -1
- package/dist/components/dropdown.d.ts.map +1 -1
- package/dist/components/dropdown.js +50 -22
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/footer.d.ts.map +1 -1
- package/dist/components/footer.js +19 -18
- package/dist/components/footer.js.map +1 -1
- package/dist/components/form/checkbox.d.ts.map +1 -1
- package/dist/components/form/checkbox.js +12 -11
- 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 +7 -22
- package/dist/components/form/date-picker.js.map +1 -1
- package/dist/components/form/description.d.ts +1 -1
- package/dist/components/form/description.d.ts.map +1 -1
- package/dist/components/form/description.js +6 -5
- 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 +53 -50
- package/dist/components/form/dropdown.js.map +1 -1
- package/dist/components/form/file-autocomplete.d.ts.map +1 -1
- package/dist/components/form/file-autocomplete.js +5 -4
- 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 +23 -22
- 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 +6 -4
- package/dist/components/form/form-end.js.map +1 -1
- package/dist/components/form/form-field-wrapper.d.ts +15 -0
- package/dist/components/form/form-field-wrapper.d.ts.map +1 -0
- package/dist/components/form/form-field-wrapper.js +29 -0
- package/dist/components/form/form-field-wrapper.js.map +1 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +31 -30
- 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 +7 -6
- package/dist/components/form/password-field.js.map +1 -1
- package/dist/components/form/separator.d.ts.map +1 -1
- package/dist/components/form/separator.js +3 -2
- package/dist/components/form/separator.js.map +1 -1
- package/dist/components/form/tagpicker.d.ts.map +1 -1
- package/dist/components/form/tagpicker.js +2 -1
- package/dist/components/form/tagpicker.js.map +1 -1
- package/dist/components/form/text-area.d.ts.map +1 -1
- package/dist/components/form/text-area.js +7 -6
- 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 +7 -6
- package/dist/components/form/text-field.js.map +1 -1
- package/dist/components/form/use-form-navigation.d.ts.map +1 -1
- package/dist/components/form/use-form-navigation.js +4 -4
- package/dist/components/form/use-form-navigation.js.map +1 -1
- package/dist/components/form/with-left-border.d.ts +15 -0
- package/dist/components/form/with-left-border.d.ts.map +1 -1
- package/dist/components/form/with-left-border.js +21 -9
- package/dist/components/form/with-left-border.js.map +1 -1
- package/dist/components/icon.d.ts +14 -0
- package/dist/components/icon.d.ts.map +1 -1
- package/dist/components/icon.js +60 -0
- package/dist/components/icon.js.map +1 -1
- package/dist/components/image.d.ts +47 -2
- package/dist/components/image.d.ts.map +1 -1
- package/dist/components/image.js +46 -7
- package/dist/components/image.js.map +1 -1
- package/dist/components/list.d.ts +5 -0
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +188 -132
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-bar.d.ts.map +1 -1
- package/dist/components/loading-bar.js +4 -3
- package/dist/components/loading-bar.js.map +1 -1
- package/dist/components/metadata.d.ts +70 -0
- package/dist/components/metadata.d.ts.map +1 -0
- package/dist/components/metadata.js +82 -0
- package/dist/components/metadata.js.map +1 -0
- package/dist/components/theme-picker.d.ts.map +1 -1
- package/dist/components/theme-picker.js +3 -2
- package/dist/components/theme-picker.js.map +1 -1
- package/dist/descendants-v2.d.ts +60 -0
- package/dist/descendants-v2.d.ts.map +1 -0
- package/dist/descendants-v2.js +144 -0
- package/dist/descendants-v2.js.map +1 -0
- package/dist/examples/actions-context.d.ts +2 -0
- package/dist/examples/actions-context.d.ts.map +1 -0
- package/dist/examples/actions-context.js +33 -0
- package/dist/examples/actions-context.js.map +1 -0
- package/dist/examples/form-basic.d.ts.map +1 -1
- package/dist/examples/form-basic.js +1 -1
- package/dist/examples/form-basic.js.map +1 -1
- package/dist/examples/form-dropdown.js +1 -1
- package/dist/examples/form-dropdown.js.map +1 -1
- package/dist/examples/internal/custom-action-renderables.d.ts +70 -0
- package/dist/examples/internal/custom-action-renderables.d.ts.map +1 -0
- package/dist/examples/internal/custom-action-renderables.js +163 -0
- package/dist/examples/internal/custom-action-renderables.js.map +1 -0
- package/dist/examples/internal/custom-dropdown.d.ts +99 -0
- package/dist/examples/internal/custom-dropdown.d.ts.map +1 -0
- package/dist/examples/internal/custom-dropdown.js +270 -0
- package/dist/examples/internal/custom-dropdown.js.map +1 -0
- package/dist/examples/internal/custom-renderable-form.d.ts +43 -0
- package/dist/examples/internal/custom-renderable-form.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-form.js +284 -0
- package/dist/examples/internal/custom-renderable-form.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list-default-search.d.ts +2 -0
- package/dist/examples/internal/custom-renderable-list-default-search.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list-default-search.js +16 -0
- package/dist/examples/internal/custom-renderable-list-default-search.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.d.ts +2 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.js +24 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2.d.ts +189 -0
- package/dist/examples/internal/custom-renderable-list-v2.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2.js +708 -0
- package/dist/examples/internal/custom-renderable-list-v2.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list.d.ts +72 -0
- package/dist/examples/internal/custom-renderable-list.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list.js +544 -0
- package/dist/examples/internal/custom-renderable-list.js.map +1 -0
- package/dist/examples/internal/rhf-custom-ref.js +5 -4
- package/dist/examples/internal/rhf-custom-ref.js.map +1 -1
- package/dist/examples/internal/scrollbox-with-descendants.js +4 -2
- package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -1
- package/dist/examples/list-controlled-search.d.ts +2 -0
- package/dist/examples/list-controlled-search.d.ts.map +1 -0
- package/dist/examples/list-controlled-search.js +12 -0
- package/dist/examples/list-controlled-search.js.map +1 -0
- package/dist/examples/list-detail-metadata.js +1 -1
- package/dist/examples/list-detail-metadata.js.map +1 -1
- package/dist/examples/simple-image-mask.d.ts +8 -0
- package/dist/examples/simple-image-mask.d.ts.map +1 -0
- package/dist/examples/simple-image-mask.js +12 -0
- package/dist/examples/simple-image-mask.js.map +1 -0
- package/dist/examples/toast-variations.js +1 -1
- package/dist/examples/toast-variations.js.map +1 -1
- package/dist/extensions/dev.d.ts.map +1 -1
- package/dist/extensions/dev.js +3 -2
- package/dist/extensions/dev.js.map +1 -1
- package/dist/extensions/react-refresh-init.d.ts.map +1 -1
- package/dist/extensions/react-refresh-init.js +4 -3
- package/dist/extensions/react-refresh-init.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/date-picker-widget.d.ts.map +1 -1
- package/dist/internal/date-picker-widget.js +2 -1
- package/dist/internal/date-picker-widget.js.map +1 -1
- package/dist/internal/dialog.d.ts +6 -0
- package/dist/internal/dialog.d.ts.map +1 -1
- package/dist/internal/dialog.js +59 -18
- package/dist/internal/dialog.js.map +1 -1
- package/dist/internal/navigation.d.ts.map +1 -1
- package/dist/internal/navigation.js +8 -1
- package/dist/internal/navigation.js.map +1 -1
- package/dist/internal/offscreen.d.ts +3 -0
- package/dist/internal/offscreen.d.ts.map +1 -1
- package/dist/internal/offscreen.js +5 -0
- package/dist/internal/offscreen.js.map +1 -1
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +20 -3
- package/dist/internal/providers.js.map +1 -1
- package/dist/internal/scrollbox.d.ts.map +1 -1
- package/dist/internal/scrollbox.js +3 -2
- package/dist/internal/scrollbox.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -1
- package/dist/preload.js +5 -17
- package/dist/preload.js.map +1 -1
- package/dist/state.d.ts +4 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +4 -0
- package/dist/state.js.map +1 -1
- package/dist/test-border-overlay.d.ts +2 -0
- package/dist/test-border-overlay.d.ts.map +1 -0
- package/dist/test-border-overlay.js +7 -0
- package/dist/test-border-overlay.js.map +1 -0
- package/dist/test-layout-2.d.ts +2 -0
- package/dist/test-layout-2.d.ts.map +1 -0
- package/dist/test-layout-2.js +5 -0
- package/dist/test-layout-2.js.map +1 -0
- package/dist/test-layout.d.ts +2 -0
- package/dist/test-layout.d.ts.map +1 -0
- package/dist/test-layout.js +7 -0
- package/dist/test-layout.js.map +1 -0
- package/dist/theme.d.ts +1 -2
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +5 -9
- package/dist/theme.js.map +1 -1
- package/dist/utils/run-command.d.ts +1 -1
- package/dist/utils/run-command.d.ts.map +1 -1
- package/dist/utils/run-command.js +27 -7
- package/dist/utils/run-command.js.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +44 -23
- package/dist/utils.js.map +1 -1
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +24 -4
- package/dist/watcher.js.map +1 -1
- package/package.json +14 -12
- package/src/action-utils.tsx +10 -0
- package/src/apis/cache.test.ts +35 -3
- package/src/apis/cache.tsx +184 -59
- package/src/apis/clipboard.tsx +5 -0
- package/src/apis/oauth.tsx +33 -4
- package/src/build.tsx +35 -58
- package/src/cli.tsx +156 -134
- package/src/compile.tsx +6 -3
- package/src/compile.vitest.tsx +33 -15
- package/src/components/actions.tsx +230 -99
- package/src/components/alert.tsx +11 -10
- package/src/components/animation-tick.tsx +1 -1
- package/src/components/detail.tsx +56 -151
- package/src/components/dropdown.tsx +70 -36
- package/src/components/footer.tsx +58 -33
- package/src/components/form/checkbox.tsx +30 -32
- package/src/components/form/date-picker.tsx +27 -47
- package/src/components/form/description.tsx +19 -18
- package/src/components/form/dropdown.tsx +95 -103
- package/src/components/form/file-autocomplete.tsx +9 -8
- package/src/components/form/file-picker.tsx +46 -46
- package/src/components/form/form-end.tsx +6 -4
- package/src/components/form/index.tsx +38 -48
- package/src/components/form/password-field.tsx +25 -27
- package/src/components/form/separator.tsx +3 -2
- package/src/components/form/tagpicker.tsx +2 -1
- package/src/components/form/text-area.tsx +25 -30
- package/src/components/form/text-field.tsx +25 -27
- package/src/components/form/use-form-navigation.tsx +4 -5
- package/src/components/form/with-left-border.tsx +48 -10
- package/src/components/icon.tsx +69 -0
- package/src/components/image.tsx +60 -7
- package/src/components/list.tsx +270 -202
- package/src/components/loading-bar.tsx +4 -3
- package/src/components/metadata.tsx +217 -0
- package/src/components/theme-picker.tsx +3 -2
- package/src/examples/actions-context.tsx +63 -0
- package/src/examples/actions-context.vitest.tsx +110 -0
- package/src/examples/actions-dialog-layout.vitest.tsx +2 -1
- package/src/examples/file-autocomplete.vitest.tsx +15 -15
- package/src/examples/form-basic.tsx +12 -0
- package/src/examples/form-basic.vitest.tsx +74 -74
- package/src/examples/form-dropdown.tsx +8 -0
- package/src/examples/form-dropdown.vitest.tsx +364 -421
- package/src/examples/form-tagpicker.vitest.tsx +56 -54
- package/src/examples/github.vitest.tsx +252 -0
- package/src/examples/internal/rhf-custom-ref.tsx +16 -15
- package/src/examples/internal/scrollbox-with-descendants.tsx +4 -2
- package/src/examples/internal/simple-dialog.tsx +1 -1
- package/src/examples/internal/simple-scrollbox.vitest.tsx +14 -9
- package/src/examples/list-controlled-search.tsx +28 -0
- package/src/examples/list-controlled-search.vitest.tsx +49 -0
- package/src/examples/list-detail-metadata.tsx +8 -5
- package/src/examples/list-detail-metadata.vitest.tsx +22 -22
- package/src/examples/list-dropdown-default.vitest.tsx +12 -12
- package/src/examples/list-scrollbox.vitest.tsx +52 -38
- package/src/examples/list-with-detail.vitest.tsx +45 -41
- package/src/examples/list-with-dropdown.vitest.tsx +5 -5
- package/src/examples/list-with-sections.vitest.tsx +65 -12
- package/src/examples/list-with-toast.vitest.tsx +4 -4
- package/src/examples/simple-file-picker.vitest.tsx +12 -12
- package/src/examples/simple-grid.vitest.tsx +53 -53
- package/src/examples/simple-image-mask.tsx +58 -0
- package/src/examples/simple-navigation.vitest.tsx +19 -19
- package/src/examples/store.vitest.tsx +1 -1
- package/src/examples/swift-extension.vitest.tsx +4 -2
- package/src/examples/synonyms.vitest.tsx +31 -9
- package/src/examples/toast-action.vitest.tsx +8 -8
- package/src/examples/toast-variations.tsx +1 -1
- package/src/examples/toast-variations.vitest.tsx +69 -134
- package/src/extensions/dev.tsx +3 -2
- package/src/extensions/dev.vitest.tsx +65 -28
- package/src/extensions/react-refresh-init.tsx +4 -3
- package/src/index.tsx +3 -1
- package/src/internal/date-picker-widget.tsx +2 -1
- package/src/internal/dialog.tsx +100 -28
- package/src/internal/navigation.tsx +8 -1
- package/src/internal/offscreen.tsx +10 -0
- package/src/internal/providers.tsx +34 -8
- package/src/internal/scrollbox.tsx +4 -2
- package/src/logger.tsx +4 -0
- package/src/preload.tsx +5 -17
- package/src/state.tsx +12 -0
- package/src/theme.tsx +6 -9
- package/src/utils/run-command.tsx +32 -8
- package/src/utils.tsx +58 -23
- package/src/watcher.tsx +26 -6
|
@@ -7,7 +7,7 @@ import React, {
|
|
|
7
7
|
useLayoutEffect,
|
|
8
8
|
} from 'react'
|
|
9
9
|
import { BoxRenderable, ScrollBoxRenderable } from '@opentui/core'
|
|
10
|
-
import { useKeyboard } from '@opentui/react'
|
|
10
|
+
import { useKeyboard, flushSync } from '@opentui/react'
|
|
11
11
|
import {
|
|
12
12
|
useFormContext,
|
|
13
13
|
Controller,
|
|
@@ -17,12 +17,12 @@ import {
|
|
|
17
17
|
import { useFocusContext, useFormFieldDescendant } from './index'
|
|
18
18
|
import { FormItemProps, FormItemRef } from './types'
|
|
19
19
|
import { logger } from 'termcast/src/logger'
|
|
20
|
-
import {
|
|
20
|
+
import { useTheme } from 'termcast/src/theme'
|
|
21
21
|
import {
|
|
22
22
|
createDescendants,
|
|
23
23
|
DescendantContextType,
|
|
24
24
|
} from 'termcast/src/descendants'
|
|
25
|
-
import { WithLeftBorder } from './with-left-border'
|
|
25
|
+
import { WithLeftBorder, TitleIndicator } from './with-left-border'
|
|
26
26
|
import { useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
27
27
|
import { useFormNavigationHelpers } from './use-form-navigation'
|
|
28
28
|
import { LoadingText } from 'termcast/src/components/loading-text'
|
|
@@ -103,6 +103,7 @@ const FormDropdownContext = createContext<FormDropdownContextValue>({
|
|
|
103
103
|
})
|
|
104
104
|
|
|
105
105
|
const DropdownItem = (props: DropdownItemProps) => {
|
|
106
|
+
const theme = useTheme()
|
|
106
107
|
const context = useContext(FormDropdownContext)
|
|
107
108
|
const sectionContext = useContext(SectionContext)
|
|
108
109
|
const elementRef = useRef<BoxRenderable | null>(null)
|
|
@@ -138,32 +139,27 @@ const DropdownItem = (props: DropdownItemProps) => {
|
|
|
138
139
|
|
|
139
140
|
return (
|
|
140
141
|
<box ref={elementRef} key={props.value}>
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
<text
|
|
143
|
+
fg={
|
|
144
|
+
context.isFocused && isFocused
|
|
145
|
+
? theme.accent
|
|
146
|
+
: context.isFocused
|
|
147
|
+
? theme.text
|
|
148
|
+
: theme.textMuted
|
|
149
|
+
}
|
|
150
|
+
onMouseDown={() => {
|
|
151
|
+
context.handleSelect(descendant.descendantId)
|
|
152
|
+
}}
|
|
145
153
|
>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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>
|
|
154
|
+
{context.isFocused && isFocused ? '› ' : ' '}
|
|
155
|
+
{isSelected ? '●' : '○'} {props.title}
|
|
156
|
+
</text>
|
|
162
157
|
</box>
|
|
163
158
|
)
|
|
164
159
|
}
|
|
165
160
|
|
|
166
161
|
const DropdownSection = (props: DropdownSectionProps) => {
|
|
162
|
+
const theme = useTheme()
|
|
167
163
|
const parentContext = useContext(FormDropdownContext)
|
|
168
164
|
|
|
169
165
|
// Create section context value
|
|
@@ -178,13 +174,7 @@ const DropdownSection = (props: DropdownSectionProps) => {
|
|
|
178
174
|
<SectionContext.Provider value={sectionContextValue}>
|
|
179
175
|
<box flexDirection='column'>
|
|
180
176
|
{props.title && (
|
|
181
|
-
<
|
|
182
|
-
paddingTop={0}
|
|
183
|
-
paddingBottom={0}
|
|
184
|
-
isFocused={parentContext.isFocused}
|
|
185
|
-
>
|
|
186
|
-
<text fg={Theme.textMuted}>{props.title}</text>
|
|
187
|
-
</WithLeftBorder>
|
|
177
|
+
<text fg={theme.textMuted}> {props.title}</text>
|
|
188
178
|
)}
|
|
189
179
|
{props.children}
|
|
190
180
|
</box>
|
|
@@ -203,6 +193,7 @@ const DropdownContent = ({
|
|
|
203
193
|
fieldState,
|
|
204
194
|
...props
|
|
205
195
|
}: DropdownContentProps) => {
|
|
196
|
+
const theme = useTheme()
|
|
206
197
|
const descendantsContext = useFormDropdownDescendants()
|
|
207
198
|
const isInFocus = useIsInFocus()
|
|
208
199
|
const focusContext = useFocusContext()
|
|
@@ -224,10 +215,6 @@ const DropdownContent = ({
|
|
|
224
215
|
|
|
225
216
|
const [selectedTitles, setSelectedTitles] = useState<string[]>([])
|
|
226
217
|
|
|
227
|
-
const { navigateToPrevious, navigateToNext } = useFormNavigationHelpers(
|
|
228
|
-
props.id,
|
|
229
|
-
)
|
|
230
|
-
|
|
231
218
|
const scrollToItem = (item: { props?: FormDropdownItemDescendant }) => {
|
|
232
219
|
const scrollBox = scrollBoxRef.current
|
|
233
220
|
const itemElementRef = item.props?.elementRef
|
|
@@ -244,11 +231,11 @@ const DropdownContent = ({
|
|
|
244
231
|
scrollBox.scrollTo(Math.max(0, targetScrollTop))
|
|
245
232
|
}
|
|
246
233
|
|
|
247
|
-
// Helper to get value for a descendantId
|
|
234
|
+
// Helper to get value for a descendantId - use committedMap for stability
|
|
248
235
|
const getValueForDescendantId = (
|
|
249
236
|
descendantId: string,
|
|
250
237
|
): string | undefined => {
|
|
251
|
-
const item = descendantsContext.
|
|
238
|
+
const item = descendantsContext.committedMap[descendantId]
|
|
252
239
|
return item?.props?.value
|
|
253
240
|
}
|
|
254
241
|
|
|
@@ -256,7 +243,7 @@ const DropdownContent = ({
|
|
|
256
243
|
const value = getValueForDescendantId(descendantId)
|
|
257
244
|
if (!value) return
|
|
258
245
|
|
|
259
|
-
const item = descendantsContext.
|
|
246
|
+
const item = descendantsContext.committedMap[descendantId]
|
|
260
247
|
const title = item?.props?.title || value
|
|
261
248
|
|
|
262
249
|
if (props.hasMultipleSelection) {
|
|
@@ -290,7 +277,7 @@ const DropdownContent = ({
|
|
|
290
277
|
useKeyboard((evt) => {
|
|
291
278
|
if (!isFocused || !isInFocus) return
|
|
292
279
|
|
|
293
|
-
const items = Object.values(descendantsContext.
|
|
280
|
+
const items = Object.values(descendantsContext.committedMap)
|
|
294
281
|
.filter((item) => item.index !== -1)
|
|
295
282
|
.sort((a, b) => a.index - b.index)
|
|
296
283
|
const itemCount = items.length
|
|
@@ -298,21 +285,29 @@ const DropdownContent = ({
|
|
|
298
285
|
if (itemCount > 0) {
|
|
299
286
|
if (evt.name === 'down') {
|
|
300
287
|
const nextIndex = (focusedIndex + 1) % itemCount
|
|
301
|
-
setFocusedIndex(nextIndex)
|
|
302
288
|
const nextItem = items[nextIndex]
|
|
303
289
|
if (nextItem) {
|
|
290
|
+
flushSync(() => {
|
|
291
|
+
setFocusedIndex(nextIndex)
|
|
292
|
+
})
|
|
304
293
|
scrollToItem(nextItem)
|
|
305
294
|
}
|
|
306
295
|
} else if (evt.name === 'up') {
|
|
307
296
|
const nextIndex = (focusedIndex - 1 + itemCount) % itemCount
|
|
308
|
-
setFocusedIndex(nextIndex)
|
|
309
297
|
const nextItem = items[nextIndex]
|
|
310
298
|
if (nextItem) {
|
|
299
|
+
flushSync(() => {
|
|
300
|
+
setFocusedIndex(nextIndex)
|
|
301
|
+
})
|
|
311
302
|
scrollToItem(nextItem)
|
|
312
303
|
}
|
|
313
|
-
} else if (
|
|
314
|
-
|
|
315
|
-
|
|
304
|
+
} else if (
|
|
305
|
+
(evt.name === 'return' || evt.name === 'space') &&
|
|
306
|
+
!evt.ctrl &&
|
|
307
|
+
!evt.meta
|
|
308
|
+
) {
|
|
309
|
+
// Toggle selection of current focused item (but not when ctrl/meta is held for form submission)
|
|
310
|
+
const entries = Object.entries(descendantsContext.committedMap)
|
|
316
311
|
const sortedEntries = entries
|
|
317
312
|
.filter(([_, item]) => item.index !== -1)
|
|
318
313
|
.sort((a, b) => a[1].index - b[1].index)
|
|
@@ -324,16 +319,6 @@ const DropdownContent = ({
|
|
|
324
319
|
}
|
|
325
320
|
}
|
|
326
321
|
|
|
327
|
-
// Handle tab navigation
|
|
328
|
-
if (evt.name === 'tab') {
|
|
329
|
-
if (evt.shift) {
|
|
330
|
-
navigateToPrevious()
|
|
331
|
-
} else {
|
|
332
|
-
navigateToNext()
|
|
333
|
-
}
|
|
334
|
-
return
|
|
335
|
-
}
|
|
336
|
-
|
|
337
322
|
// Type-ahead search
|
|
338
323
|
if (
|
|
339
324
|
evt.name.length === 1 &&
|
|
@@ -358,7 +343,9 @@ const DropdownContent = ({
|
|
|
358
343
|
})
|
|
359
344
|
|
|
360
345
|
if (matchingItem) {
|
|
361
|
-
|
|
346
|
+
flushSync(() => {
|
|
347
|
+
setFocusedIndex(matchingItem.index)
|
|
348
|
+
})
|
|
362
349
|
scrollToItem(matchingItem)
|
|
363
350
|
}
|
|
364
351
|
|
|
@@ -370,9 +357,9 @@ const DropdownContent = ({
|
|
|
370
357
|
|
|
371
358
|
// Initialize selected titles from field value only once when descendants are loaded
|
|
372
359
|
useLayoutEffect(() => {
|
|
373
|
-
if (field.value && Object.keys(descendantsContext.
|
|
360
|
+
if (field.value && Object.keys(descendantsContext.committedMap).length > 0) {
|
|
374
361
|
const titles: string[] = []
|
|
375
|
-
const entries = Object.entries(descendantsContext.
|
|
362
|
+
const entries = Object.entries(descendantsContext.committedMap)
|
|
376
363
|
|
|
377
364
|
entries.forEach(([id, item]) => {
|
|
378
365
|
if (item.props) {
|
|
@@ -410,23 +397,23 @@ const DropdownContent = ({
|
|
|
410
397
|
<FormDropdownDescendantsProvider value={descendantsContext}>
|
|
411
398
|
<FormDropdownContext.Provider value={contextValue}>
|
|
412
399
|
<box ref={elementRef} flexDirection='column'>
|
|
413
|
-
<WithLeftBorder
|
|
414
|
-
<
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
<LoadingText
|
|
420
|
-
isLoading={isFocused && focusContext.isLoading}
|
|
421
|
-
color={isFocused ? Theme.primary : Theme.text}
|
|
400
|
+
<WithLeftBorder isFocused={isFocused} paddingBottom={1}>
|
|
401
|
+
<TitleIndicator isFocused={isFocused} isLoading={focusContext.isLoading}>
|
|
402
|
+
<box
|
|
403
|
+
onMouseDown={() => {
|
|
404
|
+
setFocusedField(props.id)
|
|
405
|
+
}}
|
|
422
406
|
>
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
407
|
+
<LoadingText
|
|
408
|
+
isLoading={isFocused && focusContext.isLoading}
|
|
409
|
+
color={isFocused ? theme.primary : theme.text}
|
|
410
|
+
>
|
|
411
|
+
{props.title || ''}
|
|
412
|
+
</LoadingText>
|
|
413
|
+
</box>
|
|
414
|
+
</TitleIndicator>
|
|
428
415
|
<text
|
|
429
|
-
fg={selectedTitles.length > 0 ?
|
|
416
|
+
fg={selectedTitles.length > 0 ? theme.text : theme.textMuted}
|
|
430
417
|
selectable={false}
|
|
431
418
|
onMouseDown={() => {
|
|
432
419
|
setFocusedField(props.id)
|
|
@@ -436,40 +423,45 @@ const DropdownContent = ({
|
|
|
436
423
|
? selectedTitles.join(', ')
|
|
437
424
|
: props.placeholder || 'Select...'}
|
|
438
425
|
</text>
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
426
|
+
{props.children && (
|
|
427
|
+
<>
|
|
428
|
+
<box height={1} />
|
|
429
|
+
<box marginLeft={-2}>
|
|
430
|
+
<scrollbox
|
|
431
|
+
ref={scrollBoxRef}
|
|
432
|
+
maxHeight={5}
|
|
433
|
+
flexShrink={1}
|
|
434
|
+
style={{
|
|
435
|
+
rootOptions: {
|
|
436
|
+
flexShrink: 1,
|
|
437
|
+
},
|
|
438
|
+
viewportOptions: {
|
|
439
|
+
flexShrink: 1,
|
|
440
|
+
},
|
|
441
|
+
contentOptions: {
|
|
442
|
+
flexShrink: 0,
|
|
443
|
+
minHeight: 0,
|
|
444
|
+
},
|
|
445
|
+
scrollbarOptions: {
|
|
446
|
+
visible: false,
|
|
447
|
+
},
|
|
448
|
+
}}
|
|
449
|
+
>
|
|
450
|
+
{props.children}
|
|
451
|
+
</scrollbox>
|
|
452
|
+
</box>
|
|
453
|
+
<box height={1} />
|
|
454
|
+
</>
|
|
455
|
+
)}
|
|
456
|
+
{(fieldState.error || props.error) && (
|
|
457
|
+
<text fg={theme.error}>
|
|
464
458
|
{fieldState.error?.message || props.error}
|
|
465
459
|
</text>
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
</WithLeftBorder>
|
|
472
|
-
)}
|
|
460
|
+
)}
|
|
461
|
+
{props.info && (
|
|
462
|
+
<text fg={theme.textMuted}>{props.info}</text>
|
|
463
|
+
)}
|
|
464
|
+
</WithLeftBorder>
|
|
473
465
|
</box>
|
|
474
466
|
</FormDropdownContext.Provider>
|
|
475
467
|
</FormDropdownDescendantsProvider>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { useQuery } from '@tanstack/react-query'
|
|
3
|
-
import {
|
|
3
|
+
import { useTheme } from 'termcast/src/theme'
|
|
4
4
|
import { searchFiles, parsePath } from '../../utils/file-system'
|
|
5
5
|
import { useKeyboard } from '@opentui/react'
|
|
6
6
|
import { useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
@@ -50,6 +50,7 @@ export const FileAutocompleteDialog = ({
|
|
|
50
50
|
canChooseDirectories = false,
|
|
51
51
|
initialDirectory,
|
|
52
52
|
}: FileAutocompleteDialogProps): any => {
|
|
53
|
+
const theme = useTheme()
|
|
53
54
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
54
55
|
const isInFocus = useIsInFocus()
|
|
55
56
|
|
|
@@ -119,16 +120,16 @@ export const FileAutocompleteDialog = ({
|
|
|
119
120
|
return (
|
|
120
121
|
<box flexDirection='column' paddingLeft={1} paddingRight={1} overflow='hidden'>
|
|
121
122
|
<box flexDirection='row'>
|
|
122
|
-
<text fg={
|
|
123
|
-
<text fg={
|
|
123
|
+
<text fg={theme.textMuted} wrapMode='none'>Filter: </text>
|
|
124
|
+
<text fg={theme.primary} wrapMode='none'>
|
|
124
125
|
{filter ? filter.replace(homedir, '~') : '(type to filter)'}
|
|
125
126
|
</text>
|
|
126
127
|
</box>
|
|
127
128
|
<box height={1} />
|
|
128
129
|
{isLoading ? (
|
|
129
|
-
<text fg={
|
|
130
|
+
<text fg={theme.textMuted}>Loading files...</text>
|
|
130
131
|
) : visibleFiles.length === 0 ? (
|
|
131
|
-
<text fg={
|
|
132
|
+
<text fg={theme.textMuted}>No files found</text>
|
|
132
133
|
) : (
|
|
133
134
|
<>
|
|
134
135
|
{visibleFiles.map((item, index) => {
|
|
@@ -136,8 +137,8 @@ export const FileAutocompleteDialog = ({
|
|
|
136
137
|
return (
|
|
137
138
|
<text
|
|
138
139
|
key={item.path}
|
|
139
|
-
fg={index === selectedIndex ?
|
|
140
|
-
bg={index === selectedIndex ?
|
|
140
|
+
fg={index === selectedIndex ? theme.background : theme.text}
|
|
141
|
+
bg={index === selectedIndex ? theme.primary : theme.backgroundPanel}
|
|
141
142
|
wrapMode='none'
|
|
142
143
|
>
|
|
143
144
|
{' '}{icon}{item.name}{item.isDirectory ? '/' : ''}
|
|
@@ -147,7 +148,7 @@ export const FileAutocompleteDialog = ({
|
|
|
147
148
|
</>
|
|
148
149
|
)}
|
|
149
150
|
<box height={1} />
|
|
150
|
-
<text fg={
|
|
151
|
+
<text fg={theme.textMuted} wrapMode='none'>{hintText}</text>
|
|
151
152
|
</box>
|
|
152
153
|
)
|
|
153
154
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React, { useRef } from 'react'
|
|
2
|
-
import {
|
|
1
|
+
import React, { useRef, useCallback } from 'react'
|
|
2
|
+
import { useTheme } from 'termcast/src/theme'
|
|
3
3
|
import { BoxRenderable, TextareaRenderable } from '@opentui/core'
|
|
4
|
-
import { WithLeftBorder } from './with-left-border'
|
|
4
|
+
import { WithLeftBorder, TitleIndicator } from './with-left-border'
|
|
5
5
|
import { FormItemProps, FormItemRef } from './types'
|
|
6
6
|
import { useFormContext, Controller } from 'react-hook-form'
|
|
7
7
|
import { useFocusContext, useFormFieldDescendant } from './index'
|
|
@@ -11,6 +11,7 @@ import { FileAutocompleteDialog, createFileAutocompleteStore } from './file-auto
|
|
|
11
11
|
import { useFormNavigationHelpers } from './use-form-navigation'
|
|
12
12
|
import { useDialog } from 'termcast/src/internal/dialog'
|
|
13
13
|
import { LoadingText } from 'termcast/src/components/loading-text'
|
|
14
|
+
import { useStore } from 'termcast/src/state'
|
|
14
15
|
|
|
15
16
|
export interface FilePickerProps extends FormItemProps<string[]> {
|
|
16
17
|
/**
|
|
@@ -64,8 +65,26 @@ const FilePickerField = ({
|
|
|
64
65
|
setFocusedField: (id: string) => void
|
|
65
66
|
isFormLoading: boolean
|
|
66
67
|
}): any => {
|
|
68
|
+
const theme = useTheme()
|
|
67
69
|
const isInFocus = useIsInFocus()
|
|
68
70
|
const inputRef = React.useRef<TextareaRenderable>(null)
|
|
71
|
+
|
|
72
|
+
// Ref callback that registers the textarea in global state for ESC handling
|
|
73
|
+
const setInputRef = useCallback((node: TextareaRenderable | null) => {
|
|
74
|
+
if (!node) return
|
|
75
|
+
|
|
76
|
+
inputRef.current = node
|
|
77
|
+
useStore.setState({ activeSearchInputRef: node })
|
|
78
|
+
|
|
79
|
+
// React 19: return cleanup function for unmount
|
|
80
|
+
return () => {
|
|
81
|
+
if (useStore.getState().activeSearchInputRef === node) {
|
|
82
|
+
useStore.setState({ activeSearchInputRef: null })
|
|
83
|
+
}
|
|
84
|
+
inputRef.current = null
|
|
85
|
+
}
|
|
86
|
+
}, [])
|
|
87
|
+
|
|
69
88
|
const dialog = useDialog()
|
|
70
89
|
|
|
71
90
|
// Create store once for sharing state with dialog
|
|
@@ -150,24 +169,24 @@ const FilePickerField = ({
|
|
|
150
169
|
|
|
151
170
|
return (
|
|
152
171
|
<box flexDirection='column'>
|
|
153
|
-
<WithLeftBorder
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
<LoadingText
|
|
160
|
-
isLoading={isFocused && isFormLoading}
|
|
161
|
-
color={isFocused ? Theme.primary : Theme.text}
|
|
172
|
+
<WithLeftBorder isFocused={isFocused} paddingBottom={1}>
|
|
173
|
+
<TitleIndicator isFocused={isFocused} isLoading={isFormLoading}>
|
|
174
|
+
<box
|
|
175
|
+
onMouseDown={() => {
|
|
176
|
+
setFocusedField(props.id)
|
|
177
|
+
}}
|
|
162
178
|
>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
179
|
+
<LoadingText
|
|
180
|
+
isLoading={isFocused && isFormLoading}
|
|
181
|
+
color={isFocused ? theme.primary : theme.text}
|
|
182
|
+
>
|
|
183
|
+
{props.title || 'File Path'}
|
|
184
|
+
</LoadingText>
|
|
185
|
+
</box>
|
|
186
|
+
</TitleIndicator>
|
|
168
187
|
<box flexDirection='column'>
|
|
169
188
|
<textarea
|
|
170
|
-
ref={
|
|
189
|
+
ref={setInputRef}
|
|
171
190
|
height={1}
|
|
172
191
|
wrapMode='none'
|
|
173
192
|
keyBindings={[
|
|
@@ -189,28 +208,24 @@ const FilePickerField = ({
|
|
|
189
208
|
/>
|
|
190
209
|
{selectedFiles.length > 0 && (
|
|
191
210
|
<box flexDirection='column' marginTop={1}>
|
|
192
|
-
<text fg={
|
|
211
|
+
<text fg={theme.textMuted}>Selected files:</text>
|
|
193
212
|
{selectedFiles.map((file: string, index: number) => (
|
|
194
|
-
<text key={index} fg={
|
|
213
|
+
<text key={index} fg={theme.text}>
|
|
195
214
|
• {file}
|
|
196
215
|
</text>
|
|
197
216
|
))}
|
|
198
217
|
</box>
|
|
199
218
|
)}
|
|
200
219
|
</box>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
<WithLeftBorder isFocused={isFocused}>
|
|
204
|
-
<text fg={Theme.error}>
|
|
220
|
+
{(fieldState.error || props.error) && (
|
|
221
|
+
<text fg={theme.error}>
|
|
205
222
|
{fieldState.error?.message || props.error}
|
|
206
223
|
</text>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
</WithLeftBorder>
|
|
213
|
-
)}
|
|
224
|
+
)}
|
|
225
|
+
{props.info && (
|
|
226
|
+
<text fg={theme.textMuted}>{props.info}</text>
|
|
227
|
+
)}
|
|
228
|
+
</WithLeftBorder>
|
|
214
229
|
</box>
|
|
215
230
|
) as React.ReactElement
|
|
216
231
|
}
|
|
@@ -230,21 +245,6 @@ export const FilePicker = (props: FilePickerProps): any => {
|
|
|
230
245
|
elementRef: elementRef.current,
|
|
231
246
|
})
|
|
232
247
|
|
|
233
|
-
const { navigateToPrevious, navigateToNext } = useFormNavigationHelpers(props.id)
|
|
234
|
-
|
|
235
|
-
// Handle keyboard navigation
|
|
236
|
-
useKeyboard((evt) => {
|
|
237
|
-
if (!isFocused || !isInFocus) return
|
|
238
|
-
|
|
239
|
-
if (evt.name === 'tab') {
|
|
240
|
-
if (evt.shift) {
|
|
241
|
-
navigateToPrevious()
|
|
242
|
-
} else {
|
|
243
|
-
navigateToNext()
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
})
|
|
247
|
-
|
|
248
248
|
return (
|
|
249
249
|
<Controller
|
|
250
250
|
name={props.id}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useState, useLayoutEffect } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { useTheme } from 'termcast/src/theme'
|
|
3
3
|
import { useFocusContext, useFormScrollContext } from './index'
|
|
4
4
|
|
|
5
5
|
export const FormEnd = (): any => {
|
|
6
|
+
const theme = useTheme()
|
|
6
7
|
const { focusedField } = useFocusContext()
|
|
7
8
|
const scrollContext = useFormScrollContext()
|
|
8
9
|
const [isLastFieldFocused, setIsLastFieldFocused] = useState(false)
|
|
@@ -12,7 +13,8 @@ export const FormEnd = (): any => {
|
|
|
12
13
|
setIsLastFieldFocused(false)
|
|
13
14
|
return
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
// Use committedMap for stability
|
|
17
|
+
const descendants = Object.values(scrollContext.descendantsContext.committedMap)
|
|
16
18
|
.filter((item) => item.index !== -1 && item.props?.id)
|
|
17
19
|
.sort((a, b) => a.index - b.index)
|
|
18
20
|
if (descendants.length === 0) {
|
|
@@ -21,7 +23,7 @@ export const FormEnd = (): any => {
|
|
|
21
23
|
}
|
|
22
24
|
const lastField = descendants[descendants.length - 1]
|
|
23
25
|
setIsLastFieldFocused(lastField.props?.id === focusedField)
|
|
24
|
-
}, [focusedField])
|
|
26
|
+
}, [focusedField, scrollContext?.descendantsContext.committedMap])
|
|
25
27
|
|
|
26
|
-
return <text fg={isLastFieldFocused ?
|
|
28
|
+
return <text fg={isLastFieldFocused ? theme.accent : theme.text}>└</text>
|
|
27
29
|
}
|