termcast 1.3.30 → 1.3.32
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/apis/cache.d.ts.map +1 -1
- package/dist/apis/cache.js +4 -39
- package/dist/apis/cache.js.map +1 -1
- package/dist/apis/hud.d.ts.map +1 -1
- package/dist/apis/hud.js +13 -31
- package/dist/apis/hud.js.map +1 -1
- package/dist/apis/localstorage.d.ts.map +1 -1
- package/dist/apis/localstorage.js +3 -27
- package/dist/apis/localstorage.js.map +1 -1
- package/dist/apis/toast.d.ts +16 -43
- package/dist/apis/toast.d.ts.map +1 -1
- package/dist/apis/toast.js +78 -177
- package/dist/apis/toast.js.map +1 -1
- package/dist/build.d.ts +3 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +52 -2
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +206 -25
- package/dist/cli.js.map +1 -1
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +1 -0
- package/dist/colors.js.map +1 -1
- package/dist/compile.d.ts +0 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +18 -23
- package/dist/compile.js.map +1 -1
- package/dist/components/actions.d.ts.map +1 -1
- package/dist/components/actions.js +30 -15
- package/dist/components/actions.js.map +1 -1
- package/dist/components/animation-tick.d.ts +12 -0
- package/dist/components/animation-tick.d.ts.map +1 -0
- package/dist/components/animation-tick.js +63 -0
- package/dist/components/animation-tick.js.map +1 -0
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +10 -13
- package/dist/components/detail.js.map +1 -1
- package/dist/components/dropdown.d.ts +1 -0
- package/dist/components/dropdown.d.ts.map +1 -1
- package/dist/components/dropdown.js +27 -26
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/extension-preferences.d.ts.map +1 -1
- package/dist/components/extension-preferences.js +15 -10
- package/dist/components/extension-preferences.js.map +1 -1
- package/dist/components/footer.d.ts +13 -0
- package/dist/components/footer.d.ts.map +1 -0
- package/dist/components/footer.js +106 -0
- package/dist/components/footer.js.map +1 -0
- package/dist/components/form/file-autocomplete.d.ts +19 -4
- package/dist/components/form/file-autocomplete.d.ts.map +1 -1
- package/dist/components/form/file-autocomplete.js +56 -55
- 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 +26 -15
- package/dist/components/form/file-picker.js.map +1 -1
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +17 -15
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/form/with-left-border.d.ts.map +1 -1
- package/dist/components/form/with-left-border.js +4 -12
- package/dist/components/form/with-left-border.js.map +1 -1
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +126 -86
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-bar.d.ts.map +1 -1
- package/dist/components/loading-bar.js +5 -22
- package/dist/components/loading-bar.js.map +1 -1
- package/dist/components/loading-text.d.ts.map +1 -1
- package/dist/components/loading-text.js +3 -22
- package/dist/components/loading-text.js.map +1 -1
- package/dist/components/theme-picker.d.ts +2 -0
- package/dist/components/theme-picker.d.ts.map +1 -0
- package/dist/components/theme-picker.js +37 -0
- package/dist/components/theme-picker.js.map +1 -0
- package/dist/descendants.d.ts +6 -0
- package/dist/descendants.d.ts.map +1 -1
- package/dist/descendants.js +74 -8
- package/dist/descendants.js.map +1 -1
- package/dist/examples/internal/descendants-rerender.d.ts +14 -0
- package/dist/examples/internal/descendants-rerender.d.ts.map +1 -0
- package/dist/examples/internal/descendants-rerender.js +145 -0
- package/dist/examples/internal/descendants-rerender.js.map +1 -0
- package/dist/examples/internal/simple-dialog.js +4 -1
- package/dist/examples/internal/simple-dialog.js.map +1 -1
- package/dist/examples/internal/simple-scrollbox.js +1 -1
- package/dist/examples/internal/simple-scrollbox.js.map +1 -1
- package/dist/examples/list-with-dropdown.js +1 -1
- package/dist/examples/list-with-dropdown.js.map +1 -1
- package/dist/examples/miscellaneous.js +1 -1
- package/dist/examples/miscellaneous.js.map +1 -1
- package/dist/examples/toast-action.d.ts +2 -0
- package/dist/examples/toast-action.d.ts.map +1 -0
- package/dist/examples/toast-action.js +76 -0
- package/dist/examples/toast-action.js.map +1 -0
- package/dist/examples/toast-variations.js +38 -36
- package/dist/examples/toast-variations.js.map +1 -1
- package/dist/extensions/dev.d.ts +1 -1
- package/dist/extensions/dev.d.ts.map +1 -1
- package/dist/extensions/dev.js +62 -30
- package/dist/extensions/dev.js.map +1 -1
- package/dist/extensions/home.d.ts.map +1 -1
- package/dist/extensions/home.js +4 -3
- package/dist/extensions/home.js.map +1 -1
- package/dist/extensions/react-refresh-init.d.ts +5 -0
- package/dist/extensions/react-refresh-init.d.ts.map +1 -0
- package/dist/extensions/react-refresh-init.js +52 -0
- package/dist/extensions/react-refresh-init.js.map +1 -0
- package/dist/internal/date-picker-widget.js +1 -1
- package/dist/internal/date-picker-widget.js.map +1 -1
- package/dist/internal/dialog.d.ts +8 -3
- package/dist/internal/dialog.d.ts.map +1 -1
- package/dist/internal/dialog.js +37 -53
- package/dist/internal/dialog.js.map +1 -1
- package/dist/internal/navigation.d.ts +1 -0
- package/dist/internal/navigation.d.ts.map +1 -1
- package/dist/internal/navigation.js +25 -1
- package/dist/internal/navigation.js.map +1 -1
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +9 -197
- package/dist/internal/providers.js.map +1 -1
- package/dist/internal/scrollbox.d.ts.map +1 -1
- package/dist/internal/scrollbox.js +1 -0
- package/dist/internal/scrollbox.js.map +1 -1
- package/dist/release.d.ts +1 -0
- package/dist/release.d.ts.map +1 -1
- package/dist/release.js +16 -9
- package/dist/release.js.map +1 -1
- package/dist/state.d.ts +27 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +6 -0
- package/dist/state.js.map +1 -1
- package/dist/theme.d.ts +6 -19
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +76 -45
- package/dist/theme.js.map +1 -1
- package/dist/themes/aura.json +69 -0
- package/dist/themes/ayu.json +80 -0
- package/dist/themes/catppuccin-frappe.json +233 -0
- package/dist/themes/catppuccin-macchiato.json +233 -0
- package/dist/themes/catppuccin.json +112 -0
- package/dist/themes/cobalt2.json +228 -0
- package/dist/themes/cursor.json +249 -0
- package/dist/themes/dracula.json +219 -0
- package/dist/themes/everforest.json +241 -0
- package/dist/themes/flexoki.json +237 -0
- package/dist/themes/github-light.json +56 -0
- package/dist/themes/github.json +241 -0
- package/dist/themes/gruvbox.json +95 -0
- package/dist/themes/kanagawa.json +77 -0
- package/dist/themes/lucent-orng.json +227 -0
- package/dist/themes/material.json +235 -0
- package/dist/themes/matrix.json +77 -0
- package/dist/themes/mercury.json +245 -0
- package/dist/themes/monokai.json +221 -0
- package/dist/themes/nightowl.json +221 -0
- package/dist/themes/nord.json +223 -0
- package/dist/themes/one-dark.json +84 -0
- package/dist/themes/opencode-light.json +62 -0
- package/dist/themes/opencode.json +245 -0
- package/dist/themes/orng.json +245 -0
- package/dist/themes/palenight.json +222 -0
- package/dist/themes/rosepine.json +234 -0
- package/dist/themes/solarized.json +223 -0
- package/dist/themes/synthwave84.json +226 -0
- package/dist/themes/termcast.json +226 -0
- package/dist/themes/tokyonight.json +243 -0
- package/dist/themes/vercel.json +255 -0
- package/dist/themes/vesper.json +218 -0
- package/dist/themes/zenburn.json +223 -0
- package/dist/themes.d.ts +57 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +181 -0
- package/dist/themes.js.map +1 -0
- package/dist/utils/run-command.d.ts +2 -1
- package/dist/utils/run-command.d.ts.map +1 -1
- package/dist/utils/run-command.js +20 -10
- package/dist/utils/run-command.js.map +1 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +90 -17
- package/dist/utils.js.map +1 -1
- package/dist/watcher.d.ts +3 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +16 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +16 -10
- package/src/apis/cache.tsx +5 -44
- package/src/apis/hud.tsx +17 -62
- package/src/apis/localstorage.tsx +3 -32
- package/src/apis/toast.tsx +91 -275
- package/src/build.test.tsx +10 -0
- package/src/build.tsx +61 -1
- package/src/cli.tsx +365 -103
- package/src/colors.tsx +1 -0
- package/src/compile.tsx +21 -29
- package/src/compile.vitest.tsx +300 -0
- package/src/components/actions.tsx +64 -45
- package/src/components/animation-tick.tsx +85 -0
- package/src/components/detail.tsx +31 -35
- package/src/components/dropdown.tsx +32 -21
- package/src/components/extension-preferences.tsx +14 -10
- package/src/components/footer.tsx +241 -0
- package/src/components/form/file-autocomplete.tsx +80 -60
- package/src/components/form/file-picker.tsx +37 -25
- package/src/components/form/index.tsx +45 -41
- package/src/components/form/with-left-border.tsx +4 -14
- package/src/components/list.tsx +181 -121
- package/src/components/loading-bar.tsx +5 -25
- package/src/components/loading-text.tsx +4 -23
- package/src/components/theme-picker.tsx +57 -0
- package/src/descendants.tsx +98 -9
- package/src/examples/actions-dialog-layout.vitest.tsx +112 -0
- package/src/examples/file-autocomplete.vitest.tsx +131 -122
- package/src/examples/form-basic.vitest.tsx +463 -644
- package/src/examples/form-dropdown.vitest.tsx +553 -571
- package/src/examples/form-scroll.vitest.tsx +112 -102
- package/src/examples/form-tagpicker.vitest.tsx +364 -338
- package/src/examples/internal/descendants-rerender.tsx +273 -0
- package/src/examples/internal/descendants-rerender.vitest.tsx +194 -0
- package/src/examples/internal/simple-dialog.tsx +4 -4
- package/src/examples/internal/simple-scrollbox.tsx +2 -2
- package/src/examples/internal/simple-scrollbox.vitest.tsx +43 -31
- package/src/examples/list-detail-metadata.vitest.tsx +34 -30
- package/src/examples/list-dropdown-default.vitest.tsx +84 -72
- package/src/examples/list-empty-view.vitest.tsx +93 -0
- package/src/examples/list-fetch-data.vitest.tsx +36 -30
- package/src/examples/list-scrollbox.vitest.tsx +59 -39
- package/src/examples/list-with-detail.vitest.tsx +339 -314
- package/src/examples/list-with-dropdown.tsx +1 -0
- package/src/examples/list-with-dropdown.vitest.tsx +176 -150
- package/src/examples/list-with-sections.vitest.tsx +289 -270
- package/src/examples/list-with-toast.vitest.tsx +44 -44
- package/src/examples/miscellaneous.tsx +10 -0
- package/src/examples/simple-file-picker.vitest.tsx +90 -86
- package/src/examples/simple-grid.vitest.tsx +275 -249
- package/src/examples/simple-navigation.vitest.tsx +192 -168
- package/src/examples/store.vitest.tsx +6 -4
- package/src/examples/swift-extension.vitest.tsx +31 -19
- package/src/examples/synonyms.vitest.tsx +93 -83
- package/src/examples/toast-action.tsx +160 -0
- package/src/examples/toast-action.vitest.tsx +404 -0
- package/src/examples/toast-variations.tsx +58 -57
- package/src/examples/toast-variations.vitest.tsx +186 -166
- package/src/extensions/dev.tsx +74 -33
- package/src/extensions/dev.vitest.tsx +162 -69
- package/src/extensions/home.tsx +5 -6
- package/src/extensions/react-refresh-init.tsx +59 -0
- package/src/internal/date-picker-widget.tsx +1 -1
- package/src/internal/dialog.tsx +59 -83
- package/src/internal/navigation.tsx +37 -4
- package/src/internal/providers.tsx +27 -315
- package/src/internal/scrollbox.tsx +1 -0
- package/src/release.tsx +16 -10
- package/src/state.tsx +36 -3
- package/src/theme.tsx +82 -51
- package/src/themes/aura.json +69 -0
- package/src/themes/ayu.json +80 -0
- package/src/themes/catppuccin-frappe.json +233 -0
- package/src/themes/catppuccin-macchiato.json +233 -0
- package/src/themes/catppuccin.json +112 -0
- package/src/themes/cobalt2.json +228 -0
- package/src/themes/cursor.json +249 -0
- package/src/themes/dracula.json +219 -0
- package/src/themes/everforest.json +241 -0
- package/src/themes/flexoki.json +237 -0
- package/src/themes/github-light.json +56 -0
- package/src/themes/github.json +241 -0
- package/src/themes/gruvbox.json +95 -0
- package/src/themes/kanagawa.json +77 -0
- package/src/themes/lucent-orng.json +227 -0
- package/src/themes/material.json +235 -0
- package/src/themes/matrix.json +77 -0
- package/src/themes/mercury.json +252 -0
- package/src/themes/monokai.json +221 -0
- package/src/themes/nightowl.json +221 -0
- package/src/themes/nord.json +223 -0
- package/src/themes/one-dark.json +84 -0
- package/src/themes/opencode-light.json +62 -0
- package/src/themes/opencode.json +245 -0
- package/src/themes/orng.json +245 -0
- package/src/themes/palenight.json +222 -0
- package/src/themes/rosepine.json +234 -0
- package/src/themes/solarized.json +223 -0
- package/src/themes/synthwave84.json +226 -0
- package/src/themes/termcast.json +227 -0
- package/src/themes/tokyonight.json +243 -0
- package/src/themes/vercel.json +255 -0
- package/src/themes/vesper.json +218 -0
- package/src/themes/zenburn.json +223 -0
- package/src/themes.ts +291 -0
- package/src/utils/run-command.tsx +23 -12
- package/src/utils.tsx +115 -18
- package/src/watcher.tsx +19 -0
|
@@ -6,6 +6,7 @@ import { InFocus, useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
|
6
6
|
import { ActionPanel, Action } from 'termcast/src/components/actions'
|
|
7
7
|
import { Image } from 'termcast/src/components/list'
|
|
8
8
|
import { Color, resolveColor } from 'termcast/src/colors'
|
|
9
|
+
import { Footer } from 'termcast/src/components/footer'
|
|
9
10
|
|
|
10
11
|
import { useDialog } from 'termcast/src/internal/dialog'
|
|
11
12
|
import { ScrollBox } from 'termcast/src/internal/scrollbox'
|
|
@@ -198,37 +199,32 @@ function DetailFooter({
|
|
|
198
199
|
firstActionTitle?: string
|
|
199
200
|
}): any {
|
|
200
201
|
return (
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
paddingTop: 1,
|
|
207
|
-
marginTop: 1,
|
|
208
|
-
flexDirection: 'row',
|
|
209
|
-
}}
|
|
210
|
-
>
|
|
211
|
-
<text fg={Theme.text} attributes={TextAttributes.BOLD}>
|
|
212
|
-
esc
|
|
213
|
-
</text>
|
|
214
|
-
<text fg={Theme.textMuted}> go back</text>
|
|
215
|
-
{hasActions && (
|
|
216
|
-
<>
|
|
217
|
-
<text fg={Theme.text} attributes={TextAttributes.BOLD}>
|
|
218
|
-
{' '}^k
|
|
219
|
-
</text>
|
|
220
|
-
<text fg={Theme.textMuted}> actions</text>
|
|
221
|
-
</>
|
|
222
|
-
)}
|
|
223
|
-
{hasActions && firstActionTitle && (
|
|
224
|
-
<>
|
|
225
|
-
<text fg={Theme.text} attributes={TextAttributes.BOLD}>
|
|
226
|
-
{' '}↵
|
|
202
|
+
<Footer paddingLeft={0} paddingRight={0}>
|
|
203
|
+
<box style={{ flexDirection: 'row', gap: 3 }}>
|
|
204
|
+
<box style={{ flexDirection: 'row', gap: 1 }}>
|
|
205
|
+
<text flexShrink={0} fg={Theme.text} attributes={TextAttributes.BOLD}>
|
|
206
|
+
esc
|
|
227
207
|
</text>
|
|
228
|
-
<text fg={Theme.textMuted}>
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
208
|
+
<text flexShrink={0} fg={Theme.textMuted}>go back</text>
|
|
209
|
+
</box>
|
|
210
|
+
{hasActions && (
|
|
211
|
+
<box style={{ flexDirection: 'row', gap: 1 }}>
|
|
212
|
+
<text flexShrink={0} fg={Theme.text} attributes={TextAttributes.BOLD}>
|
|
213
|
+
^k
|
|
214
|
+
</text>
|
|
215
|
+
<text flexShrink={0} fg={Theme.textMuted}>actions</text>
|
|
216
|
+
</box>
|
|
217
|
+
)}
|
|
218
|
+
{hasActions && firstActionTitle && (
|
|
219
|
+
<box style={{ flexDirection: 'row', gap: 1 }}>
|
|
220
|
+
<text flexShrink={0} fg={Theme.text} attributes={TextAttributes.BOLD}>
|
|
221
|
+
↵
|
|
222
|
+
</text>
|
|
223
|
+
<text flexShrink={0} fg={Theme.textMuted}>{firstActionTitle}</text>
|
|
224
|
+
</box>
|
|
225
|
+
)}
|
|
226
|
+
</box>
|
|
227
|
+
</Footer>
|
|
232
228
|
)
|
|
233
229
|
}
|
|
234
230
|
|
|
@@ -277,20 +273,20 @@ const Detail: DetailType = (props) => {
|
|
|
277
273
|
useKeyboard((evt) => {
|
|
278
274
|
if (!inFocus) return
|
|
279
275
|
|
|
280
|
-
if (evt.name === 'k' && evt.ctrl
|
|
281
|
-
// Ctrl+K shows actions (always show
|
|
282
|
-
dialog.
|
|
276
|
+
if (evt.name === 'k' && evt.ctrl) {
|
|
277
|
+
// Ctrl+K shows actions (always show panel, even without actions)
|
|
278
|
+
dialog.pushActions(actions || <ActionPanel />)
|
|
283
279
|
} else if (evt.name === 'return' && actions) {
|
|
284
280
|
// Enter executes first action directly
|
|
285
281
|
useStore.setState({ shouldAutoExecuteFirstAction: true })
|
|
286
|
-
dialog.
|
|
282
|
+
dialog.pushActions(actions)
|
|
287
283
|
}
|
|
288
284
|
})
|
|
289
285
|
|
|
290
286
|
const content = (
|
|
291
287
|
<ScrollBox
|
|
292
288
|
focused={true}
|
|
293
|
-
flexGrow={1}
|
|
289
|
+
// flexGrow={1}
|
|
294
290
|
flexShrink={1}
|
|
295
291
|
style={{
|
|
296
292
|
rootOptions: {
|
|
@@ -7,7 +7,7 @@ import React, {
|
|
|
7
7
|
createContext,
|
|
8
8
|
useContext,
|
|
9
9
|
} from 'react'
|
|
10
|
-
import { useKeyboard } from '@opentui/react'
|
|
10
|
+
import { useKeyboard, flushSync } from '@opentui/react'
|
|
11
11
|
import {
|
|
12
12
|
TextAttributes,
|
|
13
13
|
ScrollBoxRenderable,
|
|
@@ -38,6 +38,7 @@ export interface DropdownProps extends SearchBarInterface, CommonProps {
|
|
|
38
38
|
defaultValue?: string
|
|
39
39
|
children?: ReactNode
|
|
40
40
|
onChange?: (newValue: string) => void
|
|
41
|
+
onSelectionChange?: (value: string) => void
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export interface DropdownItemProps extends CommonProps {
|
|
@@ -78,6 +79,7 @@ interface DropdownContextValue {
|
|
|
78
79
|
setSelectedIndex?: (index: number) => void
|
|
79
80
|
currentValue?: string
|
|
80
81
|
onChange?: (value: string) => void
|
|
82
|
+
onSelectionChange?: (value: string) => void
|
|
81
83
|
scrollBoxRef?: React.RefObject<ScrollBoxRenderable | null>
|
|
82
84
|
}
|
|
83
85
|
|
|
@@ -97,6 +99,7 @@ const Dropdown: DropdownType = (props) => {
|
|
|
97
99
|
const {
|
|
98
100
|
tooltip,
|
|
99
101
|
onChange,
|
|
102
|
+
onSelectionChange,
|
|
100
103
|
value,
|
|
101
104
|
defaultValue,
|
|
102
105
|
children,
|
|
@@ -120,19 +123,7 @@ const Dropdown: DropdownType = (props) => {
|
|
|
120
123
|
const scrollBoxRef = useRef<ScrollBoxRenderable>(null)
|
|
121
124
|
const descendantsContext = useDropdownDescendants()
|
|
122
125
|
|
|
123
|
-
// Update textarea and reset selection - single source of truth is the ref
|
|
124
|
-
const setSearchText = (text: string) => {
|
|
125
|
-
inputRef.current?.setText(text)
|
|
126
|
-
setSearchTextState(text)
|
|
127
|
-
// TODO: use flushSync when available to force descendants to update visibility
|
|
128
|
-
const items = Object.values(descendantsContext.map.current)
|
|
129
|
-
.filter((item: any) => item.index !== -1 && !item.props?.hidden)
|
|
130
|
-
.sort((a: any, b: any) => a.index - b.index)
|
|
131
126
|
|
|
132
|
-
if (items.length > 0 && items[0]) {
|
|
133
|
-
setSelected(items[0].index)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
127
|
|
|
137
128
|
const scrollToItem = (item: { props?: DropdownItemDescendant }) => {
|
|
138
129
|
const scrollBox = scrollBoxRef.current
|
|
@@ -160,9 +151,10 @@ const Dropdown: DropdownType = (props) => {
|
|
|
160
151
|
setSelectedIndex: setSelected,
|
|
161
152
|
currentValue,
|
|
162
153
|
onChange: (value: string) => selectItem(value),
|
|
154
|
+
onSelectionChange,
|
|
163
155
|
scrollBoxRef,
|
|
164
156
|
}),
|
|
165
|
-
[searchText, filtering, selected, currentValue],
|
|
157
|
+
[searchText, filtering, selected, currentValue, onSelectionChange],
|
|
166
158
|
)
|
|
167
159
|
|
|
168
160
|
// Update controlled value
|
|
@@ -176,9 +168,10 @@ const Dropdown: DropdownType = (props) => {
|
|
|
176
168
|
const handleSearchTextChange = (text: string) => {
|
|
177
169
|
if (!inFocus) return
|
|
178
170
|
|
|
179
|
-
// Update state for context
|
|
180
|
-
|
|
181
|
-
|
|
171
|
+
// Update state for context, using flushSync to force descendants to update visibility
|
|
172
|
+
flushSync(() => {
|
|
173
|
+
setSearchTextState(text)
|
|
174
|
+
})
|
|
182
175
|
const items = Object.values(descendantsContext.map.current)
|
|
183
176
|
.filter((item: any) => item.index !== -1 && !item.props?.hidden)
|
|
184
177
|
.sort((a: any, b: any) => a.index - b.index)
|
|
@@ -227,6 +220,9 @@ const Dropdown: DropdownType = (props) => {
|
|
|
227
220
|
if (nextItem) {
|
|
228
221
|
setSelected(nextItem.index)
|
|
229
222
|
scrollToItem(nextItem)
|
|
223
|
+
if (onSelectionChange && nextItem.props) {
|
|
224
|
+
onSelectionChange((nextItem.props as DropdownItemDescendant).value)
|
|
225
|
+
}
|
|
230
226
|
}
|
|
231
227
|
}
|
|
232
228
|
|
|
@@ -296,13 +292,15 @@ const Dropdown: DropdownType = (props) => {
|
|
|
296
292
|
justifyContent: 'space-between',
|
|
297
293
|
}}
|
|
298
294
|
>
|
|
299
|
-
<text
|
|
295
|
+
<text fg={Theme.textMuted}>{tooltip}</text>
|
|
300
296
|
<text fg={Theme.textMuted}>esc</text>
|
|
301
297
|
</box>
|
|
302
|
-
<box style={{ paddingTop: 1, paddingBottom: 1 }}>
|
|
298
|
+
<box style={{ paddingTop: 1, paddingBottom: 1, flexDirection: 'row' }}>
|
|
299
|
+
<text flexShrink={0} fg={Theme.primary}>> </text>
|
|
303
300
|
<textarea
|
|
304
301
|
ref={inputRef}
|
|
305
302
|
height={1}
|
|
303
|
+
flexGrow={1}
|
|
306
304
|
wrapMode='none'
|
|
307
305
|
keyBindings={[
|
|
308
306
|
{ name: 'return', action: 'submit' },
|
|
@@ -329,7 +327,7 @@ const Dropdown: DropdownType = (props) => {
|
|
|
329
327
|
style={{
|
|
330
328
|
rootOptions: {
|
|
331
329
|
backgroundColor: undefined,
|
|
332
|
-
|
|
330
|
+
height: 7, // Fixed height prevents layout shift when content mounts incrementally
|
|
333
331
|
},
|
|
334
332
|
scrollbarOptions: {
|
|
335
333
|
|
|
@@ -450,7 +448,7 @@ const DropdownItem: (props: DropdownItemProps) => any = (props) => {
|
|
|
450
448
|
const elementRef = useRef<{ y: number; height: number } | null>(null)
|
|
451
449
|
const isOffscreen = useIsOffscreen()
|
|
452
450
|
if (!context) return null
|
|
453
|
-
|
|
451
|
+
|
|
454
452
|
// Don't render UI when offscreen (used for collecting action descendants)
|
|
455
453
|
if (isOffscreen) return null
|
|
456
454
|
|
|
@@ -492,6 +490,9 @@ const DropdownItem: (props: DropdownItemProps) => any = (props) => {
|
|
|
492
490
|
index !== -1
|
|
493
491
|
) {
|
|
494
492
|
context.setSelectedIndex(index)
|
|
493
|
+
if (context.onSelectionChange) {
|
|
494
|
+
context.onSelectionChange(props.value)
|
|
495
|
+
}
|
|
495
496
|
}
|
|
496
497
|
}
|
|
497
498
|
|
|
@@ -520,6 +521,7 @@ const DropdownItem: (props: DropdownItemProps) => any = (props) => {
|
|
|
520
521
|
|
|
521
522
|
const DropdownSection: (props: DropdownSectionProps) => any = (props) => {
|
|
522
523
|
const parentContext = useContext(DropdownContext)
|
|
524
|
+
const isOffscreen = useIsOffscreen()
|
|
523
525
|
if (!parentContext) return null
|
|
524
526
|
|
|
525
527
|
// Create new context with section title
|
|
@@ -531,6 +533,15 @@ const DropdownSection: (props: DropdownSectionProps) => any = (props) => {
|
|
|
531
533
|
[parentContext, props.title],
|
|
532
534
|
)
|
|
533
535
|
|
|
536
|
+
// When offscreen, just render children without section title UI
|
|
537
|
+
if (isOffscreen) {
|
|
538
|
+
return (
|
|
539
|
+
<DropdownContext.Provider value={sectionContextValue}>
|
|
540
|
+
{props.children}
|
|
541
|
+
</DropdownContext.Provider>
|
|
542
|
+
)
|
|
543
|
+
}
|
|
544
|
+
|
|
534
545
|
return (
|
|
535
546
|
<>
|
|
536
547
|
{/* Render section title if provided */}
|
|
@@ -52,9 +52,12 @@ export function ExtensionPreferences({
|
|
|
52
52
|
|
|
53
53
|
let packageJson: RaycastPackageJson
|
|
54
54
|
|
|
55
|
-
if (
|
|
56
|
-
// Dev mode - use package.json from state
|
|
55
|
+
if (extensionPackageJson?.name === extensionName) {
|
|
56
|
+
// Dev mode or compiled extension - use package.json from state
|
|
57
57
|
packageJson = extensionPackageJson
|
|
58
|
+
} else if (extensionPath && fs.existsSync(path.join(extensionPath, 'package.json'))) {
|
|
59
|
+
// Dev mode with extensionPath - read from disk
|
|
60
|
+
packageJson = JSON.parse(fs.readFileSync(path.join(extensionPath, 'package.json'), 'utf-8'))
|
|
58
61
|
} else {
|
|
59
62
|
// Store extension - read from store directory
|
|
60
63
|
const storeDir = getStoreDirectory()
|
|
@@ -95,7 +98,7 @@ export function ExtensionPreferences({
|
|
|
95
98
|
}
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
return { preferences: prefsToUse, savedValues }
|
|
101
|
+
return { preferences: prefsToUse, savedValues, extensionTitle: packageJson.title || extensionName }
|
|
99
102
|
} catch (error) {
|
|
100
103
|
logger.error(`Failed to load preferences for ${extensionName}:`, error)
|
|
101
104
|
showToast({
|
|
@@ -103,13 +106,14 @@ export function ExtensionPreferences({
|
|
|
103
106
|
title: 'Failed to load preferences',
|
|
104
107
|
message: String(error),
|
|
105
108
|
})
|
|
106
|
-
return { preferences: [], savedValues: {} }
|
|
109
|
+
return { preferences: [], savedValues: {}, extensionTitle: extensionName }
|
|
107
110
|
}
|
|
108
111
|
},
|
|
109
112
|
})
|
|
110
113
|
|
|
111
114
|
const preferences = data?.preferences ?? []
|
|
112
115
|
const savedValues = data?.savedValues ?? {}
|
|
116
|
+
const extensionTitle = data?.extensionTitle ?? extensionName
|
|
113
117
|
|
|
114
118
|
const handleSubmit = async (values: Record<string, any>) => {
|
|
115
119
|
try {
|
|
@@ -141,8 +145,8 @@ export function ExtensionPreferences({
|
|
|
141
145
|
style: Toast.Style.Success,
|
|
142
146
|
title: 'Preferences saved',
|
|
143
147
|
message: commandName
|
|
144
|
-
? `Preferences for ${
|
|
145
|
-
: `Preferences for ${
|
|
148
|
+
? `Preferences for ${extensionTitle}/${commandName} have been saved`
|
|
149
|
+
: `Preferences for ${extensionTitle} have been saved`,
|
|
146
150
|
})
|
|
147
151
|
|
|
148
152
|
if (onSubmit) {
|
|
@@ -181,8 +185,8 @@ export function ExtensionPreferences({
|
|
|
181
185
|
<Form.Description
|
|
182
186
|
text={
|
|
183
187
|
commandName
|
|
184
|
-
? `No preferences available for ${
|
|
185
|
-
: `No preferences available for ${
|
|
188
|
+
? `No preferences available for ${extensionTitle}/${commandName}`
|
|
189
|
+
: `No preferences available for ${extensionTitle}`
|
|
186
190
|
}
|
|
187
191
|
/>
|
|
188
192
|
</Form>
|
|
@@ -198,8 +202,8 @@ export function ExtensionPreferences({
|
|
|
198
202
|
}
|
|
199
203
|
navigationTitle={
|
|
200
204
|
commandName
|
|
201
|
-
? `${
|
|
202
|
-
: `${
|
|
205
|
+
? `${extensionTitle}/${commandName} Preferences`
|
|
206
|
+
: `${extensionTitle} Preferences`
|
|
203
207
|
}
|
|
204
208
|
>
|
|
205
209
|
{preferences.map((pref) => {
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import React, { ReactNode, useState, useEffect } from 'react'
|
|
2
|
+
import { TextAttributes } from '@opentui/core'
|
|
3
|
+
import { useTerminalDimensions, useKeyboard } from '@opentui/react'
|
|
4
|
+
import { colord } from 'colord'
|
|
5
|
+
import { Theme } from 'termcast/src/theme'
|
|
6
|
+
import { openInBrowser } from 'termcast/src/action-utils'
|
|
7
|
+
import {
|
|
8
|
+
useStore,
|
|
9
|
+
toastPrimaryActionKey,
|
|
10
|
+
toastSecondaryActionKey,
|
|
11
|
+
ToastData,
|
|
12
|
+
} from 'termcast/src/state'
|
|
13
|
+
import { useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
14
|
+
|
|
15
|
+
/** Returns white or black foreground color based on background lightness */
|
|
16
|
+
function getFgForBg(bgColor: string): string {
|
|
17
|
+
return colord(bgColor).isLight() ? '#000000' : '#ffffff'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface FooterProps {
|
|
21
|
+
children?: ReactNode
|
|
22
|
+
paddingLeft?: number
|
|
23
|
+
paddingRight?: number
|
|
24
|
+
paddingTop?: number
|
|
25
|
+
paddingBottom?: number
|
|
26
|
+
marginTop?: number
|
|
27
|
+
hidePoweredBy?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const MIN_WIDTH_FOR_POWERED_BY = 75
|
|
31
|
+
|
|
32
|
+
function ToastInline({ toast }: { toast: ToastData }): any {
|
|
33
|
+
const inFocus = useIsInFocus()
|
|
34
|
+
const [animationFrame, setAnimationFrame] = useState(0)
|
|
35
|
+
|
|
36
|
+
// Keyboard handling for toast actions
|
|
37
|
+
useKeyboard((evt) => {
|
|
38
|
+
if (!inFocus) return
|
|
39
|
+
|
|
40
|
+
if (evt.name === 'escape') {
|
|
41
|
+
toast.onHide()
|
|
42
|
+
} else if (
|
|
43
|
+
toast.primaryAction &&
|
|
44
|
+
evt.ctrl &&
|
|
45
|
+
evt.name === toastPrimaryActionKey.name
|
|
46
|
+
) {
|
|
47
|
+
toast.primaryAction.onAction()
|
|
48
|
+
} else if (
|
|
49
|
+
toast.secondaryAction &&
|
|
50
|
+
evt.ctrl &&
|
|
51
|
+
evt.name === toastSecondaryActionKey.name
|
|
52
|
+
) {
|
|
53
|
+
toast.secondaryAction.onAction()
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// Animation for animated toasts
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (toast.style === 'ANIMATED') {
|
|
60
|
+
const interval = setInterval(() => {
|
|
61
|
+
setAnimationFrame((prev) => (prev + 1) % 8)
|
|
62
|
+
}, 100)
|
|
63
|
+
return () => clearInterval(interval)
|
|
64
|
+
}
|
|
65
|
+
}, [toast.style])
|
|
66
|
+
|
|
67
|
+
// Auto-dismiss for non-animated toasts
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (toast.style !== 'ANIMATED') {
|
|
70
|
+
const duration = toast.style === 'FAILURE' ? 8000 : 5000
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
toast.onHide()
|
|
73
|
+
}, duration)
|
|
74
|
+
return () => clearTimeout(timer)
|
|
75
|
+
}
|
|
76
|
+
}, [toast.style, toast.onHide])
|
|
77
|
+
|
|
78
|
+
const getIcon = () => {
|
|
79
|
+
switch (toast.style) {
|
|
80
|
+
case 'SUCCESS':
|
|
81
|
+
return '✓'
|
|
82
|
+
case 'FAILURE':
|
|
83
|
+
return '✗'
|
|
84
|
+
case 'ANIMATED':
|
|
85
|
+
return '⣾⣽⣻⢿⡿⣟⣯⣷'[animationFrame]
|
|
86
|
+
default:
|
|
87
|
+
return '✓'
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const getIconColor = () => {
|
|
92
|
+
switch (toast.style) {
|
|
93
|
+
case 'SUCCESS':
|
|
94
|
+
return Theme.success
|
|
95
|
+
case 'FAILURE':
|
|
96
|
+
return Theme.error
|
|
97
|
+
case 'ANIMATED':
|
|
98
|
+
return Theme.primary
|
|
99
|
+
default:
|
|
100
|
+
return Theme.success
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const primaryBg = Theme.primary
|
|
105
|
+
const primaryFg = getFgForBg(primaryBg)
|
|
106
|
+
const keysBg = colord(primaryBg).darken(0.06).toHex()
|
|
107
|
+
|
|
108
|
+
const hasKeys = !!toast.primaryAction?.title || !!toast.secondaryAction?.title
|
|
109
|
+
return (
|
|
110
|
+
<box
|
|
111
|
+
flexDirection='row'
|
|
112
|
+
marginLeft={-3}
|
|
113
|
+
marginRight={-3}
|
|
114
|
+
flexGrow={1}
|
|
115
|
+
overflow='hidden'
|
|
116
|
+
>
|
|
117
|
+
{/* Title box */}
|
|
118
|
+
<box
|
|
119
|
+
flexDirection='row'
|
|
120
|
+
flexShrink={0}
|
|
121
|
+
backgroundColor={colord(primaryBg).lighten(0.1).toHex()}
|
|
122
|
+
paddingLeft={3}
|
|
123
|
+
paddingRight={1}
|
|
124
|
+
>
|
|
125
|
+
<text flexShrink={0} fg={getIconColor()}>
|
|
126
|
+
{getIcon()}{' '}
|
|
127
|
+
</text>
|
|
128
|
+
<text flexShrink={0} fg={primaryFg} attributes={TextAttributes.BOLD}>
|
|
129
|
+
{toast.title}
|
|
130
|
+
</text>
|
|
131
|
+
</box>
|
|
132
|
+
{/* Message/description box (in the middle with keys background) */}
|
|
133
|
+
<box
|
|
134
|
+
flexGrow={1}
|
|
135
|
+
backgroundColor={keysBg}
|
|
136
|
+
paddingLeft={1}
|
|
137
|
+
paddingRight={1}
|
|
138
|
+
flexDirection='row'
|
|
139
|
+
overflow='hidden'
|
|
140
|
+
>
|
|
141
|
+
<text fg={primaryFg} wrapMode='none'>
|
|
142
|
+
{toast.message || ''}
|
|
143
|
+
</text>
|
|
144
|
+
</box>
|
|
145
|
+
{/* Keys box (right aligned, no grow) */}
|
|
146
|
+
|
|
147
|
+
<box
|
|
148
|
+
backgroundColor={keysBg}
|
|
149
|
+
paddingLeft={1}
|
|
150
|
+
paddingRight={3}
|
|
151
|
+
gap={1}
|
|
152
|
+
flexDirection='row'
|
|
153
|
+
flexShrink={0}
|
|
154
|
+
>
|
|
155
|
+
{toast.primaryAction?.title && (
|
|
156
|
+
<box
|
|
157
|
+
flexShrink={0}
|
|
158
|
+
flexDirection='row'
|
|
159
|
+
onMouseDown={() => {
|
|
160
|
+
toast.primaryAction?.onAction()
|
|
161
|
+
}}
|
|
162
|
+
>
|
|
163
|
+
<text fg={primaryFg} attributes={TextAttributes.BOLD}>
|
|
164
|
+
{toast.primaryAction.title}
|
|
165
|
+
</text>
|
|
166
|
+
<text fg={primaryFg}> ctrl t</text>
|
|
167
|
+
</box>
|
|
168
|
+
)}
|
|
169
|
+
{toast.secondaryAction?.title && (
|
|
170
|
+
<box
|
|
171
|
+
flexShrink={0}
|
|
172
|
+
flexDirection='row'
|
|
173
|
+
onMouseDown={() => {
|
|
174
|
+
toast.secondaryAction?.onAction()
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
<text fg={primaryFg} attributes={TextAttributes.BOLD}>
|
|
178
|
+
{toast.secondaryAction.title}
|
|
179
|
+
</text>
|
|
180
|
+
<text fg={primaryFg}> ctrl g</text>
|
|
181
|
+
</box>
|
|
182
|
+
)}
|
|
183
|
+
</box>
|
|
184
|
+
</box>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function Footer({
|
|
189
|
+
children,
|
|
190
|
+
paddingLeft = 1,
|
|
191
|
+
paddingRight = 1,
|
|
192
|
+
paddingTop = 1,
|
|
193
|
+
paddingBottom,
|
|
194
|
+
marginTop = 1,
|
|
195
|
+
hidePoweredBy = false,
|
|
196
|
+
}: FooterProps): any {
|
|
197
|
+
const { width } = useTerminalDimensions()
|
|
198
|
+
const showPoweredBy = !hidePoweredBy && width >= MIN_WIDTH_FOR_POWERED_BY
|
|
199
|
+
const toast = useStore((state) => state.toast)
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<box
|
|
203
|
+
border={false}
|
|
204
|
+
style={{
|
|
205
|
+
paddingLeft,
|
|
206
|
+
paddingRight,
|
|
207
|
+
paddingTop,
|
|
208
|
+
paddingBottom,
|
|
209
|
+
marginTop,
|
|
210
|
+
flexShrink: 0,
|
|
211
|
+
flexDirection: 'row',
|
|
212
|
+
justifyContent: 'space-between',
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
{toast ? (
|
|
216
|
+
<ToastInline toast={toast} />
|
|
217
|
+
) : (
|
|
218
|
+
<>
|
|
219
|
+
{children}
|
|
220
|
+
{showPoweredBy && (
|
|
221
|
+
<box flexDirection='row' gap={1}>
|
|
222
|
+
<text flexShrink={0} fg={Theme.textMuted}>
|
|
223
|
+
powered by
|
|
224
|
+
</text>
|
|
225
|
+
<text
|
|
226
|
+
flexShrink={0}
|
|
227
|
+
onMouseDown={() => {
|
|
228
|
+
openInBrowser('https://termcast.app')
|
|
229
|
+
}}
|
|
230
|
+
fg={Theme.textMuted}
|
|
231
|
+
attributes={TextAttributes.BOLD}
|
|
232
|
+
>
|
|
233
|
+
termcast
|
|
234
|
+
</text>
|
|
235
|
+
</box>
|
|
236
|
+
)}
|
|
237
|
+
</>
|
|
238
|
+
)}
|
|
239
|
+
</box>
|
|
240
|
+
)
|
|
241
|
+
}
|