a2uink 0.1.0
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/.eslintignore +4 -0
- package/.eslintrc.cjs +21 -0
- package/.gitattributes +5 -0
- package/.github/copilot-instructions.md +21 -0
- package/.github/workflows/ci.yml +31 -0
- package/.husky/pre-commit +6 -0
- package/.prettierignore +6 -0
- package/.prettierrc +7 -0
- package/README.md +44 -0
- package/dist/binding.d.ts +3 -0
- package/dist/binding.js +73 -0
- package/dist/catalog.d.ts +6 -0
- package/dist/catalog.js +165 -0
- package/dist/examples/demo.d.ts +1 -0
- package/dist/examples/demo.js +309 -0
- package/dist/focus.d.ts +15 -0
- package/dist/focus.js +68 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/renderer.d.ts +6 -0
- package/dist/renderer.js +144 -0
- package/dist/src/binding.d.ts +8 -0
- package/dist/src/binding.js +141 -0
- package/dist/src/catalog.d.ts +2 -0
- package/dist/src/catalog.js +1 -0
- package/dist/src/components/Box.d.ts +6 -0
- package/dist/src/components/Box.js +23 -0
- package/dist/src/components/Button.d.ts +7 -0
- package/dist/src/components/Button.js +71 -0
- package/dist/src/components/Chart.d.ts +5 -0
- package/dist/src/components/Chart.js +65 -0
- package/dist/src/components/Checkbox.d.ts +7 -0
- package/dist/src/components/Checkbox.js +51 -0
- package/dist/src/components/DateTimeInput.d.ts +1 -0
- package/dist/src/components/DateTimeInput.js +1 -0
- package/dist/src/components/Divider.d.ts +5 -0
- package/dist/src/components/Divider.js +7 -0
- package/dist/src/components/Image.d.ts +5 -0
- package/dist/src/components/Image.js +8 -0
- package/dist/src/components/Input.d.ts +7 -0
- package/dist/src/components/Input.js +124 -0
- package/dist/src/components/List.d.ts +5 -0
- package/dist/src/components/List.js +9 -0
- package/dist/src/components/Modal.d.ts +6 -0
- package/dist/src/components/Modal.js +13 -0
- package/dist/src/components/RadioGroup.d.ts +7 -0
- package/dist/src/components/RadioGroup.js +56 -0
- package/dist/src/components/Select.d.ts +7 -0
- package/dist/src/components/Select.js +66 -0
- package/dist/src/components/Slider.d.ts +7 -0
- package/dist/src/components/Slider.js +74 -0
- package/dist/src/components/Spacer.d.ts +1 -0
- package/dist/src/components/Spacer.js +1 -0
- package/dist/src/components/Table.d.ts +5 -0
- package/dist/src/components/Table.js +14 -0
- package/dist/src/components/Tabs.d.ts +7 -0
- package/dist/src/components/Tabs.js +56 -0
- package/dist/src/components/Text.d.ts +5 -0
- package/dist/src/components/Text.js +15 -0
- package/dist/src/components/helpers.d.ts +4 -0
- package/dist/src/components/helpers.js +39 -0
- package/dist/src/components/index.d.ts +16 -0
- package/dist/src/components/index.js +15 -0
- package/dist/src/components/renderNode.d.ts +4 -0
- package/dist/src/components/renderNode.js +61 -0
- package/dist/src/components/types.d.ts +7 -0
- package/dist/src/components/types.js +1 -0
- package/dist/src/components.d.ts +1 -0
- package/dist/src/components.js +1 -0
- package/dist/src/focus.d.ts +15 -0
- package/dist/src/focus.js +68 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/renderer.d.ts +6 -0
- package/dist/src/renderer.js +673 -0
- package/dist/src/tree.d.ts +2 -0
- package/dist/src/tree.js +47 -0
- package/dist/src/types.d.ts +92 -0
- package/dist/src/types.js +1 -0
- package/dist/tree.d.ts +2 -0
- package/dist/tree.js +45 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.js +1 -0
- package/docs/demo/README.md +90 -0
- package/docs/demo/app.js +268 -0
- package/docs/demo/index.html +14 -0
- package/docs/demo/package-lock.json +4512 -0
- package/docs/demo/package.json +32 -0
- package/docs/demo/src/App.tsx +1403 -0
- package/docs/demo/src/main.tsx +9 -0
- package/docs/demo/src/setEnv.ts +29 -0
- package/docs/demo/src/shims/fs.js +16 -0
- package/docs/demo/src/shims/process.js +10 -0
- package/docs/demo/src/styles.css +720 -0
- package/docs/demo/styles.css +1 -0
- package/docs/demo/tsconfig.json +14 -0
- package/docs/demo/vite-plugin-node-polyfills/shims/buffer +2 -0
- package/docs/demo/vite-plugin-node-polyfills/shims/global +2 -0
- package/docs/demo/vite-plugin-node-polyfills/shims/process +10 -0
- package/docs/demo/vite.config.js +200 -0
- package/docs/overview.md +277 -0
- package/examples/demo.d.ts +1 -0
- package/examples/demo.js +66 -0
- package/examples/demo.ts +315 -0
- package/package.json +48 -0
- package/src/binding.ts +191 -0
- package/src/catalog.ts +2 -0
- package/src/components/Box.ts +39 -0
- package/src/components/Button.ts +84 -0
- package/src/components/Checkbox.ts +66 -0
- package/src/components/DateTimeInput.ts +1 -0
- package/src/components/Divider.ts +8 -0
- package/src/components/Image.ts +15 -0
- package/src/components/Input.ts +148 -0
- package/src/components/List.ts +15 -0
- package/src/components/Modal.ts +21 -0
- package/src/components/RadioGroup.ts +77 -0
- package/src/components/Select.ts +94 -0
- package/src/components/Slider.ts +98 -0
- package/src/components/Spacer.ts +1 -0
- package/src/components/Table.ts +22 -0
- package/src/components/Tabs.ts +82 -0
- package/src/components/Text.ts +21 -0
- package/src/components/helpers.ts +42 -0
- package/src/components/index.ts +16 -0
- package/src/components/renderNode.ts +73 -0
- package/src/components/types.ts +8 -0
- package/src/components.ts +1 -0
- package/src/focus.ts +94 -0
- package/src/index.ts +12 -0
- package/src/renderer.ts +779 -0
- package/src/tree.ts +63 -0
- package/src/types.ts +110 -0
- package/tsconfig.json +16 -0
package/.eslintignore
ADDED
package/.eslintrc.cjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
parser: '@typescript-eslint/parser',
|
|
4
|
+
parserOptions: {
|
|
5
|
+
ecmaVersion: 2024,
|
|
6
|
+
sourceType: 'module'
|
|
7
|
+
},
|
|
8
|
+
plugins: ['@typescript-eslint'],
|
|
9
|
+
extends: [
|
|
10
|
+
'eslint:recommended',
|
|
11
|
+
'plugin:@typescript-eslint/recommended',
|
|
12
|
+
'prettier'
|
|
13
|
+
],
|
|
14
|
+
env: {
|
|
15
|
+
node: true,
|
|
16
|
+
es2024: true
|
|
17
|
+
},
|
|
18
|
+
rules: {
|
|
19
|
+
// project-specific rules can be added here
|
|
20
|
+
}
|
|
21
|
+
};
|
package/.gitattributes
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
- [x] Verify that the copilot-instructions.md file in the .github directory is created.
|
|
2
|
+
|
|
3
|
+
- [x] Clarify Project Requirements
|
|
4
|
+
|
|
5
|
+
- [x] Scaffold the Project
|
|
6
|
+
|
|
7
|
+
- [x] Customize the Project
|
|
8
|
+
|
|
9
|
+
- [x] Install Required Extensions
|
|
10
|
+
|
|
11
|
+
- [x] Compile the Project
|
|
12
|
+
|
|
13
|
+
- [x] Create and Run Task
|
|
14
|
+
|
|
15
|
+
- [x] Launch the Project
|
|
16
|
+
|
|
17
|
+
- [x] Ensure Documentation is Complete
|
|
18
|
+
|
|
19
|
+
- Work through each checklist item systematically.
|
|
20
|
+
- Keep communication concise and focused.
|
|
21
|
+
- Follow development best practices.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, master ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main, master ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: windows-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
node-version: [18.x, 20.x]
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Setup Node
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: ${{ matrix.node-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: npm ci
|
|
26
|
+
|
|
27
|
+
- name: Build
|
|
28
|
+
run: npm run build --if-present
|
|
29
|
+
|
|
30
|
+
- name: Test
|
|
31
|
+
run: npm test --if-present
|
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
A2UI v0.8 Ink wrapper MVP.
|
|
2
|
+
|
|
3
|
+
This library renders a minimal subset of A2UI components using Ink and React. It supports beginRendering, surfaceUpdate, dataModelUpdate, adjacency-list rendering, and basic user actions for input, selection, and buttons.
|
|
4
|
+
|
|
5
|
+
Use npm run demo to launch the sample surface renderer.
|
|
6
|
+
|
|
7
|
+
## Supported Features
|
|
8
|
+
- A2UI v0.8 render gate: beginRendering, surfaceUpdate, dataModelUpdate
|
|
9
|
+
- Adjacency-list tree reconstruction with explicit children and template lists
|
|
10
|
+
- Data binding via `BoundValue` (path + literal fallbacks)
|
|
11
|
+
- Ink-backed components: Text, Box, Spacer, Input, Select, Button, Checkbox, RadioGroup, List, Tabs, Table, Slider, Modal, Divider, Image, Chart
|
|
12
|
+
- Focus management with Tab / Shift+Tab navigation
|
|
13
|
+
- Action dispatch: onPress, onChange, onSubmit, onSelect
|
|
14
|
+
|
|
15
|
+
## Implementation Guide
|
|
16
|
+
1. Create a renderer instance with `createA2uiInkRenderer`.
|
|
17
|
+
2. Send A2UI server messages in this order:
|
|
18
|
+
- surfaceUpdate: component catalog and root ID
|
|
19
|
+
- dataModelUpdate: initial state
|
|
20
|
+
- beginRendering: unlock rendering
|
|
21
|
+
3. Handle `userAction` callbacks to round-trip input back to your A2UI server.
|
|
22
|
+
|
|
23
|
+
## Usage with A2UI
|
|
24
|
+
Create the renderer, stream A2UI messages into `handleMessage`, and forward user actions back to your server.
|
|
25
|
+
|
|
26
|
+
Example flow:
|
|
27
|
+
1. `surfaceUpdate` provides the component graph.
|
|
28
|
+
2. `dataModelUpdate` supplies initial data.
|
|
29
|
+
3. `beginRendering` allows the client to render.
|
|
30
|
+
|
|
31
|
+
Use the `onUserAction` callback to capture input and selection events.
|
|
32
|
+
|
|
33
|
+
Recommended entry points:
|
|
34
|
+
- Public API: [src/index.ts](src/index.ts)
|
|
35
|
+
- Renderer core: [src/renderer.ts](src/renderer.ts)
|
|
36
|
+
- Component registry: [src/catalog.ts](src/catalog.ts)
|
|
37
|
+
- Binding and tree build: [src/binding.ts](src/binding.ts) and [src/tree.ts](src/tree.ts)
|
|
38
|
+
|
|
39
|
+
## Future Plan
|
|
40
|
+
- Add more standard catalog components (checkbox, radio, list view, tabs)
|
|
41
|
+
- Styling parity improvements (alignment, borders, padding presets)
|
|
42
|
+
- Better input controls (multi-line input, validation, masking)
|
|
43
|
+
- Multi-surface support in a single process
|
|
44
|
+
- Performance tuning for large dynamic lists
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { BindingContext, BoundValue } from "./types.js";
|
|
2
|
+
export declare function resolveBoundValue(boundValue: BoundValue | undefined, dataModel: Record<string, unknown>, context?: BindingContext): unknown;
|
|
3
|
+
export declare function resolveProps(props: Record<string, unknown>, dataModel: Record<string, unknown>, context?: BindingContext): Record<string, unknown>;
|
package/dist/binding.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const literalKeys = [
|
|
2
|
+
"literalString",
|
|
3
|
+
"literalNumber",
|
|
4
|
+
"literalBoolean",
|
|
5
|
+
"literalObject",
|
|
6
|
+
"literalArray"
|
|
7
|
+
];
|
|
8
|
+
export function resolveBoundValue(boundValue, dataModel, context) {
|
|
9
|
+
if (!boundValue) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
if (boundValue.path) {
|
|
13
|
+
const resolved = getPathValue(boundValue.path, dataModel, context);
|
|
14
|
+
if (resolved !== undefined) {
|
|
15
|
+
return resolved;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
for (const key of literalKeys) {
|
|
19
|
+
if (key in boundValue) {
|
|
20
|
+
return boundValue[key];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
export function resolveProps(props, dataModel, context) {
|
|
26
|
+
const resolved = {};
|
|
27
|
+
for (const [key, value] of Object.entries(props)) {
|
|
28
|
+
if (isBoundValue(value)) {
|
|
29
|
+
resolved[key] = resolveBoundValue(value, dataModel, context);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
resolved[key] = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return resolved;
|
|
36
|
+
}
|
|
37
|
+
function isBoundValue(value) {
|
|
38
|
+
if (!value || typeof value !== "object") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if ("path" in value) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return literalKeys.some((key) => key in value);
|
|
45
|
+
}
|
|
46
|
+
function getPathValue(path, dataModel, context) {
|
|
47
|
+
const normalized = path.replace(/^\$\.?/, "");
|
|
48
|
+
if (normalized === "") {
|
|
49
|
+
return dataModel;
|
|
50
|
+
}
|
|
51
|
+
const parts = normalized.split(".");
|
|
52
|
+
const root = resolveContextRoot(parts[0], context, dataModel);
|
|
53
|
+
const startIndex = root === dataModel ? 0 : 1;
|
|
54
|
+
let current = root;
|
|
55
|
+
for (let i = startIndex; i < parts.length; i += 1) {
|
|
56
|
+
if (current && typeof current === "object" && parts[i] in current) {
|
|
57
|
+
current = current[parts[i]];
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return current;
|
|
64
|
+
}
|
|
65
|
+
function resolveContextRoot(firstPart, context, dataModel) {
|
|
66
|
+
if (firstPart === "item" && (context === null || context === void 0 ? void 0 : context.item) !== undefined) {
|
|
67
|
+
return context.item;
|
|
68
|
+
}
|
|
69
|
+
if (firstPart === "index" && (context === null || context === void 0 ? void 0 : context.index) !== undefined) {
|
|
70
|
+
return context.index;
|
|
71
|
+
}
|
|
72
|
+
return dataModel;
|
|
73
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ActionDef, ResolvedNode } from "./types.js";
|
|
3
|
+
export interface CatalogRenderOptions {
|
|
4
|
+
dispatchAction: (action: ActionDef, node: ResolvedNode, value?: unknown) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function renderNode(node: ResolvedNode, options: CatalogRenderOptions): React.ReactElement;
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { Box, Spacer, Text } from "ink";
|
|
3
|
+
import { useFocusRegistry } from "./focus.js";
|
|
4
|
+
export function renderNode(node, options) {
|
|
5
|
+
const { type } = node;
|
|
6
|
+
switch (type) {
|
|
7
|
+
case "Text":
|
|
8
|
+
return React.createElement(A2uiText, { key: node.instanceKey, node });
|
|
9
|
+
case "Box":
|
|
10
|
+
return React.createElement(A2uiBox, { key: node.instanceKey, node, options });
|
|
11
|
+
case "Spacer":
|
|
12
|
+
return React.createElement(Spacer, { key: node.instanceKey });
|
|
13
|
+
case "Input":
|
|
14
|
+
return React.createElement(A2uiInput, { key: node.instanceKey, node, options });
|
|
15
|
+
case "Button":
|
|
16
|
+
return React.createElement(A2uiButton, { key: node.instanceKey, node, options });
|
|
17
|
+
case "Select":
|
|
18
|
+
return React.createElement(A2uiSelect, { key: node.instanceKey, node, options });
|
|
19
|
+
default:
|
|
20
|
+
return React.createElement(Text, { key: node.instanceKey }, `Unsupported: ${type}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const A2uiText = ({ node }) => {
|
|
24
|
+
var _a, _b;
|
|
25
|
+
const props = node.props;
|
|
26
|
+
const text = ((_b = (_a = props.text) !== null && _a !== void 0 ? _a : props.value) !== null && _b !== void 0 ? _b : "");
|
|
27
|
+
return React.createElement(Text, {
|
|
28
|
+
color: props.color,
|
|
29
|
+
backgroundColor: props.backgroundColor,
|
|
30
|
+
bold: props.bold,
|
|
31
|
+
dimColor: props.dim,
|
|
32
|
+
italic: props.italic,
|
|
33
|
+
underline: props.underline
|
|
34
|
+
}, text);
|
|
35
|
+
};
|
|
36
|
+
const A2uiBox = ({ node, options }) => {
|
|
37
|
+
var _a;
|
|
38
|
+
const props = node.props;
|
|
39
|
+
return React.createElement(Box, {
|
|
40
|
+
flexDirection: (_a = props.direction) !== null && _a !== void 0 ? _a : "column",
|
|
41
|
+
padding: props.padding,
|
|
42
|
+
paddingX: props.paddingX,
|
|
43
|
+
paddingY: props.paddingY,
|
|
44
|
+
margin: props.margin,
|
|
45
|
+
marginX: props.marginX,
|
|
46
|
+
marginY: props.marginY,
|
|
47
|
+
borderStyle: props.borderStyle,
|
|
48
|
+
borderColor: props.borderColor,
|
|
49
|
+
width: props.width,
|
|
50
|
+
height: props.height,
|
|
51
|
+
flexGrow: props.flexGrow,
|
|
52
|
+
flexShrink: props.flexShrink,
|
|
53
|
+
justifyContent: props.justifyContent,
|
|
54
|
+
alignItems: props.alignItems
|
|
55
|
+
}, node.children.map((child) => renderNode(child, options)));
|
|
56
|
+
};
|
|
57
|
+
const A2uiButton = ({ node, options }) => {
|
|
58
|
+
var _a, _b;
|
|
59
|
+
const focus = useFocusRegistry();
|
|
60
|
+
const label = ((_b = (_a = node.props.label) !== null && _a !== void 0 ? _a : node.props.text) !== null && _b !== void 0 ? _b : "Button");
|
|
61
|
+
const action = node.props.onPress;
|
|
62
|
+
const isFocused = focus.isFocused(node.instanceKey);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const handler = (input, key) => {
|
|
65
|
+
if (!action) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (key.return || input === " ") {
|
|
69
|
+
options.dispatchAction(action, node);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
focus.register(node.instanceKey, handler);
|
|
73
|
+
return () => focus.unregister(node.instanceKey);
|
|
74
|
+
}, [action, focus, node, options]);
|
|
75
|
+
return React.createElement(Box, { borderStyle: "round", borderColor: isFocused ? "cyan" : undefined, paddingX: 1 }, React.createElement(Text, { inverse: isFocused }, label));
|
|
76
|
+
};
|
|
77
|
+
const A2uiInput = ({ node, options }) => {
|
|
78
|
+
var _a;
|
|
79
|
+
const focus = useFocusRegistry();
|
|
80
|
+
const action = node.props.onChange;
|
|
81
|
+
const submitAction = node.props.onSubmit;
|
|
82
|
+
const initial = ((_a = node.props.value) !== null && _a !== void 0 ? _a : "");
|
|
83
|
+
const [value, setValue] = useState(initial);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
setValue(initial);
|
|
86
|
+
}, [initial]);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const handler = (input, key) => {
|
|
89
|
+
if (key.return) {
|
|
90
|
+
if (submitAction) {
|
|
91
|
+
options.dispatchAction(submitAction, node, value);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (key.backspace || key.delete) {
|
|
96
|
+
setValue((current) => current.slice(0, -1));
|
|
97
|
+
if (action) {
|
|
98
|
+
options.dispatchAction(action, node, value.slice(0, -1));
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (input) {
|
|
103
|
+
const next = `${value}${input}`;
|
|
104
|
+
setValue(next);
|
|
105
|
+
if (action) {
|
|
106
|
+
options.dispatchAction(action, node, next);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
focus.register(node.instanceKey, handler);
|
|
111
|
+
return () => focus.unregister(node.instanceKey);
|
|
112
|
+
}, [action, focus, node, options, submitAction, value]);
|
|
113
|
+
const isFocused = focus.isFocused(node.instanceKey);
|
|
114
|
+
return React.createElement(Box, null, React.createElement(Text, { color: isFocused ? "cyan" : undefined }, value || " "));
|
|
115
|
+
};
|
|
116
|
+
const A2uiSelect = ({ node, options }) => {
|
|
117
|
+
var _a, _b;
|
|
118
|
+
const focus = useFocusRegistry();
|
|
119
|
+
const items = ((_a = node.props.items) !== null && _a !== void 0 ? _a : []);
|
|
120
|
+
const action = node.props.onSelect;
|
|
121
|
+
const initialIndex = ((_b = node.props.selectedIndex) !== null && _b !== void 0 ? _b : 0);
|
|
122
|
+
const [index, setIndex] = useState(initialIndex);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
setIndex(initialIndex);
|
|
125
|
+
}, [initialIndex]);
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
const handler = (input, key) => {
|
|
128
|
+
if (key.upArrow) {
|
|
129
|
+
setIndex((current) => (current <= 0 ? items.length - 1 : current - 1));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (key.downArrow) {
|
|
133
|
+
setIndex((current) => (current + 1) % items.length);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (key.return && action) {
|
|
137
|
+
const value = items[index];
|
|
138
|
+
options.dispatchAction(action, node, value);
|
|
139
|
+
}
|
|
140
|
+
if (input === " " && action) {
|
|
141
|
+
const value = items[index];
|
|
142
|
+
options.dispatchAction(action, node, value);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
focus.register(node.instanceKey, handler);
|
|
146
|
+
return () => focus.unregister(node.instanceKey);
|
|
147
|
+
}, [action, focus, index, items, node, options]);
|
|
148
|
+
const isFocused = focus.isFocused(node.instanceKey);
|
|
149
|
+
const labels = useMemo(() => items.map(getItemLabel), [items]);
|
|
150
|
+
return React.createElement(Box, { flexDirection: "column" }, labels.map((label, itemIndex) => React.createElement(Text, { key: `${node.instanceKey}_${itemIndex}`, inverse: isFocused && itemIndex === index }, label)));
|
|
151
|
+
};
|
|
152
|
+
function getItemLabel(item) {
|
|
153
|
+
var _a, _b;
|
|
154
|
+
if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") {
|
|
155
|
+
return String(item);
|
|
156
|
+
}
|
|
157
|
+
if (item && typeof item === "object") {
|
|
158
|
+
const record = item;
|
|
159
|
+
const label = (_b = (_a = record.label) !== null && _a !== void 0 ? _a : record.text) !== null && _b !== void 0 ? _b : record.name;
|
|
160
|
+
if (label) {
|
|
161
|
+
return String(label);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return JSON.stringify(item);
|
|
165
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|