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
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Custom Renderable Form Example
|
|
4
|
+
*
|
|
5
|
+
* Demonstrates hybrid pattern for building forms:
|
|
6
|
+
* - Form container = custom renderable (owns focus, navigation, scroll, RHF methods)
|
|
7
|
+
* - Form fields = React components wrapped in self-registering renderables
|
|
8
|
+
* - Tests dynamic field registration via timeout
|
|
9
|
+
*
|
|
10
|
+
* ## Architecture
|
|
11
|
+
*
|
|
12
|
+
* CustomFormRenderable (custom renderable)
|
|
13
|
+
* ├── owns: react-hook-form methods, focusedFieldId, field registry, scrollbox
|
|
14
|
+
* ├── methods: registerField(), focusField(), focusNext(), focusPrev()
|
|
15
|
+
* │
|
|
16
|
+
* └── CustomFormFieldWrapperRenderable (thin wrapper)
|
|
17
|
+
* ├── self-registers via onLifecyclePass (SYNC - no timing issues)
|
|
18
|
+
* └── React children (label, textarea) rendered inside
|
|
19
|
+
*
|
|
20
|
+
* ## Key Patterns
|
|
21
|
+
*
|
|
22
|
+
* 1. Wrapper renderable self-registers synchronously when added to tree
|
|
23
|
+
* 2. No useEffect needed for registration - onLifecyclePass handles it
|
|
24
|
+
* 3. Focus state in parent, checked in children each render
|
|
25
|
+
* 4. react-hook-form: useForm() in React, passed as prop to renderable, exposed via context
|
|
26
|
+
* 5. Tab navigation via parent methods (focusNext/focusPrev)
|
|
27
|
+
*/
|
|
28
|
+
import { BoxRenderable, TextRenderable, ScrollBoxRenderable, } from '@opentui/core';
|
|
29
|
+
import { extend, useKeyboard } from '@opentui/react';
|
|
30
|
+
import { useIsInFocus } from 'termcast/src/internal/focus-context';
|
|
31
|
+
import { useRef, createContext, useContext, useState, useEffect } from 'react';
|
|
32
|
+
// Generic helper to find parent of specific type by traversing up
|
|
33
|
+
function findParent(node, type) {
|
|
34
|
+
let current = node.parent;
|
|
35
|
+
while (current) {
|
|
36
|
+
if (current instanceof type) {
|
|
37
|
+
return current;
|
|
38
|
+
}
|
|
39
|
+
current = current.parent;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
import { renderWithProviders } from '../../utils';
|
|
44
|
+
import { useForm } from 'react-hook-form';
|
|
45
|
+
import { Theme } from 'termcast/src/theme';
|
|
46
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
47
|
+
// CustomFormRenderable
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
+
class CustomFormRenderable extends BoxRenderable {
|
|
50
|
+
// RHF integration - set by React as prop
|
|
51
|
+
formControl;
|
|
52
|
+
// Field registry - replaces useDescendants
|
|
53
|
+
fields = new Map();
|
|
54
|
+
fieldOrder = []; // ordered list for tab navigation
|
|
55
|
+
nextOrder = 0;
|
|
56
|
+
// Focus state
|
|
57
|
+
_focusedFieldId = null;
|
|
58
|
+
// UI components
|
|
59
|
+
scrollBox;
|
|
60
|
+
statusText;
|
|
61
|
+
constructor(ctx, options) {
|
|
62
|
+
super(ctx, { ...options, flexDirection: 'column' });
|
|
63
|
+
this.scrollBox = new ScrollBoxRenderable(ctx, { flexGrow: 1 });
|
|
64
|
+
this.statusText = new TextRenderable(ctx, { content: '0 fields', fg: Theme.textMuted });
|
|
65
|
+
super.add(this.scrollBox);
|
|
66
|
+
super.add(this.statusText);
|
|
67
|
+
}
|
|
68
|
+
// React children go to scrollbox
|
|
69
|
+
add(child, index) {
|
|
70
|
+
return this.scrollBox.add(child, index);
|
|
71
|
+
}
|
|
72
|
+
// React reconciler calls insertBefore when a new child must be inserted
|
|
73
|
+
// before an existing sibling. Example: delayed field inserted before "Field 1"
|
|
74
|
+
// Without this, dynamic children cause "Anchor does not exist" error
|
|
75
|
+
insertBefore(child, anchor) {
|
|
76
|
+
return this.scrollBox.insertBefore(child, anchor);
|
|
77
|
+
}
|
|
78
|
+
// Delegate remove to scrollbox
|
|
79
|
+
remove(id) {
|
|
80
|
+
this.scrollBox.remove(id);
|
|
81
|
+
}
|
|
82
|
+
// --- RHF Integration ---
|
|
83
|
+
// formControl is set by React as prop, use it directly
|
|
84
|
+
getValues() {
|
|
85
|
+
return this.formControl?.getValues() ?? {};
|
|
86
|
+
}
|
|
87
|
+
setValue(name, value) {
|
|
88
|
+
this.formControl?.setValue(name, value);
|
|
89
|
+
}
|
|
90
|
+
// --- Field Registration ---
|
|
91
|
+
// Called by wrapper renderables via onLifecyclePass (SYNC)
|
|
92
|
+
registerField(id, wrapper) {
|
|
93
|
+
this.fields.set(id, { id, elementRef: wrapper, order: this.nextOrder++ });
|
|
94
|
+
this.updateFieldOrder();
|
|
95
|
+
// Update status text immediately
|
|
96
|
+
this.statusText.content = `${this.fieldOrder.length} fields registered`;
|
|
97
|
+
this.requestRender();
|
|
98
|
+
// Auto-focus first field
|
|
99
|
+
if (this.fieldOrder.length === 1 && !this._focusedFieldId) {
|
|
100
|
+
this._focusedFieldId = id;
|
|
101
|
+
if (wrapper instanceof CustomFormFieldWrapperRenderable) {
|
|
102
|
+
wrapper.setFocused(true);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
unregisterField(id) {
|
|
107
|
+
this.fields.delete(id);
|
|
108
|
+
this.updateFieldOrder();
|
|
109
|
+
this.statusText.content = `${this.fieldOrder.length} fields registered`;
|
|
110
|
+
this.requestRender();
|
|
111
|
+
}
|
|
112
|
+
updateFieldOrder() {
|
|
113
|
+
this.fieldOrder = Array.from(this.fields.values())
|
|
114
|
+
.sort((a, b) => a.order - b.order)
|
|
115
|
+
.map((f) => f.id);
|
|
116
|
+
}
|
|
117
|
+
// --- Focus Management ---
|
|
118
|
+
get focusedFieldId() {
|
|
119
|
+
return this._focusedFieldId;
|
|
120
|
+
}
|
|
121
|
+
focusField(id) {
|
|
122
|
+
if (!this.fields.has(id))
|
|
123
|
+
return;
|
|
124
|
+
// Update focus state on wrappers
|
|
125
|
+
const oldField = this._focusedFieldId ? this.fields.get(this._focusedFieldId) : null;
|
|
126
|
+
const newField = this.fields.get(id);
|
|
127
|
+
if (oldField?.elementRef instanceof CustomFormFieldWrapperRenderable) {
|
|
128
|
+
oldField.elementRef.setFocused(false);
|
|
129
|
+
}
|
|
130
|
+
if (newField?.elementRef instanceof CustomFormFieldWrapperRenderable) {
|
|
131
|
+
newField.elementRef.setFocused(true);
|
|
132
|
+
}
|
|
133
|
+
this._focusedFieldId = id;
|
|
134
|
+
this.scrollToField(id);
|
|
135
|
+
this.requestRender();
|
|
136
|
+
}
|
|
137
|
+
focusNext() {
|
|
138
|
+
if (this.fieldOrder.length === 0)
|
|
139
|
+
return;
|
|
140
|
+
const idx = this._focusedFieldId ? this.fieldOrder.indexOf(this._focusedFieldId) : -1;
|
|
141
|
+
const nextIdx = (idx + 1) % this.fieldOrder.length;
|
|
142
|
+
this.focusField(this.fieldOrder[nextIdx]);
|
|
143
|
+
}
|
|
144
|
+
focusPrev() {
|
|
145
|
+
if (this.fieldOrder.length === 0)
|
|
146
|
+
return;
|
|
147
|
+
const idx = this._focusedFieldId ? this.fieldOrder.indexOf(this._focusedFieldId) : 0;
|
|
148
|
+
const prevIdx = (idx - 1 + this.fieldOrder.length) % this.fieldOrder.length;
|
|
149
|
+
this.focusField(this.fieldOrder[prevIdx]);
|
|
150
|
+
}
|
|
151
|
+
scrollToField(id) {
|
|
152
|
+
const field = this.fields.get(id);
|
|
153
|
+
if (!field?.elementRef)
|
|
154
|
+
return;
|
|
155
|
+
const itemY = field.elementRef.y;
|
|
156
|
+
const scrollBoxY = this.scrollBox.content.y;
|
|
157
|
+
const viewportHeight = this.scrollBox.viewport?.height || 10;
|
|
158
|
+
const relativeY = itemY - scrollBoxY;
|
|
159
|
+
const targetScrollTop = relativeY - Math.floor(viewportHeight / 2);
|
|
160
|
+
this.scrollBox.scrollTop = Math.max(0, targetScrollTop);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
class CustomFormFieldWrapperRenderable extends BoxRenderable {
|
|
164
|
+
parentForm;
|
|
165
|
+
focusIndicator;
|
|
166
|
+
contentBox;
|
|
167
|
+
isFocusedField = false;
|
|
168
|
+
// Set by React after constructor
|
|
169
|
+
fieldId = '';
|
|
170
|
+
constructor(ctx, options) {
|
|
171
|
+
// Outer wrapper is a row: [indicator] [content column]
|
|
172
|
+
super(ctx, { ...options, flexDirection: 'row', paddingBottom: 1 });
|
|
173
|
+
// Focus indicator - updated by parent form
|
|
174
|
+
this.focusIndicator = new TextRenderable(ctx, { content: ' ', flexShrink: 0 });
|
|
175
|
+
super.add(this.focusIndicator);
|
|
176
|
+
// Content box holds React children in a column
|
|
177
|
+
this.contentBox = new BoxRenderable(ctx, { flexDirection: 'column', flexGrow: 1 });
|
|
178
|
+
super.add(this.contentBox);
|
|
179
|
+
// SYNC registration when added to tree - no timing issues
|
|
180
|
+
this.onLifecyclePass = () => {
|
|
181
|
+
if (!this.parentForm && this.fieldId) {
|
|
182
|
+
this.parentForm = findParent(this, CustomFormRenderable);
|
|
183
|
+
this.parentForm?.registerField(this.fieldId, this);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// React children go into the content box
|
|
188
|
+
add(child, index) {
|
|
189
|
+
return this.contentBox.add(child, index);
|
|
190
|
+
}
|
|
191
|
+
// React reconciler calls insertBefore when a new child must be inserted
|
|
192
|
+
// before an existing sibling. Not strictly needed here since wrapper children
|
|
193
|
+
// (title, textarea) are always added in fixed order, but included for safety
|
|
194
|
+
// in case future usage adds dynamic children inside the wrapper
|
|
195
|
+
insertBefore(child, anchor) {
|
|
196
|
+
return this.contentBox.insertBefore(child, anchor);
|
|
197
|
+
}
|
|
198
|
+
// Called by parent form when focus changes
|
|
199
|
+
setFocused(focused) {
|
|
200
|
+
if (this.isFocusedField === focused)
|
|
201
|
+
return;
|
|
202
|
+
this.isFocusedField = focused;
|
|
203
|
+
this.focusIndicator.content = focused ? '› ' : ' ';
|
|
204
|
+
this.focusIndicator.fg = focused ? Theme.primary : undefined;
|
|
205
|
+
this.requestRender();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
209
|
+
// Register with opentui
|
|
210
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
211
|
+
extend({
|
|
212
|
+
'custom-form': CustomFormRenderable,
|
|
213
|
+
'custom-form-field-wrapper': CustomFormFieldWrapperRenderable,
|
|
214
|
+
});
|
|
215
|
+
const CustomFormContext = createContext(null);
|
|
216
|
+
function useCustomForm() {
|
|
217
|
+
const ctx = useContext(CustomFormContext);
|
|
218
|
+
if (!ctx)
|
|
219
|
+
throw new Error('Must be inside CustomForm');
|
|
220
|
+
return ctx;
|
|
221
|
+
}
|
|
222
|
+
function CustomForm({ children }) {
|
|
223
|
+
const formRef = useRef(null);
|
|
224
|
+
const methods = useForm();
|
|
225
|
+
const inFocus = useIsInFocus();
|
|
226
|
+
// Tab navigation
|
|
227
|
+
useKeyboard((evt) => {
|
|
228
|
+
if (!inFocus || !formRef.current)
|
|
229
|
+
return;
|
|
230
|
+
if (evt.name === 'tab') {
|
|
231
|
+
if (evt.shift) {
|
|
232
|
+
formRef.current.focusPrev();
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
formRef.current.focusNext();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
// Pass formControl as prop to renderable, expose methods via context
|
|
240
|
+
return (_jsx(CustomFormContext.Provider, { value: { formRef, methods }, children: _jsx("custom-form", { ref: formRef, formControl: methods, flexGrow: 1, children: children }) }));
|
|
241
|
+
}
|
|
242
|
+
function CustomFormTextField({ id, title, placeholder }) {
|
|
243
|
+
const { formRef, methods } = useCustomForm();
|
|
244
|
+
const inputRef = useRef(null);
|
|
245
|
+
// No useEffect needed - wrapper self-registers via onLifecyclePass
|
|
246
|
+
// Check if focused - read from parent each render
|
|
247
|
+
const isFocused = formRef.current?.focusedFieldId === id;
|
|
248
|
+
// RHF integration - use methods from context (no useFormContext needed)
|
|
249
|
+
const registration = methods.register(id);
|
|
250
|
+
const handleContentChange = () => {
|
|
251
|
+
registration.onChange({
|
|
252
|
+
target: { name: id, value: inputRef.current?.plainText || '' },
|
|
253
|
+
type: 'change',
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
// Focus indicator is rendered by the wrapper renderable (updates synchronously)
|
|
257
|
+
return (_jsxs("custom-form-field-wrapper", { fieldId: id, children: [_jsx("text", { fg: isFocused ? Theme.primary : Theme.text, children: title }), _jsx("textarea", { ref: inputRef, height: 1, placeholder: placeholder, focused: isFocused, keyBindings: [
|
|
258
|
+
{ name: 'return', action: 'submit' },
|
|
259
|
+
{ name: 'linefeed', action: 'submit' },
|
|
260
|
+
], onContentChange: handleContentChange })] }));
|
|
261
|
+
}
|
|
262
|
+
// Add compound component
|
|
263
|
+
CustomForm.TextField = CustomFormTextField;
|
|
264
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
265
|
+
// Example with dynamic field via useEffect
|
|
266
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
267
|
+
function Example() {
|
|
268
|
+
const [showDelayedField, setShowDelayedField] = useState(false);
|
|
269
|
+
// Test: add field after timeout
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
const timer = setTimeout(() => {
|
|
272
|
+
setShowDelayedField(true);
|
|
273
|
+
}, 2000);
|
|
274
|
+
return () => {
|
|
275
|
+
clearTimeout(timer);
|
|
276
|
+
};
|
|
277
|
+
}, []);
|
|
278
|
+
return (_jsxs("box", { flexDirection: "column", padding: 1, flexGrow: 1, children: [_jsx("text", { marginBottom: 1, children: "Custom Form Renderable Example" }), _jsx("text", { fg: Theme.textMuted, marginBottom: 1, children: "Tab to navigate \u2022 Dynamic field appears after 2s" }), _jsxs(CustomForm, { children: [_jsx(CustomForm.TextField, { id: "name", title: "Name", placeholder: "Enter name..." }), _jsx(CustomForm.TextField, { id: "email", title: "Email", placeholder: "Enter email..." }), _jsx(CustomForm.TextField, { id: "phone", title: "Phone", placeholder: "Enter phone..." }), showDelayedField && (_jsx(CustomForm.TextField, { id: "delayed", title: "Delayed Field (added after 2s)", placeholder: "Dynamic field..." })), Array.from({ length: 8 }, (_, i) => (_jsx(CustomForm.TextField, { id: `field-${i}`, title: `Field ${i + 1}`, placeholder: `Enter field ${i + 1}...` }, `field-${i}`)))] })] }));
|
|
279
|
+
}
|
|
280
|
+
if (import.meta.main) {
|
|
281
|
+
renderWithProviders(_jsx(Example, {}));
|
|
282
|
+
}
|
|
283
|
+
export { CustomForm, CustomFormTextField, Example };
|
|
284
|
+
//# sourceMappingURL=custom-renderable-form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-renderable-form.js","sourceRoot":"","sources":["../../../src/examples/internal/custom-renderable-form.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-renderable-list-default-search.d.ts","sourceRoot":"","sources":["../../../src/examples/internal/custom-renderable-list-default-search.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Test file for defaultSearchQuery prop
|
|
4
|
+
*/
|
|
5
|
+
import { renderWithProviders } from '../../utils';
|
|
6
|
+
import { CustomList } from './custom-renderable-list';
|
|
7
|
+
const ITEMS = [
|
|
8
|
+
{ title: 'Apple', subtitle: 'Red fruit' },
|
|
9
|
+
{ title: 'Banana', subtitle: 'Yellow fruit' },
|
|
10
|
+
{ title: 'Cherry', subtitle: 'Small red fruit' },
|
|
11
|
+
];
|
|
12
|
+
function Test() {
|
|
13
|
+
return (_jsxs("box", { flexDirection: "column", padding: 1, flexGrow: 1, children: [_jsx("text", { marginBottom: 1, children: "Default Search Test" }), _jsx(CustomList, { defaultSearchQuery: "banana", children: ITEMS.map((item) => (_jsx(CustomList.Item, { title: item.title, subtitle: item.subtitle }, item.title))) })] }));
|
|
14
|
+
}
|
|
15
|
+
renderWithProviders(_jsx(Test, {}));
|
|
16
|
+
//# sourceMappingURL=custom-renderable-list-default-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-renderable-list-default-search.js","sourceRoot":"","sources":["../../../src/examples/internal/custom-renderable-list-default-search.tsx"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAErD,MAAM,KAAK,GAAG;IACZ,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE;IACzC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE;IAC7C,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE;CACjD,CAAA;AAED,SAAS,IAAI;IACX,OAAO,CACL,eAAK,aAAa,EAAC,QAAQ,EAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,aACjD,eAAM,YAAY,EAAE,CAAC,oCAA4B,EACjD,KAAC,UAAU,IAAC,kBAAkB,EAAC,QAAQ,YACpC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,KAAC,UAAU,CAAC,IAAI,IAAkB,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAtD,IAAI,CAAC,KAAK,CAAgD,CACjF,CAAC,GACS,IACT,CACP,CAAA;AACH,CAAC;AAED,mBAAmB,CAAC,KAAC,IAAI,KAAG,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-renderable-list-v2-default-search.d.ts","sourceRoot":"","sources":["../../../src/examples/internal/custom-renderable-list-v2-default-search.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Test file for defaultSearchQuery prop (v2)
|
|
4
|
+
*/
|
|
5
|
+
import { renderWithProviders } from '../../utils';
|
|
6
|
+
import { CustomList, useCustomListStore } from './custom-renderable-list-v2';
|
|
7
|
+
const ITEMS = [
|
|
8
|
+
{ title: 'Apple', subtitle: 'Red fruit' },
|
|
9
|
+
{ title: 'Banana', subtitle: 'Yellow fruit' },
|
|
10
|
+
{ title: 'Cherry', subtitle: 'Small red fruit' },
|
|
11
|
+
];
|
|
12
|
+
function Test() {
|
|
13
|
+
return (_jsxs("box", { flexDirection: "column", padding: 1, flexGrow: 1, children: [_jsx("text", { marginBottom: 1, children: "Default Search Test" }), _jsx(CustomList, { defaultSearchQuery: "banana", children: ITEMS.map((item) => (_jsx(CustomList.Item, { title: item.title, subtitle: item.subtitle }, item.title))) })] }));
|
|
14
|
+
}
|
|
15
|
+
// Reset store before running
|
|
16
|
+
useCustomListStore.setState({
|
|
17
|
+
selectedIndex: 0,
|
|
18
|
+
visibleCount: 0,
|
|
19
|
+
totalCount: 0,
|
|
20
|
+
searchQuery: '',
|
|
21
|
+
itemStates: {},
|
|
22
|
+
});
|
|
23
|
+
renderWithProviders(_jsx(Test, {}));
|
|
24
|
+
//# sourceMappingURL=custom-renderable-list-v2-default-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-renderable-list-v2-default-search.js","sourceRoot":"","sources":["../../../src/examples/internal/custom-renderable-list-v2-default-search.tsx"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAE5E,MAAM,KAAK,GAAG;IACZ,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE;IACzC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE;IAC7C,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE;CACjD,CAAA;AAED,SAAS,IAAI;IACX,OAAO,CACL,eAAK,aAAa,EAAC,QAAQ,EAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,aACjD,eAAM,YAAY,EAAE,CAAC,oCAA4B,EACjD,KAAC,UAAU,IAAC,kBAAkB,EAAC,QAAQ,YACpC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,KAAC,UAAU,CAAC,IAAI,IAAkB,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAtD,IAAI,CAAC,KAAK,CAAgD,CACjF,CAAC,GACS,IACT,CACP,CAAA;AACH,CAAC;AAED,6BAA6B;AAC7B,kBAAkB,CAAC,QAAQ,CAAC;IAC1B,aAAa,EAAE,CAAC;IAChB,YAAY,EAAE,CAAC;IACf,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,EAAE;IACf,UAAU,EAAE,EAAE;CACf,CAAC,CAAA;AAEF,mBAAmB,CAAC,KAAC,IAAI,KAAG,CAAC,CAAA"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Renderable List V2 - Wrapper Pattern
|
|
3
|
+
*
|
|
4
|
+
* This example uses opentui's extend() to register thin wrapper renderables
|
|
5
|
+
* that handle tracking and visibility, while React handles all UI rendering.
|
|
6
|
+
*
|
|
7
|
+
* ## Architecture
|
|
8
|
+
*
|
|
9
|
+
* CustomListRenderable (custom renderable)
|
|
10
|
+
* ├── owns: filtering logic, navigation, scrolling
|
|
11
|
+
* ├── children redirected to scrollBox
|
|
12
|
+
* │
|
|
13
|
+
* └── CustomListItemWrapperRenderable (thin wrapper)
|
|
14
|
+
* ├── tracks: keywords, onAction, section, visibleIndex, itemId
|
|
15
|
+
* ├── handles: visibility (height: 0 when hidden)
|
|
16
|
+
* └── React children render all UI (indicator, title, subtitle)
|
|
17
|
+
*
|
|
18
|
+
* ## Key Patterns
|
|
19
|
+
*
|
|
20
|
+
* 1. Wrapper renderables self-register via onLifecyclePass (SYNC)
|
|
21
|
+
* 2. All UI in JSX - wrappers only track/hide
|
|
22
|
+
* 3. Zustand store for React state (selectedIndex, visibleCount, searchQuery)
|
|
23
|
+
* 4. Renderable calls zustand.setState to trigger React re-renders
|
|
24
|
+
* 5. Items compare visibleIndex to selectedIndex from zustand
|
|
25
|
+
*
|
|
26
|
+
* ## Phase 1: Bidirectional Selection Sync
|
|
27
|
+
*
|
|
28
|
+
* - `selectedItemId` prop on List controls selection by item id
|
|
29
|
+
* - `onSelectionChange` callback fires when selection changes
|
|
30
|
+
* - Supports both controlled (via prop) and uncontrolled (internal) selection
|
|
31
|
+
*
|
|
32
|
+
* ## Phase 2: Detail Panel Support
|
|
33
|
+
*
|
|
34
|
+
* - `isShowingDetail` prop on List enables detail panel on the right
|
|
35
|
+
* - `detail` prop on Item provides React node to render in detail panel
|
|
36
|
+
* - Detail updates automatically when selection changes
|
|
37
|
+
* - List splits into two columns when detail is shown
|
|
38
|
+
*
|
|
39
|
+
* ## How React Props Work with opentui Renderables
|
|
40
|
+
*
|
|
41
|
+
* React props are applied via direct property assignment, NOT just constructor:
|
|
42
|
+
*
|
|
43
|
+
* 1. `createInstance()` - constructor called (props passed in options)
|
|
44
|
+
* 2. `setInitialProperties()` - iterates props, does `instance[propKey] = propValue`
|
|
45
|
+
* 3. `commitUpdate()` - on re-render, applies changed props via `instance[propKey] = propValue`
|
|
46
|
+
*
|
|
47
|
+
* This means:
|
|
48
|
+
* - Props don't need to be read from constructor options - React sets them after
|
|
49
|
+
* - Simple props (just stored/read later) can be public fields
|
|
50
|
+
* - Props that need side effects on change require setters
|
|
51
|
+
* - Constructor should just create the renderable structure
|
|
52
|
+
*
|
|
53
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
54
|
+
* ## Migration Notes (Lessons from Form conversion)
|
|
55
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
*
|
|
57
|
+
* ### 1. Registration Order ≠ Visual Order
|
|
58
|
+
*
|
|
59
|
+
* IMPORTANT: `onLifecyclePass` is called in tree traversal order, which may NOT
|
|
60
|
+
* match the visual/React render order. For Form, we solved this by sorting
|
|
61
|
+
* fields by y-position instead of registration order:
|
|
62
|
+
*
|
|
63
|
+
* ```typescript
|
|
64
|
+
* private getFieldOrder(): string[] {
|
|
65
|
+
* return Array.from(this.fields.values())
|
|
66
|
+
* .sort((a, b) => (a.elementRef?.y ?? 0) - (b.elementRef?.y ?? 0))
|
|
67
|
+
* .map((f) => f.id)
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* For List, this is less critical since items use visibleIndex from filtering,
|
|
72
|
+
* but be aware if you need stable ordering based on visual position.
|
|
73
|
+
*
|
|
74
|
+
* ### 2. Auto-Focus Pattern
|
|
75
|
+
*
|
|
76
|
+
* Don't auto-focus in registerField() - registration order is unpredictable.
|
|
77
|
+
* Instead, use React useEffect that runs until focus is set:
|
|
78
|
+
*
|
|
79
|
+
* ```typescript
|
|
80
|
+
* useEffect(() => {
|
|
81
|
+
* if (focusedField) return
|
|
82
|
+
* const firstId = formRef.current?.getFirstFieldId()
|
|
83
|
+
* if (firstId) formRef.current?.focusField(firstId)
|
|
84
|
+
* })
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* ### 3. Hybrid Approach - Keep React for Styling
|
|
88
|
+
*
|
|
89
|
+
* The renderable should only handle STATE (registry, focus, scroll).
|
|
90
|
+
* Keep existing React components for UI (e.g., WithLeftBorder for Form).
|
|
91
|
+
* This ensures visual output stays identical - only the wiring changes.
|
|
92
|
+
*
|
|
93
|
+
* ### 4. Focus State Sync to React
|
|
94
|
+
*
|
|
95
|
+
* Form uses `onFocusChange` callback to sync focus to React state:
|
|
96
|
+
*
|
|
97
|
+
* ```typescript
|
|
98
|
+
* // In FormRenderable
|
|
99
|
+
* public onFocusChange?: (fieldId: string | null) => void
|
|
100
|
+
*
|
|
101
|
+
* focusField(id: string) {
|
|
102
|
+
* this._focusedFieldId = id
|
|
103
|
+
* this.onFocusChange?.(id) // Notify React
|
|
104
|
+
* }
|
|
105
|
+
*
|
|
106
|
+
* // In Form component
|
|
107
|
+
* const handleFormRef = (ref: FormRenderable | null) => {
|
|
108
|
+
* formRef.current = ref
|
|
109
|
+
* if (ref) ref.onFocusChange = setFocusedField
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* This example uses zustand instead (setState triggers re-render).
|
|
114
|
+
* Both patterns work - zustand is simpler for shared state.
|
|
115
|
+
*
|
|
116
|
+
* ### 5. Legacy Compatibility
|
|
117
|
+
*
|
|
118
|
+
* When migrating, keep old descendants/context during transition:
|
|
119
|
+
*
|
|
120
|
+
* ```typescript
|
|
121
|
+
* // Keep old system for components not yet migrated
|
|
122
|
+
* const { DescendantsProvider, useDescendant } = createDescendants<...>()
|
|
123
|
+
* export { useDescendant } // Still exported for old components
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* ### 6. Field Component Changes
|
|
127
|
+
*
|
|
128
|
+
* Each field component needs minimal changes:
|
|
129
|
+
* - Remove: useFormFieldDescendant() call, elementRef
|
|
130
|
+
* - Add: wrap return in <termcast-form-field-wrapper fieldId={id}>
|
|
131
|
+
* - Keep: all UI code (WithLeftBorder, etc.) unchanged
|
|
132
|
+
*
|
|
133
|
+
* ### 7. Testing Strategy
|
|
134
|
+
*
|
|
135
|
+
* Run existing tests with -u to update snapshots. Visual output should be
|
|
136
|
+
* identical - if tests fail, the wiring is wrong, not the rendering.
|
|
137
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
138
|
+
*/
|
|
139
|
+
import React from 'react';
|
|
140
|
+
interface ItemState {
|
|
141
|
+
visibleIndex: number;
|
|
142
|
+
}
|
|
143
|
+
interface CustomListStoreState {
|
|
144
|
+
selectedIndex: number;
|
|
145
|
+
visibleCount: number;
|
|
146
|
+
totalCount: number;
|
|
147
|
+
searchQuery: string;
|
|
148
|
+
itemStates: Record<string, ItemState>;
|
|
149
|
+
selectedItemId: string | null;
|
|
150
|
+
isShowingDetail: boolean;
|
|
151
|
+
currentDetailNode: React.ReactNode | null;
|
|
152
|
+
}
|
|
153
|
+
declare const useCustomListStore: import("zustand").UseBoundStore<import("zustand").StoreApi<CustomListStoreState>>;
|
|
154
|
+
interface ListProps {
|
|
155
|
+
children?: React.ReactNode;
|
|
156
|
+
placeholder?: string;
|
|
157
|
+
defaultSearchQuery?: string;
|
|
158
|
+
selectedItemId?: string | null;
|
|
159
|
+
onSelectionChange?: (id: string | null) => void;
|
|
160
|
+
isShowingDetail?: boolean;
|
|
161
|
+
}
|
|
162
|
+
declare function CustomList({ children, placeholder, defaultSearchQuery, selectedItemId, onSelectionChange, isShowingDetail }: ListProps): any;
|
|
163
|
+
declare namespace CustomList {
|
|
164
|
+
var Item: typeof CustomListItem;
|
|
165
|
+
var Section: typeof CustomListSection;
|
|
166
|
+
var EmptyView: typeof CustomListEmptyView;
|
|
167
|
+
}
|
|
168
|
+
interface ListItemProps {
|
|
169
|
+
id?: string;
|
|
170
|
+
title: string;
|
|
171
|
+
subtitle?: string;
|
|
172
|
+
keywords?: string[];
|
|
173
|
+
onAction?: () => void;
|
|
174
|
+
detail?: React.ReactNode;
|
|
175
|
+
}
|
|
176
|
+
declare function CustomListItem({ id, title, subtitle, keywords, onAction, detail }: ListItemProps): any;
|
|
177
|
+
interface ListSectionProps {
|
|
178
|
+
title?: string;
|
|
179
|
+
children?: React.ReactNode;
|
|
180
|
+
}
|
|
181
|
+
declare function CustomListSection({ title, children }: ListSectionProps): any;
|
|
182
|
+
interface ListEmptyViewProps {
|
|
183
|
+
title?: string;
|
|
184
|
+
description?: string;
|
|
185
|
+
}
|
|
186
|
+
declare function CustomListEmptyView({ title, description }: ListEmptyViewProps): any;
|
|
187
|
+
declare function Example(): any;
|
|
188
|
+
export { CustomList, CustomListItem, CustomListSection, CustomListEmptyView, Example, useCustomListStore };
|
|
189
|
+
//# sourceMappingURL=custom-renderable-list-v2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-renderable-list-v2.d.ts","sourceRoot":"","sources":["../../../src/examples/internal/custom-renderable-list-v2.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyIG;AAcH,OAAO,KAA2B,MAAM,OAAO,CAAA;AAS/C,UAAU,SAAS;IACjB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,UAAU,oBAAoB;IAC5B,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IAEnB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAIrC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAE7B,eAAe,EAAE,OAAO,CAAA;IACxB,iBAAiB,EAAE,KAAK,CAAC,SAAS,GAAG,IAAI,CAAA;CAC1C;AAED,QAAA,MAAM,kBAAkB,mFASrB,CAAA;AAihBH,UAAU,SAAS;IACjB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAE3B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAE/C,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,iBAAS,UAAU,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,EAAE,SAAS,GAAG,GAAG,CA4ErI;kBA5EQ,UAAU;;;;;AAkFnB,UAAU,aAAa;IACrB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IAErB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB;AAED,iBAAS,cAAc,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,aAAa,GAAG,GAAG,CA4B/F;AAMD,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B;AAED,iBAAS,iBAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,gBAAgB,GAAG,GAAG,CAYrE;AAMD,UAAU,kBAAkB;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,iBAAS,mBAAmB,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,kBAAkB,GAAG,GAAG,CAE5E;AAmCD,iBAAS,OAAO,IAAI,GAAG,CAoFtB;AAgBD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
|