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
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import React, { ReactNode, useMemo, ReactElement } from 'react'
|
|
2
2
|
import { TextAttributes } from '@opentui/core'
|
|
3
|
-
import { useKeyboard } from '@opentui/react'
|
|
4
|
-
import {
|
|
3
|
+
import { useKeyboard, useTerminalDimensions } from '@opentui/react'
|
|
4
|
+
import { useTheme, markdownSyntaxStyle } from 'termcast/src/theme'
|
|
5
5
|
import { InFocus, useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
6
6
|
import { ActionPanel, Action } from 'termcast/src/components/actions'
|
|
7
|
-
import { Image } from 'termcast/src/components/list'
|
|
8
|
-
import { Color, resolveColor } from 'termcast/src/colors'
|
|
9
7
|
import { Footer } from 'termcast/src/components/footer'
|
|
10
8
|
|
|
11
9
|
import { useDialog } from 'termcast/src/internal/dialog'
|
|
12
10
|
import { ScrollBox } from 'termcast/src/internal/scrollbox'
|
|
13
11
|
import { useStore } from 'termcast/src/state'
|
|
12
|
+
import { Offscreen } from 'termcast/src/internal/offscreen'
|
|
13
|
+
import { Metadata, MetadataContext } from 'termcast/src/components/metadata'
|
|
14
|
+
import type { LabelProps, SeparatorProps, LinkProps, TagListProps, TagListItemProps, MetadataConfig } from 'termcast/src/components/metadata'
|
|
14
15
|
|
|
15
16
|
interface ActionsInterface {
|
|
16
17
|
actions?: ReactNode
|
|
@@ -35,133 +36,11 @@ interface MetadataProps {
|
|
|
35
36
|
children: ReactNode
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
interface LabelProps {
|
|
39
|
-
title: string
|
|
40
|
-
icon?: Image.ImageLike | undefined | null
|
|
41
|
-
text?:
|
|
42
|
-
| string
|
|
43
|
-
| {
|
|
44
|
-
value: string
|
|
45
|
-
color?: Color.ColorLike | null
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface SeparatorProps {}
|
|
50
|
-
|
|
51
|
-
interface LinkProps {
|
|
52
|
-
title: string
|
|
53
|
-
target: string
|
|
54
|
-
text: string
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface TagListProps {
|
|
58
|
-
title: string
|
|
59
|
-
children: ReactNode
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface TagListItemProps {
|
|
63
|
-
icon?: Image.ImageLike | undefined | null
|
|
64
|
-
text?: string
|
|
65
|
-
color?: Color.ColorLike | undefined | null
|
|
66
|
-
onAction?: () => void
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
interface DetailType {
|
|
70
|
-
(props: DetailProps): any
|
|
71
|
-
Metadata: DetailMetadataType
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const DetailMetadataLabel = (props: LabelProps): any => {
|
|
75
|
-
const textValue =
|
|
76
|
-
typeof props.text === 'string' ? props.text : props.text?.value
|
|
77
|
-
const textColor =
|
|
78
|
-
typeof props.text === 'object' ? props.text?.color : undefined
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<box
|
|
82
|
-
style={{
|
|
83
|
-
flexDirection: 'row',
|
|
84
|
-
paddingBottom: 1,
|
|
85
|
-
}}
|
|
86
|
-
>
|
|
87
|
-
<text fg={Theme.textMuted} style={{ minWidth: 15 }}>
|
|
88
|
-
{props.title}:
|
|
89
|
-
</text>
|
|
90
|
-
<text fg={resolveColor(textColor) || Theme.text}>{textValue || '—'}</text>
|
|
91
|
-
</box>
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const DetailMetadataSeparator = (props: SeparatorProps): any => {
|
|
96
|
-
return (
|
|
97
|
-
<box
|
|
98
|
-
style={{
|
|
99
|
-
paddingTop: 1,
|
|
100
|
-
paddingBottom: 1,
|
|
101
|
-
}}
|
|
102
|
-
>
|
|
103
|
-
<text fg={Theme.textMuted}>{'─'.repeat(30)}</text>
|
|
104
|
-
</box>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const DetailMetadataLink = (props: LinkProps): any => {
|
|
109
|
-
return (
|
|
110
|
-
<box
|
|
111
|
-
style={{
|
|
112
|
-
flexDirection: 'row',
|
|
113
|
-
paddingBottom: 1,
|
|
114
|
-
}}
|
|
115
|
-
>
|
|
116
|
-
<text fg={Theme.textMuted} style={{ minWidth: 15 }}>
|
|
117
|
-
{props.title}:
|
|
118
|
-
</text>
|
|
119
|
-
<text fg={Theme.accent} attributes={TextAttributes.UNDERLINE}>
|
|
120
|
-
{props.text}
|
|
121
|
-
</text>
|
|
122
|
-
</box>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const DetailMetadataTagListItem = (props: TagListItemProps): any => {
|
|
127
|
-
const displayText = props.text || ''
|
|
128
|
-
|
|
129
|
-
return (
|
|
130
|
-
<text
|
|
131
|
-
fg={resolveColor(props.color) || Theme.text}
|
|
132
|
-
style={{
|
|
133
|
-
paddingRight: 1,
|
|
134
|
-
paddingLeft: props.icon ? 1 : 0,
|
|
135
|
-
}}
|
|
136
|
-
>
|
|
137
|
-
{displayText}
|
|
138
|
-
</text>
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
39
|
interface DetailMetadataTagListType {
|
|
143
40
|
(props: TagListProps): any
|
|
144
41
|
Item: (props: TagListItemProps) => any
|
|
145
42
|
}
|
|
146
43
|
|
|
147
|
-
const DetailMetadataTagList: DetailMetadataTagListType = (props) => {
|
|
148
|
-
return (
|
|
149
|
-
<box
|
|
150
|
-
style={{
|
|
151
|
-
flexDirection: 'column',
|
|
152
|
-
paddingBottom: 1,
|
|
153
|
-
}}
|
|
154
|
-
>
|
|
155
|
-
<text fg={Theme.textMuted} style={{ minWidth: 15 }}>
|
|
156
|
-
{props.title}:
|
|
157
|
-
</text>
|
|
158
|
-
<box style={{ flexDirection: 'row' }}>{props.children}</box>
|
|
159
|
-
</box>
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
DetailMetadataTagList.Item = DetailMetadataTagListItem
|
|
164
|
-
|
|
165
44
|
interface DetailMetadataType {
|
|
166
45
|
(props: MetadataProps): any
|
|
167
46
|
Label: (props: LabelProps) => any
|
|
@@ -170,26 +49,47 @@ interface DetailMetadataType {
|
|
|
170
49
|
TagList: DetailMetadataTagListType
|
|
171
50
|
}
|
|
172
51
|
|
|
52
|
+
interface DetailType {
|
|
53
|
+
(props: DetailProps): any
|
|
54
|
+
Metadata: DetailMetadataType
|
|
55
|
+
}
|
|
56
|
+
|
|
173
57
|
const DetailMetadata: DetailMetadataType = (props) => {
|
|
58
|
+
const { width } = useTerminalDimensions()
|
|
59
|
+
|
|
60
|
+
// Dynamic config based on terminal width
|
|
61
|
+
// Calculate maxValueLen as a function of title length:
|
|
62
|
+
// availableWidth = terminalWidth - padding(~4) - titleWidth - colon+space(2)
|
|
63
|
+
const config: MetadataConfig = {
|
|
64
|
+
maxValueLen: (titleLen: number) => {
|
|
65
|
+
const padding = 4
|
|
66
|
+
const colonSpace = 2
|
|
67
|
+
const titleWidth = Math.max(titleLen, 15) // minimum title width
|
|
68
|
+
return Math.max(10, width - padding - titleWidth - colonSpace)
|
|
69
|
+
},
|
|
70
|
+
titleMinWidth: 15,
|
|
71
|
+
paddingBottom: 1,
|
|
72
|
+
separatorWidth: Math.min(30, width - 4),
|
|
73
|
+
}
|
|
74
|
+
|
|
174
75
|
return (
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
</box>
|
|
76
|
+
<MetadataContext.Provider value={config}>
|
|
77
|
+
<box
|
|
78
|
+
style={{
|
|
79
|
+
flexDirection: 'column',
|
|
80
|
+
paddingTop: 1,
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
{props.children}
|
|
84
|
+
</box>
|
|
85
|
+
</MetadataContext.Provider>
|
|
186
86
|
)
|
|
187
87
|
}
|
|
188
88
|
|
|
189
|
-
DetailMetadata.Label =
|
|
190
|
-
DetailMetadata.Separator =
|
|
191
|
-
DetailMetadata.Link =
|
|
192
|
-
DetailMetadata.TagList =
|
|
89
|
+
DetailMetadata.Label = Metadata.Label
|
|
90
|
+
DetailMetadata.Separator = Metadata.Separator
|
|
91
|
+
DetailMetadata.Link = Metadata.Link
|
|
92
|
+
DetailMetadata.TagList = Metadata.TagList
|
|
193
93
|
|
|
194
94
|
function DetailFooter({
|
|
195
95
|
hasActions,
|
|
@@ -198,29 +98,31 @@ function DetailFooter({
|
|
|
198
98
|
hasActions?: boolean
|
|
199
99
|
firstActionTitle?: string
|
|
200
100
|
}): any {
|
|
101
|
+
const theme = useTheme()
|
|
102
|
+
|
|
201
103
|
return (
|
|
202
104
|
<Footer paddingLeft={0} paddingRight={0}>
|
|
203
105
|
<box style={{ flexDirection: 'row', gap: 3 }}>
|
|
204
106
|
<box style={{ flexDirection: 'row', gap: 1 }}>
|
|
205
|
-
<text flexShrink={0} fg={
|
|
107
|
+
<text flexShrink={0} fg={theme.text} attributes={TextAttributes.BOLD}>
|
|
206
108
|
esc
|
|
207
109
|
</text>
|
|
208
|
-
<text flexShrink={0} fg={
|
|
110
|
+
<text flexShrink={0} fg={theme.textMuted}>go back</text>
|
|
209
111
|
</box>
|
|
210
112
|
{hasActions && (
|
|
211
113
|
<box style={{ flexDirection: 'row', gap: 1 }}>
|
|
212
|
-
<text flexShrink={0} fg={
|
|
114
|
+
<text flexShrink={0} fg={theme.text} attributes={TextAttributes.BOLD}>
|
|
213
115
|
^k
|
|
214
116
|
</text>
|
|
215
|
-
<text flexShrink={0} fg={
|
|
117
|
+
<text flexShrink={0} fg={theme.textMuted}>actions</text>
|
|
216
118
|
</box>
|
|
217
119
|
)}
|
|
218
120
|
{hasActions && firstActionTitle && (
|
|
219
121
|
<box style={{ flexDirection: 'row', gap: 1 }}>
|
|
220
|
-
<text flexShrink={0} fg={
|
|
122
|
+
<text flexShrink={0} fg={theme.text} attributes={TextAttributes.BOLD}>
|
|
221
123
|
↵
|
|
222
124
|
</text>
|
|
223
|
-
<text flexShrink={0} fg={
|
|
125
|
+
<text flexShrink={0} fg={theme.textMuted}>{firstActionTitle}</text>
|
|
224
126
|
</box>
|
|
225
127
|
)}
|
|
226
128
|
</box>
|
|
@@ -274,12 +176,13 @@ const Detail: DetailType = (props) => {
|
|
|
274
176
|
if (!inFocus) return
|
|
275
177
|
|
|
276
178
|
if (evt.name === 'k' && evt.ctrl) {
|
|
277
|
-
// Ctrl+K shows actions
|
|
278
|
-
|
|
179
|
+
// Ctrl+K shows actions dialog via portal
|
|
180
|
+
if (actions) {
|
|
181
|
+
useStore.setState({ showActionsDialog: true })
|
|
182
|
+
}
|
|
279
183
|
} else if (evt.name === 'return' && actions) {
|
|
280
|
-
// Enter executes first action
|
|
184
|
+
// Enter auto-executes first action via ActionPanel's layout effect
|
|
281
185
|
useStore.setState({ shouldAutoExecuteFirstAction: true })
|
|
282
|
-
dialog.pushActions(actions)
|
|
283
186
|
}
|
|
284
187
|
})
|
|
285
188
|
|
|
@@ -317,6 +220,8 @@ const Detail: DetailType = (props) => {
|
|
|
317
220
|
hasActions={!!actions}
|
|
318
221
|
firstActionTitle={firstActionTitle}
|
|
319
222
|
/>
|
|
223
|
+
{/* Render actions offscreen to capture them */}
|
|
224
|
+
{actions && <Offscreen>{actions}</Offscreen>}
|
|
320
225
|
</box>
|
|
321
226
|
)
|
|
322
227
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, {
|
|
2
2
|
ReactNode,
|
|
3
3
|
useState,
|
|
4
|
-
|
|
4
|
+
useLayoutEffect,
|
|
5
5
|
useMemo,
|
|
6
6
|
useRef,
|
|
7
|
+
useCallback,
|
|
7
8
|
createContext,
|
|
8
9
|
useContext,
|
|
9
10
|
} from 'react'
|
|
@@ -13,8 +14,10 @@ import {
|
|
|
13
14
|
ScrollBoxRenderable,
|
|
14
15
|
TextareaRenderable,
|
|
15
16
|
} from '@opentui/core'
|
|
16
|
-
import {
|
|
17
|
+
import { useTheme } from 'termcast/src/theme'
|
|
18
|
+
import { getIconValue } from 'termcast/src/components/icon'
|
|
17
19
|
import { logger } from 'termcast/src/logger'
|
|
20
|
+
import { useStore } from 'termcast/src/state'
|
|
18
21
|
import { useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
19
22
|
import { useIsOffscreen } from 'termcast/src/internal/offscreen'
|
|
20
23
|
import { CommonProps } from 'termcast/src/utils'
|
|
@@ -43,7 +46,7 @@ export interface DropdownProps extends SearchBarInterface, CommonProps {
|
|
|
43
46
|
|
|
44
47
|
export interface DropdownItemProps extends CommonProps {
|
|
45
48
|
title: string
|
|
46
|
-
value
|
|
49
|
+
value?: string
|
|
47
50
|
icon?: ReactNode
|
|
48
51
|
|
|
49
52
|
keywords?: string[]
|
|
@@ -111,6 +114,7 @@ const Dropdown: DropdownType = (props) => {
|
|
|
111
114
|
throttle,
|
|
112
115
|
} = props
|
|
113
116
|
|
|
117
|
+
const theme = useTheme()
|
|
114
118
|
const isOffscreen = useIsOffscreen()
|
|
115
119
|
const [selected, setSelected] = useState(0)
|
|
116
120
|
const [searchText, setSearchTextState] = useState('')
|
|
@@ -118,6 +122,23 @@ const Dropdown: DropdownType = (props) => {
|
|
|
118
122
|
value || defaultValue,
|
|
119
123
|
)
|
|
120
124
|
const inputRef = useRef<TextareaRenderable>(null)
|
|
125
|
+
|
|
126
|
+
// Ref callback that registers the textarea in global state for ESC handling
|
|
127
|
+
const setInputRef = useCallback((node: TextareaRenderable | null) => {
|
|
128
|
+
if (!node) return
|
|
129
|
+
|
|
130
|
+
inputRef.current = node
|
|
131
|
+
useStore.setState({ activeSearchInputRef: node })
|
|
132
|
+
|
|
133
|
+
// React 19: return cleanup function for unmount
|
|
134
|
+
return () => {
|
|
135
|
+
if (useStore.getState().activeSearchInputRef === node) {
|
|
136
|
+
useStore.setState({ activeSearchInputRef: null })
|
|
137
|
+
}
|
|
138
|
+
inputRef.current = null
|
|
139
|
+
}
|
|
140
|
+
}, [])
|
|
141
|
+
|
|
121
142
|
const lastSearchTextRef = useRef('')
|
|
122
143
|
const throttleTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined)
|
|
123
144
|
const scrollBoxRef = useRef<ScrollBoxRenderable>(null)
|
|
@@ -157,8 +178,8 @@ const Dropdown: DropdownType = (props) => {
|
|
|
157
178
|
[searchText, filtering, selected, currentValue, onSelectionChange],
|
|
158
179
|
)
|
|
159
180
|
|
|
160
|
-
// Update controlled value
|
|
161
|
-
|
|
181
|
+
// Update controlled value (before paint to avoid flash)
|
|
182
|
+
useLayoutEffect(() => {
|
|
162
183
|
if (value !== undefined) {
|
|
163
184
|
setCurrentValue(value)
|
|
164
185
|
}
|
|
@@ -177,7 +198,10 @@ const Dropdown: DropdownType = (props) => {
|
|
|
177
198
|
.sort((a: any, b: any) => a.index - b.index)
|
|
178
199
|
|
|
179
200
|
if (items.length > 0 && items[0]) {
|
|
180
|
-
|
|
201
|
+
flushSync(() => {
|
|
202
|
+
setSelected(items[0].index)
|
|
203
|
+
})
|
|
204
|
+
scrollToItem(items[0])
|
|
181
205
|
}
|
|
182
206
|
|
|
183
207
|
if (onSearchTextChange) {
|
|
@@ -218,7 +242,9 @@ const Dropdown: DropdownType = (props) => {
|
|
|
218
242
|
|
|
219
243
|
const nextItem = items[nextVisibleIndex]
|
|
220
244
|
if (nextItem) {
|
|
221
|
-
|
|
245
|
+
flushSync(() => {
|
|
246
|
+
setSelected(nextItem.index)
|
|
247
|
+
})
|
|
222
248
|
scrollToItem(nextItem)
|
|
223
249
|
if (onSelectionChange && nextItem.props) {
|
|
224
250
|
onSelectionChange((nextItem.props as DropdownItemDescendant).value)
|
|
@@ -292,13 +318,13 @@ const Dropdown: DropdownType = (props) => {
|
|
|
292
318
|
justifyContent: 'space-between',
|
|
293
319
|
}}
|
|
294
320
|
>
|
|
295
|
-
<text fg={
|
|
296
|
-
<text fg={
|
|
321
|
+
<text fg={theme.textMuted}>{tooltip}</text>
|
|
322
|
+
<text fg={theme.textMuted}>esc</text>
|
|
297
323
|
</box>
|
|
298
324
|
<box style={{ paddingTop: 1, paddingBottom: 1, flexDirection: 'row' }}>
|
|
299
|
-
<text flexShrink={0} fg={
|
|
325
|
+
<text flexShrink={0} fg={theme.primary}>> </text>
|
|
300
326
|
<textarea
|
|
301
|
-
ref={
|
|
327
|
+
ref={setInputRef}
|
|
302
328
|
height={1}
|
|
303
329
|
flexGrow={1}
|
|
304
330
|
wrapMode='none'
|
|
@@ -313,9 +339,9 @@ const Dropdown: DropdownType = (props) => {
|
|
|
313
339
|
placeholder={placeholder}
|
|
314
340
|
focused={inFocus}
|
|
315
341
|
initialValue=""
|
|
316
|
-
focusedBackgroundColor={
|
|
317
|
-
cursorColor={
|
|
318
|
-
focusedTextColor={
|
|
342
|
+
focusedBackgroundColor={theme.backgroundPanel}
|
|
343
|
+
cursorColor={theme.primary}
|
|
344
|
+
focusedTextColor={theme.textMuted}
|
|
319
345
|
/>
|
|
320
346
|
</box>
|
|
321
347
|
</box>
|
|
@@ -350,14 +376,14 @@ const Dropdown: DropdownType = (props) => {
|
|
|
350
376
|
flexDirection: 'row',
|
|
351
377
|
}}
|
|
352
378
|
>
|
|
353
|
-
<text fg={
|
|
379
|
+
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
|
354
380
|
↵
|
|
355
381
|
</text>
|
|
356
|
-
<text fg={
|
|
357
|
-
<text fg={
|
|
382
|
+
<text fg={theme.textMuted}> select</text>
|
|
383
|
+
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
|
358
384
|
{' '}↑↓
|
|
359
385
|
</text>
|
|
360
|
-
<text fg={
|
|
386
|
+
<text fg={theme.textMuted}> navigate</text>
|
|
361
387
|
</box>
|
|
362
388
|
</box>
|
|
363
389
|
</DropdownContext.Provider>
|
|
@@ -376,6 +402,7 @@ function ItemOption(props: {
|
|
|
376
402
|
onMouseMove?: () => void
|
|
377
403
|
elementRef?: React.Ref<any>
|
|
378
404
|
}) {
|
|
405
|
+
const theme = useTheme()
|
|
379
406
|
const [isHovered, setIsHovered] = useState(false)
|
|
380
407
|
|
|
381
408
|
return (
|
|
@@ -384,9 +411,9 @@ function ItemOption(props: {
|
|
|
384
411
|
style={{
|
|
385
412
|
flexDirection: 'row',
|
|
386
413
|
backgroundColor: props.active
|
|
387
|
-
?
|
|
414
|
+
? theme.primary
|
|
388
415
|
: isHovered
|
|
389
|
-
?
|
|
416
|
+
? theme.backgroundPanel
|
|
390
417
|
: undefined,
|
|
391
418
|
paddingLeft: props.active ? 0 : 1,
|
|
392
419
|
paddingRight: 1,
|
|
@@ -402,27 +429,27 @@ function ItemOption(props: {
|
|
|
402
429
|
>
|
|
403
430
|
<box style={{ flexDirection: 'row' }}>
|
|
404
431
|
{props.active && (
|
|
405
|
-
<text fg={
|
|
432
|
+
<text fg={theme.background} selectable={false}>
|
|
406
433
|
›{''}
|
|
407
434
|
</text>
|
|
408
435
|
)}
|
|
409
436
|
{props.icon && (
|
|
410
437
|
<text
|
|
411
|
-
fg={props.active ?
|
|
438
|
+
fg={props.active ? theme.background : theme.text}
|
|
412
439
|
selectable={false}
|
|
413
440
|
>
|
|
414
|
-
{
|
|
441
|
+
{getIconValue(props.icon)}{' '}
|
|
415
442
|
</text>
|
|
416
443
|
)}
|
|
417
444
|
<text
|
|
418
445
|
fg={
|
|
419
446
|
props.active
|
|
420
|
-
?
|
|
447
|
+
? theme.background
|
|
421
448
|
: props.color
|
|
422
449
|
? props.color
|
|
423
450
|
: props.current
|
|
424
|
-
?
|
|
425
|
-
:
|
|
451
|
+
? theme.primary
|
|
452
|
+
: theme.text
|
|
426
453
|
}
|
|
427
454
|
attributes={props.active ? TextAttributes.BOLD : undefined}
|
|
428
455
|
selectable={false}
|
|
@@ -432,7 +459,7 @@ function ItemOption(props: {
|
|
|
432
459
|
</box>
|
|
433
460
|
{props.label && (
|
|
434
461
|
<text
|
|
435
|
-
fg={props.active ?
|
|
462
|
+
fg={props.active ? theme.background : theme.textMuted}
|
|
436
463
|
attributes={props.active ? TextAttributes.BOLD : undefined}
|
|
437
464
|
selectable={false}
|
|
438
465
|
>
|
|
@@ -466,9 +493,10 @@ const DropdownItem: (props: DropdownItemProps) => any = (props) => {
|
|
|
466
493
|
return !searchableText.includes(needle)
|
|
467
494
|
})()
|
|
468
495
|
|
|
469
|
-
// Register as descendant
|
|
496
|
+
// Register as descendant - use title as fallback for value
|
|
497
|
+
const value = props.value ?? props.title
|
|
470
498
|
const { index } = useDropdownItemDescendant({
|
|
471
|
-
value
|
|
499
|
+
value,
|
|
472
500
|
title: props.title,
|
|
473
501
|
hidden: shouldHide,
|
|
474
502
|
elementRef: elementRef.current,
|
|
@@ -479,7 +507,7 @@ const DropdownItem: (props: DropdownItemProps) => any = (props) => {
|
|
|
479
507
|
|
|
480
508
|
// Determine if active (index will be -1 if hidden)
|
|
481
509
|
const isActive = index === selectedIndex && index !== -1
|
|
482
|
-
const isCurrent =
|
|
510
|
+
const isCurrent = value === currentValue
|
|
483
511
|
|
|
484
512
|
// Handle mouse events
|
|
485
513
|
const handleMouseMove = () => {
|
|
@@ -491,15 +519,15 @@ const DropdownItem: (props: DropdownItemProps) => any = (props) => {
|
|
|
491
519
|
) {
|
|
492
520
|
context.setSelectedIndex(index)
|
|
493
521
|
if (context.onSelectionChange) {
|
|
494
|
-
context.onSelectionChange(
|
|
522
|
+
context.onSelectionChange(value)
|
|
495
523
|
}
|
|
496
524
|
}
|
|
497
525
|
}
|
|
498
526
|
|
|
499
527
|
const handleMouseDown = () => {
|
|
500
528
|
// Trigger selection on click
|
|
501
|
-
if (context.onChange &&
|
|
502
|
-
context.onChange(
|
|
529
|
+
if (context.onChange && value) {
|
|
530
|
+
context.onChange(value)
|
|
503
531
|
}
|
|
504
532
|
}
|
|
505
533
|
|
|
@@ -520,6 +548,7 @@ const DropdownItem: (props: DropdownItemProps) => any = (props) => {
|
|
|
520
548
|
}
|
|
521
549
|
|
|
522
550
|
const DropdownSection: (props: DropdownSectionProps) => any = (props) => {
|
|
551
|
+
const theme = useTheme()
|
|
523
552
|
const parentContext = useContext(DropdownContext)
|
|
524
553
|
const isOffscreen = useIsOffscreen()
|
|
525
554
|
if (!parentContext) return null
|
|
@@ -542,12 +571,17 @@ const DropdownSection: (props: DropdownSectionProps) => any = (props) => {
|
|
|
542
571
|
)
|
|
543
572
|
}
|
|
544
573
|
|
|
574
|
+
// Hide section titles when filtering is active and there's search text
|
|
575
|
+
// This prevents showing empty section headers when all items are filtered out
|
|
576
|
+
const hideTitle =
|
|
577
|
+
parentContext.filtering && parentContext.searchText.trim().length > 0
|
|
578
|
+
|
|
545
579
|
return (
|
|
546
580
|
<>
|
|
547
|
-
{/* Render section title if provided */}
|
|
548
|
-
{props.title && (
|
|
581
|
+
{/* Render section title if provided and not hidden by filtering */}
|
|
582
|
+
{props.title && !hideTitle && (
|
|
549
583
|
<box style={{ paddingTop: 1, paddingLeft: 1 }}>
|
|
550
|
-
<text fg={
|
|
584
|
+
<text fg={theme.accent} attributes={TextAttributes.BOLD}>
|
|
551
585
|
{props.title}
|
|
552
586
|
</text>
|
|
553
587
|
</box>
|