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
package/dist/components/list.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/react/jsx-runtime";
|
|
2
2
|
import { TextAttributes, } from '@opentui/core';
|
|
3
3
|
import { useKeyboard, flushSync } from '@opentui/react';
|
|
4
|
-
import React, { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import React, { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { LoadingBar } from 'termcast/src/components/loading-bar';
|
|
6
|
+
import { LoadingText } from 'termcast/src/components/loading-text';
|
|
6
7
|
import { Footer } from 'termcast/src/components/footer';
|
|
7
8
|
import { createDescendants } from 'termcast/src/descendants';
|
|
8
9
|
import { useStore } from 'termcast/src/state';
|
|
@@ -12,9 +13,8 @@ import { useNavigationPending } from 'termcast/src/internal/navigation';
|
|
|
12
13
|
import { Offscreen } from 'termcast/src/internal/offscreen';
|
|
13
14
|
import { ScrollBox } from 'termcast/src/internal/scrollbox';
|
|
14
15
|
import { Color, resolveColor } from 'termcast/src/colors';
|
|
15
|
-
import { getIconEmoji } from 'termcast/src/components/icon';
|
|
16
|
-
import {
|
|
17
|
-
import { Theme, markdownSyntaxStyle } from 'termcast/src/theme';
|
|
16
|
+
import { getIconEmoji, getIconValue } from 'termcast/src/components/icon';
|
|
17
|
+
import { useTheme, markdownSyntaxStyle } from 'termcast/src/theme';
|
|
18
18
|
export { Color };
|
|
19
19
|
function formatRelativeDate(date) {
|
|
20
20
|
const now = new Date();
|
|
@@ -47,13 +47,47 @@ function formatRelativeDate(date) {
|
|
|
47
47
|
return `${diffYear}y`;
|
|
48
48
|
}
|
|
49
49
|
function ListFooter() {
|
|
50
|
+
const theme = useTheme();
|
|
50
51
|
const firstActionTitle = useStore((s) => s.firstActionTitle);
|
|
51
52
|
const hasToast = useStore((s) => s.toast !== null);
|
|
52
53
|
const listContext = useContext(ListContext);
|
|
53
54
|
const isShowingDetail = listContext?.isShowingDetail ?? false;
|
|
54
|
-
const
|
|
55
|
+
const hasDropdown = listContext?.hasDropdown ?? false;
|
|
56
|
+
const content = hasToast ? null : (_jsxs("box", { style: { flexDirection: 'row', gap: 3 }, children: [firstActionTitle && (_jsxs("box", { style: { flexDirection: 'row', gap: 1 }, children: [_jsx("text", { flexShrink: 0, fg: theme.text, attributes: TextAttributes.BOLD, children: "\u21B5" }), _jsx("text", { flexShrink: 0, fg: theme.textMuted, children: firstActionTitle.toLowerCase() })] })), _jsxs("box", { style: { flexDirection: 'row', gap: 1 }, children: [_jsx("text", { flexShrink: 0, fg: theme.text, attributes: TextAttributes.BOLD, children: "\u2191\u2193" }), _jsx("text", { flexShrink: 0, fg: theme.textMuted, children: "navigate" })] }), hasDropdown && (_jsxs("box", { style: { flexDirection: 'row', gap: 1 }, children: [_jsx("text", { flexShrink: 0, fg: theme.text, attributes: TextAttributes.BOLD, children: "^p" }), _jsx("text", { flexShrink: 0, fg: theme.textMuted, children: "dropdown" })] })), _jsxs("box", { style: { flexDirection: 'row', gap: 1 }, children: [_jsx("text", { flexShrink: 0, fg: theme.text, attributes: TextAttributes.BOLD, children: "^k" }), _jsx("text", { flexShrink: 0, fg: theme.textMuted, children: "actions" })] })] }));
|
|
55
57
|
return _jsx(Footer, { hidePoweredBy: isShowingDetail, children: content });
|
|
56
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Component that subscribes to descendants changes and renders current item's
|
|
61
|
+
* actions offscreen. This ensures actions are captured even when items register
|
|
62
|
+
* after the initial render (preserving context via closures).
|
|
63
|
+
*/
|
|
64
|
+
function CurrentItemActionsOffscreen(props) {
|
|
65
|
+
// Subscribe to descendants changes - this hook triggers re-render when items register
|
|
66
|
+
const descendantsMap = useListDescendantsRerender();
|
|
67
|
+
// Get current item's actions
|
|
68
|
+
const items = Object.values(descendantsMap)
|
|
69
|
+
.filter((item) => item.index !== -1)
|
|
70
|
+
.sort((a, b) => a.index - b.index);
|
|
71
|
+
const currentItem = items.find((item) => item.index === props.selectedIndex);
|
|
72
|
+
const actions = currentItem?.props?.actions ?? props.fallbackActions ?? null;
|
|
73
|
+
// Clear first action title when there are no actions
|
|
74
|
+
useLayoutEffect(() => {
|
|
75
|
+
if (!actions) {
|
|
76
|
+
useStore.setState({ firstActionTitle: '' });
|
|
77
|
+
}
|
|
78
|
+
}, [actions]);
|
|
79
|
+
if (!actions)
|
|
80
|
+
return null;
|
|
81
|
+
return _jsx(Offscreen, { children: actions });
|
|
82
|
+
}
|
|
83
|
+
export var Image;
|
|
84
|
+
(function (Image) {
|
|
85
|
+
let ImageMask;
|
|
86
|
+
(function (ImageMask) {
|
|
87
|
+
ImageMask["Circle"] = "circle";
|
|
88
|
+
ImageMask["RoundedRectangle"] = "roundedRectangle";
|
|
89
|
+
})(ImageMask = Image.ImageMask || (Image.ImageMask = {}));
|
|
90
|
+
})(Image || (Image = {}));
|
|
57
91
|
const ListContext = createContext(undefined);
|
|
58
92
|
// Helper function to determine if an item should be visible based on search
|
|
59
93
|
function shouldItemBeVisible(searchQuery, props) {
|
|
@@ -75,6 +109,7 @@ const { DescendantsProvider: ListDescendantsProvider, useDescendants: useListDes
|
|
|
75
109
|
const { DescendantsProvider: DropdownDescendantsProvider, useDescendants: useDropdownDescendants, useDescendant: useDropdownItemDescendant, } = createDescendants();
|
|
76
110
|
const DropdownContext = createContext(undefined);
|
|
77
111
|
function ListDropdownDialog(props) {
|
|
112
|
+
const theme = useTheme();
|
|
78
113
|
const [searchText, setSearchTextRaw] = useState('');
|
|
79
114
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
80
115
|
const inputRef = useRef(null);
|
|
@@ -143,13 +178,13 @@ function ListDropdownDialog(props) {
|
|
|
143
178
|
return (_jsx(DropdownDescendantsProvider, { value: descendantsContext, children: _jsxs("box", { children: [_jsxs("box", { style: { paddingLeft: 2, paddingRight: 2 }, children: [_jsxs("box", { style: { paddingLeft: 1, paddingRight: 1 }, children: [_jsxs("box", { style: {
|
|
144
179
|
flexDirection: 'row',
|
|
145
180
|
justifyContent: 'space-between',
|
|
146
|
-
}, children: [_jsx("text", { flexShrink: 0, fg:
|
|
181
|
+
}, children: [_jsx("text", { flexShrink: 0, fg: theme.textMuted, children: props.tooltip }), _jsx("text", { flexShrink: 0, fg: theme.textMuted, children: "esc" })] }), _jsxs("box", { style: { paddingTop: 1, paddingBottom: 1, flexDirection: 'row' }, children: [_jsx("text", { flexShrink: 0, fg: theme.textMuted, children: "> " }), _jsx("textarea", { ref: inputRef, height: 1, flexGrow: 1, wrapMode: 'none', keyBindings: [
|
|
147
182
|
{ name: 'return', action: 'submit' },
|
|
148
183
|
{ name: 'linefeed', action: 'submit' },
|
|
149
184
|
], onContentChange: () => {
|
|
150
185
|
const value = inputRef.current?.plainText || '';
|
|
151
186
|
setSearchText(value);
|
|
152
|
-
}, placeholder: props.placeholder || 'Search...', focused: inFocus, initialValue: searchText, focusedBackgroundColor:
|
|
187
|
+
}, placeholder: props.placeholder || 'Search...', focused: inFocus, initialValue: searchText, focusedBackgroundColor: theme.backgroundPanel, cursorColor: theme.primary, focusedTextColor: theme.textMuted })] })] }), _jsx("box", { style: { paddingBottom: 1 }, children: _jsx(DropdownContext.Provider, { value: {
|
|
153
188
|
currentSection: undefined,
|
|
154
189
|
selectedIndex,
|
|
155
190
|
setSelectedIndex,
|
|
@@ -159,15 +194,17 @@ function ListDropdownDialog(props) {
|
|
|
159
194
|
onChange: (value) => {
|
|
160
195
|
props.onChange?.(value);
|
|
161
196
|
},
|
|
162
|
-
}, children: props.children }) }), props.isLoading && (_jsx("box", { style: { paddingLeft: 1 }, children: _jsx("text", { flexShrink: 0, fg:
|
|
197
|
+
}, children: props.children }) }), props.isLoading && (_jsx("box", { style: { paddingLeft: 1 }, children: _jsx("text", { flexShrink: 0, fg: theme.textMuted, children: "Loading..." }) }))] }), _jsx(DropdownFooter, {})] }) }));
|
|
163
198
|
}
|
|
164
199
|
function DropdownFooter() {
|
|
200
|
+
const theme = useTheme();
|
|
165
201
|
const hasToast = useStore((s) => s.toast !== null);
|
|
166
|
-
const content = hasToast ? null : (_jsxs("box", { style: { flexDirection: 'row', gap: 3 }, children: [_jsxs("box", { style: { flexDirection: 'row', gap: 1 }, children: [_jsx("text", { flexShrink: 0, fg:
|
|
202
|
+
const content = hasToast ? null : (_jsxs("box", { style: { flexDirection: 'row', gap: 3 }, children: [_jsxs("box", { style: { flexDirection: 'row', gap: 1 }, children: [_jsx("text", { flexShrink: 0, fg: theme.text, attributes: TextAttributes.BOLD, children: "\u21B5" }), _jsx("text", { flexShrink: 0, fg: theme.textMuted, children: "select" })] }), _jsxs("box", { style: { flexDirection: 'row', gap: 1 }, children: [_jsx("text", { flexShrink: 0, fg: theme.text, attributes: TextAttributes.BOLD, children: "\u2191\u2193" }), _jsx("text", { flexShrink: 0, fg: theme.textMuted, children: "navigate" })] })] }));
|
|
167
203
|
return (_jsx(Footer, { paddingLeft: 3, paddingRight: 2, paddingBottom: 1, marginTop: 0, children: content }));
|
|
168
204
|
}
|
|
169
205
|
// Render a single list item row
|
|
170
206
|
function ListItemRow(props) {
|
|
207
|
+
const theme = useTheme();
|
|
171
208
|
const { title, subtitle, icon, iconColor, accessories, active, ref } = props;
|
|
172
209
|
const [isHovered, setIsHovered] = useState(false);
|
|
173
210
|
const accessoryElements = [];
|
|
@@ -179,7 +216,7 @@ function ListItemRow(props) {
|
|
|
179
216
|
: accessory.text?.value;
|
|
180
217
|
const textColor = typeof accessory.text === 'object' ? accessory.text?.color : undefined;
|
|
181
218
|
if (textValue) {
|
|
182
|
-
accessoryElements.push(_jsx("text", { flexShrink: 0, fg: active ?
|
|
219
|
+
accessoryElements.push(_jsx("text", { flexShrink: 0, fg: active ? theme.background : resolveColor(textColor) || theme.info, wrapMode: "none", children: textValue }, `text-${textValue}`));
|
|
183
220
|
}
|
|
184
221
|
}
|
|
185
222
|
if ('tag' in accessory && accessory.tag) {
|
|
@@ -188,7 +225,7 @@ function ListItemRow(props) {
|
|
|
188
225
|
: accessory.tag?.value;
|
|
189
226
|
const tagColor = typeof accessory.tag === 'object' ? accessory.tag?.color : undefined;
|
|
190
227
|
if (tagValue) {
|
|
191
|
-
accessoryElements.push(_jsxs("text", { flexShrink: 0, fg: active ?
|
|
228
|
+
accessoryElements.push(_jsxs("text", { flexShrink: 0, fg: active ? theme.background : resolveColor(tagColor) || theme.warning, wrapMode: "none", children: ["[", tagValue, "]"] }, `tag-${tagValue}`));
|
|
192
229
|
}
|
|
193
230
|
}
|
|
194
231
|
if ('date' in accessory && accessory.date) {
|
|
@@ -200,7 +237,7 @@ function ListItemRow(props) {
|
|
|
200
237
|
: undefined;
|
|
201
238
|
if (dateValue) {
|
|
202
239
|
const formatted = formatRelativeDate(dateValue);
|
|
203
|
-
accessoryElements.push(_jsx("text", { flexShrink: 0, fg: active ?
|
|
240
|
+
accessoryElements.push(_jsx("text", { flexShrink: 0, fg: active ? theme.background : resolveColor(dateColor) || theme.success, wrapMode: "none", children: formatted }, `date-${dateValue.getTime()}`));
|
|
204
241
|
}
|
|
205
242
|
}
|
|
206
243
|
});
|
|
@@ -209,9 +246,9 @@ function ListItemRow(props) {
|
|
|
209
246
|
flexDirection: 'row',
|
|
210
247
|
justifyContent: 'space-between',
|
|
211
248
|
backgroundColor: active
|
|
212
|
-
?
|
|
249
|
+
? theme.primary
|
|
213
250
|
: isHovered
|
|
214
|
-
?
|
|
251
|
+
? theme.backgroundPanel
|
|
215
252
|
: undefined,
|
|
216
253
|
paddingLeft: 0,
|
|
217
254
|
paddingRight: 1,
|
|
@@ -220,16 +257,31 @@ function ListItemRow(props) {
|
|
|
220
257
|
setIsHovered(true);
|
|
221
258
|
}, onMouseOut: () => {
|
|
222
259
|
setIsHovered(false);
|
|
223
|
-
}, onMouseDown: props.onMouseDown, children: [_jsxs("box", { style: { flexDirection: 'row', flexGrow: 1, flexShrink: 1, overflow: 'hidden', gap: 1 }, children: [_jsxs("box", { style: { flexDirection: 'row', flexShrink: 0 }, children: [_jsx("text", { flexShrink: 0, fg: active ?
|
|
260
|
+
}, onMouseDown: props.onMouseDown, children: [_jsxs("box", { style: { flexDirection: 'row', flexGrow: 1, flexShrink: 1, overflow: 'hidden', gap: 1 }, children: [_jsxs("box", { style: { flexDirection: 'row', flexShrink: 0 }, children: [_jsx("text", { flexShrink: 0, fg: active ? theme.background : theme.text, attributes: active ? TextAttributes.BOLD : undefined, selectable: false, wrapMode: "none", children: active ? '›' : ' ' }), icon && _jsxs("text", { flexShrink: 0, fg: active ? theme.background : iconColor || theme.text, selectable: false, wrapMode: "none", children: [getIconEmoji(icon), " "] }), _jsx("text", { flexShrink: 0, fg: active ? theme.background : theme.text, attributes: active ? TextAttributes.BOLD : undefined, selectable: false, wrapMode: "none", children: title })] }), subtitle && (_jsx("text", { flexShrink: 0, fg: active ? theme.background : theme.textMuted, selectable: false, wrapMode: "none", children: subtitle }))] }), accessoryElements.length > 0 && (_jsx("box", { style: { flexDirection: 'row', flexShrink: 0 }, children: accessoryElements.map((elem, i) => (_jsxs("box", { style: { flexDirection: 'row' }, children: [i > 0 && _jsx("text", { flexShrink: 0, children: " " }), elem] }, i))) }))] }));
|
|
224
261
|
}
|
|
225
262
|
export const List = (props) => {
|
|
226
263
|
const { children, onSelectionChange, filtering, searchText: controlledSearchText, onSearchTextChange, searchBarPlaceholder = 'Search...', isLoading, navigationTitle, isShowingDetail, selectedItemId, searchBarAccessory, ...otherProps } = props;
|
|
227
|
-
const
|
|
264
|
+
const theme = useTheme();
|
|
265
|
+
const [internalSearchText, setInternalSearchText] = useState('');
|
|
228
266
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
229
267
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
230
268
|
const [currentDetail, setCurrentDetail] = useState(null);
|
|
231
|
-
const [currentItemActions, setCurrentItemActions] = useState(null);
|
|
232
269
|
const inputRef = useRef(null);
|
|
270
|
+
const customEmptyViewRef = useRef(false);
|
|
271
|
+
// Ref callback that registers the textarea in global state for ESC handling
|
|
272
|
+
const setInputRef = useCallback((node) => {
|
|
273
|
+
if (!node)
|
|
274
|
+
return;
|
|
275
|
+
inputRef.current = node;
|
|
276
|
+
useStore.setState({ activeSearchInputRef: node });
|
|
277
|
+
// React 19: return cleanup function for unmount
|
|
278
|
+
return () => {
|
|
279
|
+
if (useStore.getState().activeSearchInputRef === node) {
|
|
280
|
+
useStore.setState({ activeSearchInputRef: null });
|
|
281
|
+
}
|
|
282
|
+
inputRef.current = null;
|
|
283
|
+
};
|
|
284
|
+
}, []);
|
|
233
285
|
const scrollBoxRef = useRef(null);
|
|
234
286
|
const descendantsContext = useListDescendants();
|
|
235
287
|
const navigationPending = useNavigationPending();
|
|
@@ -275,19 +327,24 @@ export const List = (props) => {
|
|
|
275
327
|
const openDropdown = () => {
|
|
276
328
|
setIsDropdownOpen(true);
|
|
277
329
|
};
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
330
|
+
// Sync selection to the first visible item whenever searchText changes.
|
|
331
|
+
// Runs after children's useLayoutEffects (descendants registered) but before paint,
|
|
332
|
+
// so there is no intermediate frame with stale selection.
|
|
333
|
+
// Works for both controlled and uncontrolled searchText.
|
|
334
|
+
const prevSearchTextRef = useRef(searchText);
|
|
335
|
+
useLayoutEffect(() => {
|
|
336
|
+
if (prevSearchTextRef.current === searchText)
|
|
337
|
+
return;
|
|
338
|
+
prevSearchTextRef.current = searchText;
|
|
339
|
+
if (!isFilteringEnabled)
|
|
340
|
+
return;
|
|
284
341
|
const items = Object.values(descendantsContext.map.current)
|
|
285
342
|
.filter((item) => item.index !== -1 && item.props?.visible !== false)
|
|
286
343
|
.sort((a, b) => a.index - b.index);
|
|
287
344
|
if (items.length > 0 && items[0]) {
|
|
288
345
|
setSelectedIndex(items[0].index);
|
|
289
346
|
}
|
|
290
|
-
};
|
|
347
|
+
});
|
|
291
348
|
const listContextValue = useMemo(() => ({
|
|
292
349
|
isDropdownOpen,
|
|
293
350
|
setIsDropdownOpen,
|
|
@@ -298,15 +355,18 @@ export const List = (props) => {
|
|
|
298
355
|
isFiltering: isFilteringEnabled,
|
|
299
356
|
setCurrentDetail,
|
|
300
357
|
isShowingDetail,
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
358
|
+
customEmptyViewRef,
|
|
359
|
+
isLoading,
|
|
360
|
+
hasDropdown: !!searchBarAccessory,
|
|
361
|
+
}), [isDropdownOpen, selectedIndex, searchText, isFilteringEnabled, isShowingDetail, isLoading, searchBarAccessory]);
|
|
362
|
+
// Clear detail when detail view is hidden (before paint to avoid flash)
|
|
363
|
+
useLayoutEffect(() => {
|
|
304
364
|
if (!isShowingDetail) {
|
|
305
365
|
setCurrentDetail(null);
|
|
306
366
|
}
|
|
307
367
|
}, [isShowingDetail]);
|
|
308
|
-
// Handle selectedItemId prop changes
|
|
309
|
-
|
|
368
|
+
// Handle selectedItemId prop changes (before paint to avoid flash)
|
|
369
|
+
useLayoutEffect(() => {
|
|
310
370
|
// Only update selection if selectedItemId is explicitly provided
|
|
311
371
|
if (selectedItemId !== undefined) {
|
|
312
372
|
const items = Object.values(descendantsContext.map.current)
|
|
@@ -317,25 +377,18 @@ export const List = (props) => {
|
|
|
317
377
|
}
|
|
318
378
|
}
|
|
319
379
|
}, [selectedItemId]);
|
|
320
|
-
// Call onSelectionChange when selection changes
|
|
380
|
+
// Call onSelectionChange when selection changes
|
|
321
381
|
useEffect(() => {
|
|
322
382
|
const items = Object.values(descendantsContext.map.current)
|
|
323
383
|
.filter((item) => item.index !== -1)
|
|
324
384
|
.sort((a, b) => a.index - b.index);
|
|
325
385
|
const currentItem = items.find((item) => item.index === selectedIndex);
|
|
326
|
-
// Track current item's actions for footer display
|
|
327
|
-
const actions = currentItem?.props?.actions ?? props.actions ?? null;
|
|
328
|
-
setCurrentItemActions(actions);
|
|
329
|
-
// Clear first action title when there are no actions
|
|
330
|
-
if (!actions) {
|
|
331
|
-
useStore.setState({ firstActionTitle: '' });
|
|
332
|
-
}
|
|
333
386
|
// Call onSelectionChange callback if provided
|
|
334
387
|
if (onSelectionChange) {
|
|
335
388
|
const selectedId = currentItem?.props?.id ?? null;
|
|
336
389
|
onSelectionChange(selectedId);
|
|
337
390
|
}
|
|
338
|
-
}, [selectedIndex
|
|
391
|
+
}, [selectedIndex]);
|
|
339
392
|
const scrollToItem = (item) => {
|
|
340
393
|
const scrollBox = scrollBoxRef.current;
|
|
341
394
|
const elementRef = item.props?.elementRef;
|
|
@@ -373,8 +426,10 @@ export const List = (props) => {
|
|
|
373
426
|
nextVisibleIndex = 0;
|
|
374
427
|
const nextItem = items[nextVisibleIndex];
|
|
375
428
|
if (nextItem) {
|
|
429
|
+
flushSync(() => {
|
|
430
|
+
setSelectedIndex(nextItem.index);
|
|
431
|
+
});
|
|
376
432
|
scrollToItem(nextItem);
|
|
377
|
-
setSelectedIndex(nextItem.index);
|
|
378
433
|
}
|
|
379
434
|
};
|
|
380
435
|
const inFocus = useIsInFocus();
|
|
@@ -392,19 +447,11 @@ export const List = (props) => {
|
|
|
392
447
|
.filter((item) => item.index !== -1)
|
|
393
448
|
.sort((a, b) => a.index - b.index);
|
|
394
449
|
const currentItem = items.find((item) => item.index === selectedIndex);
|
|
395
|
-
// Handle Ctrl+K to show actions
|
|
450
|
+
// Handle Ctrl+K to show actions dialog via portal
|
|
396
451
|
if (evt.name === 'k' && evt.ctrl) {
|
|
397
|
-
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
// Otherwise show List's own actions
|
|
402
|
-
else if (props.actions) {
|
|
403
|
-
dialog.pushActions(props.actions);
|
|
404
|
-
}
|
|
405
|
-
// Otherwise show empty ActionPanel (still has Settings section with Configure Extension, etc.)
|
|
406
|
-
else {
|
|
407
|
-
dialog.pushActions(_jsx(ActionPanel, {}));
|
|
452
|
+
const hasActions = currentItem?.props?.actions || props.actions;
|
|
453
|
+
if (hasActions) {
|
|
454
|
+
useStore.setState({ showActionsDialog: true });
|
|
408
455
|
}
|
|
409
456
|
return;
|
|
410
457
|
}
|
|
@@ -412,13 +459,12 @@ export const List = (props) => {
|
|
|
412
459
|
move(-1);
|
|
413
460
|
if (evt.name === 'down')
|
|
414
461
|
move(1);
|
|
415
|
-
// Handle Enter to execute first action
|
|
462
|
+
// Handle Enter to auto-execute first action via ActionPanel
|
|
416
463
|
if (evt.name === 'return') {
|
|
417
464
|
if (!currentItem?.props)
|
|
418
465
|
return;
|
|
419
466
|
if (currentItem.props.actions) {
|
|
420
467
|
useStore.setState({ shouldAutoExecuteFirstAction: true });
|
|
421
|
-
dialog.pushActions(currentItem.props.actions);
|
|
422
468
|
}
|
|
423
469
|
}
|
|
424
470
|
});
|
|
@@ -451,31 +497,33 @@ export const List = (props) => {
|
|
|
451
497
|
flexGrow: 1,
|
|
452
498
|
flexDirection: 'row',
|
|
453
499
|
flexShrink: 1,
|
|
454
|
-
}, children: [_jsx("text", { flexShrink: 0, fg:
|
|
500
|
+
}, children: [_jsx("text", { flexShrink: 0, fg: theme.textMuted, children: "> " }), _jsx("textarea", { ref: setInputRef, height: 1, flexGrow: 1, wrapMode: 'none', keyBindings: [
|
|
455
501
|
{ name: 'return', action: 'submit' },
|
|
456
502
|
{ name: 'linefeed', action: 'submit' },
|
|
457
503
|
], placeholder: searchBarPlaceholder, focused: inFocus && !isDropdownOpen, initialValue: searchText, onContentChange: () => {
|
|
458
504
|
const value = inputRef.current?.plainText || '';
|
|
459
505
|
handleSearchChange(value);
|
|
460
|
-
}, focusedBackgroundColor:
|
|
506
|
+
}, focusedBackgroundColor: theme.backgroundPanel, cursorColor: theme.primary, focusedTextColor: theme.text })] }), searchBarAccessory] }) }), _jsxs("box", { style: { flexDirection: 'row', flexGrow: 1, flexShrink: 1 }, children: [_jsxs("box", { style: { width: isShowingDetail ? '50%' : '100%', flexGrow: 1, flexShrink: 1, flexDirection: 'column' }, children: [_jsx(ScrollBox, { ref: scrollBoxRef, focused: false, flexGrow: 1, flexShrink: 1, minHeight: 6, style: {
|
|
461
507
|
rootOptions: {
|
|
462
508
|
backgroundColor: undefined,
|
|
463
509
|
},
|
|
464
510
|
scrollbarOptions: {
|
|
465
511
|
showArrows: true,
|
|
466
512
|
},
|
|
467
|
-
}, children: _jsx(ListItemsRenderer, { children: children }) }), _jsx(ListFooter, {}),
|
|
513
|
+
}, children: _jsx(ListItemsRenderer, { children: children }) }), _jsx(ListFooter, {}), _jsx(CurrentItemActionsOffscreen, { selectedIndex: selectedIndex, fallbackActions: props.actions })] }), isShowingDetail && currentDetail && (_jsx("box", { style: {
|
|
468
514
|
marginTop: 1,
|
|
469
515
|
width: '50%',
|
|
470
516
|
paddingLeft: 1,
|
|
471
517
|
paddingRight: 1,
|
|
472
|
-
}, border: ['left'], borderStyle: 'single', borderColor:
|
|
518
|
+
}, border: ['left'], borderStyle: 'single', borderColor: theme.border, children: currentDetail }))] })] }) }) }));
|
|
473
519
|
};
|
|
474
|
-
|
|
520
|
+
// Wrapper component that only renders children when no visible items exist
|
|
521
|
+
function ShowOnNoItems(props) {
|
|
475
522
|
// Subscribe to re-render when items are added/removed
|
|
476
523
|
void useListDescendantsRerender();
|
|
477
524
|
// Get live map ref for reading in useLayoutEffect
|
|
478
525
|
const map = useListDescendantsMap();
|
|
526
|
+
const listContext = useContext(ListContext);
|
|
479
527
|
const [hasVisibleItems, setHasVisibleItems] = useState(true);
|
|
480
528
|
// We must check visibility in useLayoutEffect because:
|
|
481
529
|
// 1. map.current is cleared by reset() during render, so it's empty if read during render
|
|
@@ -486,19 +534,25 @@ function DefaultEmptyView() {
|
|
|
486
534
|
useLayoutEffect(() => {
|
|
487
535
|
const items = Object.values(map.current)
|
|
488
536
|
.filter((item) => item.index !== -1 && item.props?.visible !== false);
|
|
489
|
-
|
|
537
|
+
// For default empty view, also check if custom empty view exists
|
|
538
|
+
const hasCustomEmptyView = !props.isCustomEmptyView && (listContext?.customEmptyViewRef.current ?? false);
|
|
539
|
+
setHasVisibleItems(items.length > 0 || hasCustomEmptyView);
|
|
490
540
|
});
|
|
491
541
|
if (hasVisibleItems)
|
|
492
542
|
return null;
|
|
493
|
-
return
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
543
|
+
return props.children;
|
|
544
|
+
}
|
|
545
|
+
function DefaultEmptyView() {
|
|
546
|
+
const theme = useTheme();
|
|
547
|
+
return (_jsx(ShowOnNoItems, { children: _jsx("box", { style: {
|
|
548
|
+
flexDirection: 'column',
|
|
549
|
+
alignItems: 'center',
|
|
550
|
+
justifyContent: 'center',
|
|
551
|
+
paddingTop: 2,
|
|
552
|
+
paddingBottom: 2,
|
|
553
|
+
paddingLeft: 2,
|
|
554
|
+
paddingRight: 2,
|
|
555
|
+
}, children: _jsx("text", { flexShrink: 0, fg: theme.textMuted, children: "No items found" }) }) }));
|
|
502
556
|
}
|
|
503
557
|
// Component to render list items and sections
|
|
504
558
|
function ListItemsRenderer(props) {
|
|
@@ -545,8 +599,8 @@ const ListItem = (props) => {
|
|
|
545
599
|
// Check if this item is selected
|
|
546
600
|
const selectedIndex = listContext?.selectedIndex ?? 0;
|
|
547
601
|
const isActive = index === selectedIndex;
|
|
548
|
-
// Update detail when this item becomes active or detail prop changes
|
|
549
|
-
|
|
602
|
+
// Update detail when this item becomes active or detail prop changes (before paint)
|
|
603
|
+
useLayoutEffect(() => {
|
|
550
604
|
if (isActive && listContext?.isShowingDetail && listContext?.setCurrentDetail) {
|
|
551
605
|
listContext.setCurrentDetail(props.detail || null);
|
|
552
606
|
}
|
|
@@ -559,7 +613,10 @@ const ListItem = (props) => {
|
|
|
559
613
|
if (listContext && index !== -1) {
|
|
560
614
|
// If clicking on already selected item, show actions (like pressing Enter)
|
|
561
615
|
if (isActive) {
|
|
562
|
-
|
|
616
|
+
// Show actions dialog via portal
|
|
617
|
+
if (props.actions) {
|
|
618
|
+
useStore.setState({ showActionsDialog: true });
|
|
619
|
+
}
|
|
563
620
|
}
|
|
564
621
|
else if (listContext.setSelectedIndex) {
|
|
565
622
|
// Otherwise just select the item
|
|
@@ -596,8 +653,9 @@ const ListItem = (props) => {
|
|
|
596
653
|
return (_jsx(ListItemRow, { title: titleText, subtitle: subtitleText, icon: iconValue, iconColor: iconColor, accessories: showAccessories ? props.accessories : undefined, active: isActive, isShowingDetail: props.detail !== undefined, onMouseDown: handleMouseDown, index: index, ref: elementRef }));
|
|
597
654
|
};
|
|
598
655
|
const ListItemDetail = (props) => {
|
|
656
|
+
const theme = useTheme();
|
|
599
657
|
const { isLoading, markdown, metadata } = props;
|
|
600
|
-
return (_jsxs("box", { style: { flexDirection: 'column', flexGrow: 1 }, children: [isLoading && (_jsx("box", { style: { paddingBottom: 1 }, children: _jsx("text", { flexShrink: 0, fg:
|
|
658
|
+
return (_jsxs("box", { style: { flexDirection: 'column', flexGrow: 1 }, children: [isLoading && (_jsx("box", { style: { paddingBottom: 1 }, children: _jsx("text", { flexShrink: 0, fg: theme.textMuted, children: "Loading..." }) })), _jsx(ScrollBox, { focused: false,
|
|
601
659
|
// flexGrow={1}
|
|
602
660
|
flexShrink: 1, style: {
|
|
603
661
|
rootOptions: {
|
|
@@ -606,32 +664,24 @@ const ListItemDetail = (props) => {
|
|
|
606
664
|
scrollbarOptions: {
|
|
607
665
|
showArrows: true,
|
|
608
666
|
},
|
|
609
|
-
}, children: _jsxs("box", { style: { flexDirection: 'column' }, children: [markdown && (_jsx("code", { content: markdown, filetype: "markdown", syntaxStyle: markdownSyntaxStyle, drawUnstyledText: false })), metadata && (_jsx("box", { style: { paddingTop: 1 },
|
|
610
|
-
};
|
|
611
|
-
const ListItemDetailMetadata = (props) => {
|
|
612
|
-
return (_jsx("box", { style: { flexDirection: 'column' }, children: props.children }));
|
|
667
|
+
}, children: _jsxs("box", { gap: 1, style: { flexDirection: 'column' }, children: [markdown && (_jsx("code", { content: markdown, filetype: "markdown", syntaxStyle: markdownSyntaxStyle, drawUnstyledText: false })), metadata && (_jsx("box", { style: { paddingTop: 1 }, children: metadata }))] }) })] }));
|
|
613
668
|
};
|
|
614
|
-
|
|
615
|
-
|
|
669
|
+
import { Metadata, MetadataContext } from 'termcast/src/components/metadata';
|
|
670
|
+
// List.Item.Detail.Metadata config: smaller padding for compact list detail panel
|
|
671
|
+
const listDetailMetadataConfig = {
|
|
672
|
+
maxValueLen: 20,
|
|
673
|
+
titleMinWidth: 12,
|
|
674
|
+
paddingBottom: 0.5,
|
|
675
|
+
separatorWidth: 17,
|
|
616
676
|
};
|
|
617
|
-
const
|
|
618
|
-
return (_jsx(
|
|
619
|
-
};
|
|
620
|
-
const ListItemDetailMetadataLink = (props) => {
|
|
621
|
-
return (_jsxs("box", { style: { flexDirection: 'column', paddingBottom: 0.5 }, children: [_jsxs("text", { flexShrink: 0, fg: Theme.textMuted, children: [props.title, ":"] }), _jsx("text", { flexShrink: 0, fg: Theme.markdownLink, children: props.text })] }));
|
|
622
|
-
};
|
|
623
|
-
const ListItemDetailMetadataTagList = (props) => {
|
|
624
|
-
return (_jsxs("box", { style: { flexDirection: 'column', paddingBottom: 0.5 }, children: [_jsxs("text", { flexShrink: 0, fg: Theme.textMuted, children: [props.title, ":"] }), _jsx("box", { style: { flexDirection: 'row', paddingLeft: 1 }, children: props.children })] }));
|
|
625
|
-
};
|
|
626
|
-
const ListItemDetailMetadataTagListItem = (props) => {
|
|
627
|
-
return (_jsx("box", { style: { paddingRight: 1 }, children: _jsxs("text", { flexShrink: 0, fg: resolveColor(props.color) || Theme.accent, children: ["[", props.text, "]"] }) }));
|
|
677
|
+
const ListItemDetailMetadata = (props) => {
|
|
678
|
+
return (_jsx(MetadataContext.Provider, { value: listDetailMetadataConfig, children: _jsx("box", { style: { flexDirection: 'column' }, children: props.children }) }));
|
|
628
679
|
};
|
|
629
680
|
ListItemDetail.Metadata = ListItemDetailMetadata;
|
|
630
|
-
ListItemDetailMetadata.Label =
|
|
631
|
-
ListItemDetailMetadata.Separator =
|
|
632
|
-
ListItemDetailMetadata.Link =
|
|
633
|
-
ListItemDetailMetadata.TagList =
|
|
634
|
-
ListItemDetailMetadataTagList.Item = ListItemDetailMetadataTagListItem;
|
|
681
|
+
ListItemDetailMetadata.Label = Metadata.Label;
|
|
682
|
+
ListItemDetailMetadata.Separator = Metadata.Separator;
|
|
683
|
+
ListItemDetailMetadata.Link = Metadata.Link;
|
|
684
|
+
ListItemDetailMetadata.TagList = Metadata.TagList;
|
|
635
685
|
ListItem.Detail = ListItemDetail;
|
|
636
686
|
/**
|
|
637
687
|
* A dropdown menu shown in the right-hand-side of the search bar.
|
|
@@ -642,6 +692,7 @@ ListItem.Detail = ListItemDetail;
|
|
|
642
692
|
* value="" (or your preferred reset value) at the top of your dropdown items.
|
|
643
693
|
*/
|
|
644
694
|
const ListDropdown = (props) => {
|
|
695
|
+
const theme = useTheme();
|
|
645
696
|
const listContext = useContext(ListContext);
|
|
646
697
|
const [isHovered, setIsHovered] = useState(false);
|
|
647
698
|
// If not inside a List, just render nothing (for type safety)
|
|
@@ -690,8 +741,8 @@ const ListDropdown = (props) => {
|
|
|
690
741
|
const dropdownContextValue = useMemo(() => ({
|
|
691
742
|
currentSection: undefined,
|
|
692
743
|
}), []);
|
|
693
|
-
// Open dropdown dialog when triggered
|
|
694
|
-
|
|
744
|
+
// Open dropdown dialog when triggered (before paint to avoid flash)
|
|
745
|
+
useLayoutEffect(() => {
|
|
695
746
|
if (isDropdownOpen && !dialog.stack.length) {
|
|
696
747
|
// Pass the children to the dialog to render them there
|
|
697
748
|
dialog.push({
|
|
@@ -728,15 +779,16 @@ const ListDropdown = (props) => {
|
|
|
728
779
|
// minWidth: value.length + 4,
|
|
729
780
|
flexDirection: 'row',
|
|
730
781
|
flexShrink: 0,
|
|
731
|
-
backgroundColor: isHovered ?
|
|
782
|
+
backgroundColor: isHovered ? theme.backgroundPanel : undefined,
|
|
732
783
|
}, onMouseMove: () => setIsHovered(true), onMouseOut: () => setIsHovered(false), onMouseDown: () => {
|
|
733
784
|
// Open dropdown when clicked
|
|
734
785
|
if (!isDropdownOpen) {
|
|
735
786
|
listContext.openDropdown();
|
|
736
787
|
}
|
|
737
|
-
}, children: [_jsx("text", { flexShrink: 0, fg: isHovered ?
|
|
788
|
+
}, children: [listContext.isLoading ? (_jsx(LoadingText, { isLoading: true, color: isHovered ? theme.text : theme.textMuted, children: displayValue || 'Loading...' })) : (_jsx("text", { flexShrink: 0, fg: isHovered ? theme.text : theme.textMuted, selectable: false, children: displayValue })), _jsxs("text", { flexShrink: 0, fg: isHovered ? theme.text : theme.textMuted, selectable: false, children: [' ', "\u25BE"] })] }, dropdownState.value)] }) }));
|
|
738
789
|
};
|
|
739
790
|
ListDropdown.Item = (props) => {
|
|
791
|
+
const theme = useTheme();
|
|
740
792
|
const dropdownContext = useContext(DropdownContext);
|
|
741
793
|
const [isHovered, setIsHovered] = useState(false);
|
|
742
794
|
// If not inside a Dropdown, just render nothing
|
|
@@ -781,22 +833,23 @@ ListDropdown.Item = (props) => {
|
|
|
781
833
|
return (_jsx("box", { style: {
|
|
782
834
|
flexDirection: 'row',
|
|
783
835
|
backgroundColor: isActive
|
|
784
|
-
?
|
|
836
|
+
? theme.primary
|
|
785
837
|
: isHovered
|
|
786
|
-
?
|
|
838
|
+
? theme.backgroundPanel
|
|
787
839
|
: undefined,
|
|
788
840
|
paddingLeft: isActive ? 0 : 1,
|
|
789
841
|
paddingRight: 1,
|
|
790
842
|
justifyContent: 'space-between',
|
|
791
|
-
}, border: false, onMouseMove: handleMouseMove, onMouseOut: () => setIsHovered(false), onMouseDown: handleMouseDown, children: _jsxs("box", { style: { flexDirection: 'row' }, children: [isActive && (_jsxs("text", { flexShrink: 0, fg:
|
|
792
|
-
?
|
|
843
|
+
}, border: false, onMouseMove: handleMouseMove, onMouseOut: () => setIsHovered(false), onMouseDown: handleMouseDown, children: _jsxs("box", { style: { flexDirection: 'row' }, children: [isActive && (_jsxs("text", { flexShrink: 0, fg: theme.background, selectable: false, children: ["\u203A", ''] })), _jsx("text", { flexShrink: 0, fg: isActive
|
|
844
|
+
? theme.background
|
|
793
845
|
: isCurrent
|
|
794
|
-
?
|
|
795
|
-
:
|
|
846
|
+
? theme.primary
|
|
847
|
+
: theme.text, attributes: isActive ? TextAttributes.BOLD : undefined, selectable: false, children: props.title })] }) }));
|
|
796
848
|
}
|
|
797
849
|
return null;
|
|
798
850
|
};
|
|
799
851
|
ListDropdown.Section = (props) => {
|
|
852
|
+
const theme = useTheme();
|
|
800
853
|
const parentContext = useContext(DropdownContext);
|
|
801
854
|
// If not inside a Dropdown, just render nothing
|
|
802
855
|
if (!parentContext) {
|
|
@@ -811,23 +864,25 @@ ListDropdown.Section = (props) => {
|
|
|
811
864
|
const showTitle = parentContext.selectedIndex !== undefined &&
|
|
812
865
|
props.title &&
|
|
813
866
|
!parentContext.searchText?.trim();
|
|
814
|
-
return (_jsxs(_Fragment, { children: [showTitle && (_jsx("box", { style: { paddingTop: 1, paddingLeft: 1 }, children: _jsx("text", { flexShrink: 0, fg:
|
|
867
|
+
return (_jsxs(_Fragment, { children: [showTitle && (_jsx("box", { style: { paddingTop: 1, paddingLeft: 1 }, children: _jsx("text", { flexShrink: 0, fg: theme.accent, attributes: TextAttributes.BOLD, children: props.title }) })), _jsx(DropdownContext.Provider, { value: sectionContextValue, children: props.children })] }));
|
|
815
868
|
};
|
|
816
869
|
List.Item = ListItem;
|
|
817
870
|
const ListSection = (props) => {
|
|
871
|
+
const theme = useTheme();
|
|
818
872
|
const parentContext = useContext(ListSectionContext);
|
|
819
873
|
const listContext = useContext(ListContext);
|
|
820
874
|
const searchText = listContext?.searchText || '';
|
|
821
|
-
// Don't render empty sections
|
|
822
|
-
if (React.Children.count(props.children) === 0) {
|
|
823
|
-
return null;
|
|
824
|
-
}
|
|
825
875
|
// Create new context with section title and search text
|
|
876
|
+
// NOTE: Must be called before any early returns to satisfy React hooks rules
|
|
826
877
|
const sectionContextValue = useMemo(() => ({
|
|
827
878
|
...parentContext,
|
|
828
879
|
sectionTitle: props.title,
|
|
829
880
|
searchText,
|
|
830
881
|
}), [parentContext, props.title, searchText]);
|
|
882
|
+
// Don't render empty sections
|
|
883
|
+
if (React.Children.count(props.children) === 0) {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
831
886
|
const isSearching = searchText.trim().length > 0;
|
|
832
887
|
const children = (_jsx(ListSectionContext.Provider, { value: sectionContextValue, children: props.children }));
|
|
833
888
|
if (isSearching) {
|
|
@@ -835,43 +890,31 @@ const ListSection = (props) => {
|
|
|
835
890
|
}
|
|
836
891
|
return (_jsxs("box", { style: { marginBottom: 1 }, children: [props.title && (_jsx("box", { border: false, style: {
|
|
837
892
|
paddingLeft: 1,
|
|
838
|
-
}, children: _jsx("text", { flexShrink: 0, fg:
|
|
893
|
+
}, children: _jsx("text", { flexShrink: 0, fg: theme.accent, attributes: TextAttributes.BOLD, children: props.title }) })), children] }));
|
|
839
894
|
};
|
|
840
895
|
List.Section = ListSection;
|
|
841
896
|
List.Dropdown = ListDropdown;
|
|
842
|
-
|
|
843
|
-
|
|
897
|
+
// Inner component for EmptyView content (needs hooks at top level)
|
|
898
|
+
function EmptyViewContent(props) {
|
|
899
|
+
const theme = useTheme();
|
|
844
900
|
const inFocus = useIsInFocus();
|
|
845
901
|
// Handle keyboard for actions
|
|
846
902
|
useKeyboard((evt) => {
|
|
847
903
|
if (!inFocus)
|
|
848
904
|
return;
|
|
849
|
-
// Handle Ctrl+K to show actions
|
|
905
|
+
// Handle Ctrl+K to show actions dialog via portal
|
|
850
906
|
if (evt.name === 'k' && evt.ctrl) {
|
|
851
|
-
|
|
907
|
+
if (props.actions) {
|
|
908
|
+
useStore.setState({ showActionsDialog: true });
|
|
909
|
+
}
|
|
852
910
|
return;
|
|
853
911
|
}
|
|
854
|
-
// Handle Enter to execute first action
|
|
912
|
+
// Handle Enter to auto-execute first action via ActionPanel
|
|
855
913
|
if (evt.name === 'return' && props.actions) {
|
|
856
914
|
useStore.setState({ shouldAutoExecuteFirstAction: true });
|
|
857
|
-
dialog.pushActions(props.actions);
|
|
858
915
|
}
|
|
859
916
|
});
|
|
860
|
-
|
|
861
|
-
const getIconString = (icon) => {
|
|
862
|
-
if (typeof icon === 'string') {
|
|
863
|
-
return getIconEmoji(icon);
|
|
864
|
-
}
|
|
865
|
-
if (icon && typeof icon === 'object' && 'source' in icon) {
|
|
866
|
-
// For { source: string } or { source: { light, dark } } objects
|
|
867
|
-
const source = icon.source;
|
|
868
|
-
if (typeof source === 'string') {
|
|
869
|
-
return getIconEmoji(source);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
return null;
|
|
873
|
-
};
|
|
874
|
-
const iconEmoji = props.icon ? getIconString(props.icon) : null;
|
|
917
|
+
const iconEmoji = props.icon ? getIconValue(props.icon) || null : null;
|
|
875
918
|
return (_jsxs("box", { style: {
|
|
876
919
|
flexDirection: 'column',
|
|
877
920
|
alignItems: 'center',
|
|
@@ -882,7 +925,20 @@ List.EmptyView = (props) => {
|
|
|
882
925
|
paddingLeft: 2,
|
|
883
926
|
paddingRight: 2,
|
|
884
927
|
gap: 1,
|
|
885
|
-
}, children: [iconEmoji && (_jsx("text", { flexShrink: 0, fg:
|
|
928
|
+
}, children: [iconEmoji && (_jsx("text", { flexShrink: 0, fg: theme.textMuted, style: { marginBottom: 1 }, children: iconEmoji })), props.title && (_jsx("text", { flexShrink: 0, fg: theme.text, attributes: TextAttributes.BOLD, children: props.title?.replace(/\bRaycast\b/g, 'Termcast').replace(/\braycast\b/g, 'termcast') || '' })), props.description && (_jsx("text", { flexShrink: 0, fg: theme.textMuted, wrapMode: 'word', children: props.description?.replace(/\bRaycast\b/g, 'Termcast').replace(/\braycast\b/g, 'termcast') || '' })), props.actions && _jsx(Offscreen, { children: props.actions })] }));
|
|
929
|
+
}
|
|
930
|
+
List.EmptyView = (props) => {
|
|
931
|
+
const listContext = useContext(ListContext);
|
|
932
|
+
// Register that a custom empty view exists
|
|
933
|
+
useLayoutEffect(() => {
|
|
934
|
+
if (listContext?.customEmptyViewRef) {
|
|
935
|
+
listContext.customEmptyViewRef.current = true;
|
|
936
|
+
return () => {
|
|
937
|
+
listContext.customEmptyViewRef.current = false;
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
}, [listContext]);
|
|
941
|
+
return (_jsx(ShowOnNoItems, { isCustomEmptyView: true, children: _jsx(EmptyViewContent, { ...props }) }));
|
|
886
942
|
};
|
|
887
943
|
export default List;
|
|
888
944
|
// Grid uses List internally with a different visual representation
|