@flyingrobots/bijou-tui 0.5.0 → 0.6.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/README.md +49 -7
- package/dist/browsable-list.d.ts +87 -0
- package/dist/browsable-list.d.ts.map +1 -0
- package/dist/browsable-list.js +143 -0
- package/dist/browsable-list.js.map +1 -0
- package/dist/file-picker.d.ts +91 -0
- package/dist/file-picker.d.ts.map +1 -0
- package/dist/file-picker.js +210 -0
- package/dist/file-picker.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/navigable-table.d.ts +79 -0
- package/dist/navigable-table.d.ts.map +1 -0
- package/dist/navigable-table.js +145 -0
- package/dist/navigable-table.js.map +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -4,14 +4,11 @@ TEA runtime for terminal UIs — model/update/view with physics-based animation,
|
|
|
4
4
|
|
|
5
5
|
Inspired by [Bubble Tea](https://github.com/charmbracelet/bubbletea) (Go) and [GSAP](https://gsap.com/) animation.
|
|
6
6
|
|
|
7
|
-
## What's New in 0.
|
|
7
|
+
## What's New in 0.6.0?
|
|
8
8
|
|
|
9
|
-
- **`
|
|
10
|
-
- **`
|
|
11
|
-
- **`
|
|
12
|
-
- **`pager()`** — scrollable content viewer with status line and convenience keymap
|
|
13
|
-
- **`interactiveAccordion()`** — navigable accordion with focus tracking and expand/collapse
|
|
14
|
-
- **`createPanelGroup()`** — multi-pane focus management with hotkey switching
|
|
9
|
+
- **`navigableTable()`** — keyboard-navigable table with focus management, vertical scrolling, and vim-style keybindings
|
|
10
|
+
- **`browsableList()`** — navigable list with focus tracking, scroll viewport, descriptions, and convenience keymap
|
|
11
|
+
- **`filePicker()`** — directory browser with focus navigation, extension filtering, and `IOPort` integration
|
|
15
12
|
|
|
16
13
|
See the [CHANGELOG](https://github.com/flyingrobots/bijou/blob/main/docs/CHANGELOG.md) for the full release history.
|
|
17
14
|
|
|
@@ -286,6 +283,51 @@ const output = composite(backgroundView, [dialog, notification], { dim: true });
|
|
|
286
283
|
|
|
287
284
|
Each overlay is a `{ content, row, col }` object. `composite()` splices them onto the background using painter's algorithm (last overlay wins on overlap). The `dim` option fades the background with ANSI dim.
|
|
288
285
|
|
|
286
|
+
## Building Blocks
|
|
287
|
+
|
|
288
|
+
Reusable stateful components that follow the TEA state + pure transformers + sync render + convenience keymap pattern:
|
|
289
|
+
|
|
290
|
+
### Navigable Table
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
import {
|
|
294
|
+
createNavigableTableState, navigableTable, navTableFocusNext,
|
|
295
|
+
navTableKeyMap, helpShort,
|
|
296
|
+
} from '@flyingrobots/bijou-tui';
|
|
297
|
+
|
|
298
|
+
const state = createNavigableTableState({ columns, rows, height: 10 });
|
|
299
|
+
const output = navigableTable(state, { ctx });
|
|
300
|
+
const next = navTableFocusNext(state);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Browsable List
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import {
|
|
307
|
+
createBrowsableListState, browsableList, listFocusNext,
|
|
308
|
+
browsableListKeyMap,
|
|
309
|
+
} from '@flyingrobots/bijou-tui';
|
|
310
|
+
|
|
311
|
+
const state = createBrowsableListState({ items, height: 10 });
|
|
312
|
+
const output = browsableList(state);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### File Picker
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import {
|
|
319
|
+
createFilePickerState, filePicker, fpFocusNext, fpEnter, fpBack,
|
|
320
|
+
filePickerKeyMap,
|
|
321
|
+
} from '@flyingrobots/bijou-tui';
|
|
322
|
+
import { nodeIO } from '@flyingrobots/bijou-node';
|
|
323
|
+
|
|
324
|
+
const io = nodeIO();
|
|
325
|
+
const state = createFilePickerState({ cwd: process.cwd(), io, height: 15 });
|
|
326
|
+
const output = filePicker(state);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
All building blocks include `*KeyMap()` factories for preconfigured vim-style keybindings.
|
|
330
|
+
|
|
289
331
|
## Related Packages
|
|
290
332
|
|
|
291
333
|
- [`@flyingrobots/bijou`](https://www.npmjs.com/package/@flyingrobots/bijou) — Zero-dependency core with all components and theme engine
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browsable list building block — a scrollable, navigable list with focus tracking.
|
|
3
|
+
*
|
|
4
|
+
* Provides state transformers for focus and page navigation, a pure render
|
|
5
|
+
* function with viewport clipping, and a convenience keymap for vim-style
|
|
6
|
+
* navigation.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // In TEA init:
|
|
10
|
+
* const listState = createBrowsableListState({ items, height: 10 });
|
|
11
|
+
*
|
|
12
|
+
* // In TEA view:
|
|
13
|
+
* const output = browsableList(model.listState);
|
|
14
|
+
*
|
|
15
|
+
* // In TEA update:
|
|
16
|
+
* case 'focus-next':
|
|
17
|
+
* return [{ ...model, listState: listFocusNext(model.listState) }, []];
|
|
18
|
+
* case 'select':
|
|
19
|
+
* const selected = model.listState.items[model.listState.focusIndex];
|
|
20
|
+
* // handle selection...
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import type { BijouContext } from '@flyingrobots/bijou';
|
|
24
|
+
import { type KeyMap } from './keybindings.js';
|
|
25
|
+
export interface BrowsableListItem<T = string> {
|
|
26
|
+
label: string;
|
|
27
|
+
value: T;
|
|
28
|
+
description?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface BrowsableListState<T = string> {
|
|
31
|
+
readonly items: readonly BrowsableListItem<T>[];
|
|
32
|
+
readonly focusIndex: number;
|
|
33
|
+
readonly scrollY: number;
|
|
34
|
+
readonly height: number;
|
|
35
|
+
}
|
|
36
|
+
export interface BrowsableListOptions<T = string> {
|
|
37
|
+
readonly items: readonly BrowsableListItem<T>[];
|
|
38
|
+
readonly height?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface BrowsableListRenderOptions {
|
|
41
|
+
readonly focusIndicator?: string;
|
|
42
|
+
readonly ctx?: BijouContext;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create initial browsable list state from items and optional height.
|
|
46
|
+
* Focus starts at index 0 with scroll at the top.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createBrowsableListState<T = string>(options: BrowsableListOptions<T>): BrowsableListState<T>;
|
|
49
|
+
/** Move focus to the next item (wraps around). */
|
|
50
|
+
export declare function listFocusNext<T>(state: BrowsableListState<T>): BrowsableListState<T>;
|
|
51
|
+
/** Move focus to the previous item (wraps around). */
|
|
52
|
+
export declare function listFocusPrev<T>(state: BrowsableListState<T>): BrowsableListState<T>;
|
|
53
|
+
/** Move focus down by one page (clamps to last item). */
|
|
54
|
+
export declare function listPageDown<T>(state: BrowsableListState<T>): BrowsableListState<T>;
|
|
55
|
+
/** Move focus up by one page (clamps to first item). */
|
|
56
|
+
export declare function listPageUp<T>(state: BrowsableListState<T>): BrowsableListState<T>;
|
|
57
|
+
/**
|
|
58
|
+
* Render the browsable list — visible items within the viewport with a
|
|
59
|
+
* focus indicator on the currently focused item.
|
|
60
|
+
*
|
|
61
|
+
* Items with a `description` field render as `label — description`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function browsableList<T>(state: BrowsableListState<T>, options?: BrowsableListRenderOptions): string;
|
|
64
|
+
/**
|
|
65
|
+
* Create a preconfigured KeyMap for browsable list navigation.
|
|
66
|
+
*
|
|
67
|
+
* The caller provides their own message types for each action:
|
|
68
|
+
* ```ts
|
|
69
|
+
* const keys = browsableListKeyMap({
|
|
70
|
+
* focusNext: { type: 'next' },
|
|
71
|
+
* focusPrev: { type: 'prev' },
|
|
72
|
+
* pageDown: { type: 'page-down' },
|
|
73
|
+
* pageUp: { type: 'page-up' },
|
|
74
|
+
* select: { type: 'select' },
|
|
75
|
+
* quit: { type: 'quit' },
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function browsableListKeyMap<Msg>(actions: {
|
|
80
|
+
focusNext: Msg;
|
|
81
|
+
focusPrev: Msg;
|
|
82
|
+
pageDown: Msg;
|
|
83
|
+
pageUp: Msg;
|
|
84
|
+
select: Msg;
|
|
85
|
+
quit: Msg;
|
|
86
|
+
}): KeyMap<Msg>;
|
|
87
|
+
//# sourceMappingURL=browsable-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browsable-list.d.ts","sourceRoot":"","sources":["../src/browsable-list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM7D,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,MAAM;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;IACT,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,MAAM;IAC5C,QAAQ,CAAC,KAAK,EAAE,SAAS,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;IAChD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB,CAAC,CAAC,GAAG,MAAM;IAC9C,QAAQ,CAAC,KAAK,EAAE,SAAS,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,YAAY,CAAC;CAC7B;AAMD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,GAAG,MAAM,EACjD,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAC/B,kBAAkB,CAAC,CAAC,CAAC,CAQvB;AAMD,kDAAkD;AAClD,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAIpF;AAED,sDAAsD;AACtD,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAIpF;AAED,yDAAyD;AACzD,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAInF;AAED,wDAAwD;AACxD,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAIjF;AAqBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAC5B,OAAO,CAAC,EAAE,0BAA0B,GACnC,MAAM,CAkBR;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE;IAChD,SAAS,EAAE,GAAG,CAAC;IACf,SAAS,EAAE,GAAG,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC;IACd,MAAM,EAAE,GAAG,CAAC;IACZ,MAAM,EAAE,GAAG,CAAC;IACZ,IAAI,EAAE,GAAG,CAAC;CACX,GAAG,MAAM,CAAC,GAAG,CAAC,CAed"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browsable list building block — a scrollable, navigable list with focus tracking.
|
|
3
|
+
*
|
|
4
|
+
* Provides state transformers for focus and page navigation, a pure render
|
|
5
|
+
* function with viewport clipping, and a convenience keymap for vim-style
|
|
6
|
+
* navigation.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // In TEA init:
|
|
10
|
+
* const listState = createBrowsableListState({ items, height: 10 });
|
|
11
|
+
*
|
|
12
|
+
* // In TEA view:
|
|
13
|
+
* const output = browsableList(model.listState);
|
|
14
|
+
*
|
|
15
|
+
* // In TEA update:
|
|
16
|
+
* case 'focus-next':
|
|
17
|
+
* return [{ ...model, listState: listFocusNext(model.listState) }, []];
|
|
18
|
+
* case 'select':
|
|
19
|
+
* const selected = model.listState.items[model.listState.focusIndex];
|
|
20
|
+
* // handle selection...
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { createKeyMap } from './keybindings.js';
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// State creation
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/**
|
|
28
|
+
* Create initial browsable list state from items and optional height.
|
|
29
|
+
* Focus starts at index 0 with scroll at the top.
|
|
30
|
+
*/
|
|
31
|
+
export function createBrowsableListState(options) {
|
|
32
|
+
const height = Math.max(1, options.height ?? 10);
|
|
33
|
+
return {
|
|
34
|
+
items: [...options.items],
|
|
35
|
+
focusIndex: 0,
|
|
36
|
+
scrollY: 0,
|
|
37
|
+
height,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// State transformers
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
/** Move focus to the next item (wraps around). */
|
|
44
|
+
export function listFocusNext(state) {
|
|
45
|
+
if (state.items.length === 0)
|
|
46
|
+
return state;
|
|
47
|
+
const focusIndex = (state.focusIndex + 1) % state.items.length;
|
|
48
|
+
return { ...state, focusIndex, scrollY: adjustScroll(focusIndex, state.scrollY, state.height, state.items.length) };
|
|
49
|
+
}
|
|
50
|
+
/** Move focus to the previous item (wraps around). */
|
|
51
|
+
export function listFocusPrev(state) {
|
|
52
|
+
if (state.items.length === 0)
|
|
53
|
+
return state;
|
|
54
|
+
const focusIndex = (state.focusIndex - 1 + state.items.length) % state.items.length;
|
|
55
|
+
return { ...state, focusIndex, scrollY: adjustScroll(focusIndex, state.scrollY, state.height, state.items.length) };
|
|
56
|
+
}
|
|
57
|
+
/** Move focus down by one page (clamps to last item). */
|
|
58
|
+
export function listPageDown(state) {
|
|
59
|
+
if (state.items.length === 0)
|
|
60
|
+
return state;
|
|
61
|
+
const focusIndex = Math.min(state.focusIndex + state.height, state.items.length - 1);
|
|
62
|
+
return { ...state, focusIndex, scrollY: adjustScroll(focusIndex, state.scrollY, state.height, state.items.length) };
|
|
63
|
+
}
|
|
64
|
+
/** Move focus up by one page (clamps to first item). */
|
|
65
|
+
export function listPageUp(state) {
|
|
66
|
+
if (state.items.length === 0)
|
|
67
|
+
return state;
|
|
68
|
+
const focusIndex = Math.max(state.focusIndex - state.height, 0);
|
|
69
|
+
return { ...state, focusIndex, scrollY: adjustScroll(focusIndex, state.scrollY, state.height, state.items.length) };
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Scroll helper
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
function adjustScroll(focusIndex, scrollY, height, totalItems) {
|
|
75
|
+
let newScrollY = scrollY;
|
|
76
|
+
if (focusIndex < newScrollY) {
|
|
77
|
+
newScrollY = focusIndex;
|
|
78
|
+
}
|
|
79
|
+
else if (focusIndex >= newScrollY + height) {
|
|
80
|
+
newScrollY = focusIndex - height + 1;
|
|
81
|
+
}
|
|
82
|
+
const maxScroll = Math.max(0, totalItems - height);
|
|
83
|
+
return Math.min(newScrollY, maxScroll);
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Render
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
/**
|
|
89
|
+
* Render the browsable list — visible items within the viewport with a
|
|
90
|
+
* focus indicator on the currently focused item.
|
|
91
|
+
*
|
|
92
|
+
* Items with a `description` field render as `label — description`.
|
|
93
|
+
*/
|
|
94
|
+
export function browsableList(state, options) {
|
|
95
|
+
if (state.items.length === 0)
|
|
96
|
+
return '';
|
|
97
|
+
const indicator = options?.focusIndicator ?? '\u25b8';
|
|
98
|
+
const pad = ' '.repeat(indicator.length);
|
|
99
|
+
const visibleItems = state.items.slice(state.scrollY, state.scrollY + state.height);
|
|
100
|
+
const lines = [];
|
|
101
|
+
for (let i = 0; i < visibleItems.length; i++) {
|
|
102
|
+
const item = visibleItems[i];
|
|
103
|
+
const globalIndex = state.scrollY + i;
|
|
104
|
+
const prefix = globalIndex === state.focusIndex ? indicator : pad;
|
|
105
|
+
const desc = item.description ? ` \u2014 ${item.description}` : '';
|
|
106
|
+
lines.push(`${prefix} ${item.label}${desc}`);
|
|
107
|
+
}
|
|
108
|
+
return lines.join('\n');
|
|
109
|
+
}
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Convenience keymap
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
/**
|
|
114
|
+
* Create a preconfigured KeyMap for browsable list navigation.
|
|
115
|
+
*
|
|
116
|
+
* The caller provides their own message types for each action:
|
|
117
|
+
* ```ts
|
|
118
|
+
* const keys = browsableListKeyMap({
|
|
119
|
+
* focusNext: { type: 'next' },
|
|
120
|
+
* focusPrev: { type: 'prev' },
|
|
121
|
+
* pageDown: { type: 'page-down' },
|
|
122
|
+
* pageUp: { type: 'page-up' },
|
|
123
|
+
* select: { type: 'select' },
|
|
124
|
+
* quit: { type: 'quit' },
|
|
125
|
+
* });
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export function browsableListKeyMap(actions) {
|
|
129
|
+
return createKeyMap()
|
|
130
|
+
.group('Navigation', (g) => g
|
|
131
|
+
.bind('j', 'Next item', actions.focusNext)
|
|
132
|
+
.bind('down', 'Next item', actions.focusNext)
|
|
133
|
+
.bind('k', 'Previous item', actions.focusPrev)
|
|
134
|
+
.bind('up', 'Previous item', actions.focusPrev)
|
|
135
|
+
.bind('d', 'Page down', actions.pageDown)
|
|
136
|
+
.bind('pagedown', 'Page down', actions.pageDown)
|
|
137
|
+
.bind('u', 'Page up', actions.pageUp)
|
|
138
|
+
.bind('pageup', 'Page up', actions.pageUp))
|
|
139
|
+
.bind('enter', 'Select', actions.select)
|
|
140
|
+
.bind('q', 'Quit', actions.quit)
|
|
141
|
+
.bind('ctrl+c', 'Quit', actions.quit);
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=browsable-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browsable-list.js","sourceRoot":"","sources":["../src/browsable-list.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File picker building block — a directory browser with focus navigation.
|
|
3
|
+
*
|
|
4
|
+
* Uses `IOPort.readDir()` to list directory contents and `IOPort.joinPath()`
|
|
5
|
+
* to navigate between directories. Follows the same state + pure transformers
|
|
6
|
+
* + sync render + convenience keymap pattern as pager and accordion.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // In TEA init:
|
|
10
|
+
* const fpState = createFilePickerState({ cwd: '/project', io });
|
|
11
|
+
*
|
|
12
|
+
* // In TEA view:
|
|
13
|
+
* const output = filePicker(model.fpState);
|
|
14
|
+
*
|
|
15
|
+
* // In TEA update:
|
|
16
|
+
* case 'focus-next':
|
|
17
|
+
* return [{ ...model, fpState: fpFocusNext(model.fpState) }, []];
|
|
18
|
+
* case 'enter':
|
|
19
|
+
* return [{ ...model, fpState: fpEnter(model.fpState, io) }, []];
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import type { IOPort } from '@flyingrobots/bijou';
|
|
23
|
+
import { type KeyMap } from './keybindings.js';
|
|
24
|
+
export interface FileEntry {
|
|
25
|
+
name: string;
|
|
26
|
+
isDirectory: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface FilePickerState {
|
|
29
|
+
readonly cwd: string;
|
|
30
|
+
readonly entries: readonly FileEntry[];
|
|
31
|
+
readonly focusIndex: number;
|
|
32
|
+
readonly scrollY: number;
|
|
33
|
+
readonly height: number;
|
|
34
|
+
readonly filter?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface FilePickerOptions {
|
|
37
|
+
readonly cwd: string;
|
|
38
|
+
readonly io: IOPort;
|
|
39
|
+
readonly height?: number;
|
|
40
|
+
readonly filter?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface FilePickerRenderOptions {
|
|
43
|
+
readonly focusIndicator?: string;
|
|
44
|
+
readonly dirIndicator?: string;
|
|
45
|
+
readonly fileIndicator?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create initial file picker state for the given directory.
|
|
49
|
+
*
|
|
50
|
+
* Reads directory contents via `io.readDir()` and parses entries into
|
|
51
|
+
* directories (trailing `/`) and files. Directories are sorted first,
|
|
52
|
+
* then files, both alphabetically.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createFilePickerState(options: FilePickerOptions): FilePickerState;
|
|
55
|
+
/** Move focus to the next entry (wraps around). */
|
|
56
|
+
export declare function fpFocusNext(state: FilePickerState): FilePickerState;
|
|
57
|
+
/** Move focus to the previous entry (wraps around). */
|
|
58
|
+
export declare function fpFocusPrev(state: FilePickerState): FilePickerState;
|
|
59
|
+
/** Enter the focused directory, refreshing the entry list. No-op on files. */
|
|
60
|
+
export declare function fpEnter(state: FilePickerState, io: IOPort): FilePickerState;
|
|
61
|
+
/** Navigate to the parent directory. No-op at filesystem root. */
|
|
62
|
+
export declare function fpBack(state: FilePickerState, io: IOPort): FilePickerState;
|
|
63
|
+
/**
|
|
64
|
+
* Render the file picker — cwd header followed by the visible entry list.
|
|
65
|
+
*
|
|
66
|
+
* Each entry is prefixed with a focus indicator and a type indicator
|
|
67
|
+
* (directory or file). Directories are suffixed with `/`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function filePicker(state: FilePickerState, options?: FilePickerRenderOptions): string;
|
|
70
|
+
/**
|
|
71
|
+
* Create a preconfigured KeyMap for file picker navigation.
|
|
72
|
+
*
|
|
73
|
+
* The caller provides their own message types for each action:
|
|
74
|
+
* ```ts
|
|
75
|
+
* const keys = filePickerKeyMap({
|
|
76
|
+
* focusNext: { type: 'next' },
|
|
77
|
+
* focusPrev: { type: 'prev' },
|
|
78
|
+
* enter: { type: 'enter' },
|
|
79
|
+
* back: { type: 'back' },
|
|
80
|
+
* quit: { type: 'quit' },
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function filePickerKeyMap<Msg>(actions: {
|
|
85
|
+
focusNext: Msg;
|
|
86
|
+
focusPrev: Msg;
|
|
87
|
+
enter: Msg;
|
|
88
|
+
back: Msg;
|
|
89
|
+
quit: Msg;
|
|
90
|
+
}): KeyMap<Msg>;
|
|
91
|
+
//# sourceMappingURL=file-picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-picker.d.ts","sourceRoot":"","sources":["../src/file-picker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM7D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAkDD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,eAAe,CAYjF;AAMD,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,eAAe,CAInE;AAED,uDAAuD;AACvD,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,eAAe,CAInE;AAED,8EAA8E;AAC9E,wBAAgB,OAAO,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,eAAe,CAe3E;AAED,kEAAkE;AAClE,wBAAgB,MAAM,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,eAAe,CAa1E;AAqBD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,MAAM,CA0B5F;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE;IAC7C,SAAS,EAAE,GAAG,CAAC;IACf,SAAS,EAAE,GAAG,CAAC;IACf,KAAK,EAAE,GAAG,CAAC;IACX,IAAI,EAAE,GAAG,CAAC;IACV,IAAI,EAAE,GAAG,CAAC;CACX,GAAG,MAAM,CAAC,GAAG,CAAC,CAed"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File picker building block — a directory browser with focus navigation.
|
|
3
|
+
*
|
|
4
|
+
* Uses `IOPort.readDir()` to list directory contents and `IOPort.joinPath()`
|
|
5
|
+
* to navigate between directories. Follows the same state + pure transformers
|
|
6
|
+
* + sync render + convenience keymap pattern as pager and accordion.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // In TEA init:
|
|
10
|
+
* const fpState = createFilePickerState({ cwd: '/project', io });
|
|
11
|
+
*
|
|
12
|
+
* // In TEA view:
|
|
13
|
+
* const output = filePicker(model.fpState);
|
|
14
|
+
*
|
|
15
|
+
* // In TEA update:
|
|
16
|
+
* case 'focus-next':
|
|
17
|
+
* return [{ ...model, fpState: fpFocusNext(model.fpState) }, []];
|
|
18
|
+
* case 'enter':
|
|
19
|
+
* return [{ ...model, fpState: fpEnter(model.fpState, io) }, []];
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import { createKeyMap } from './keybindings.js';
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* Safely read and parse directory entries. Returns `[]` if the directory
|
|
28
|
+
* is unreadable (permissions, missing, etc.) instead of throwing.
|
|
29
|
+
*/
|
|
30
|
+
function safeReadEntries(io, cwd, filter) {
|
|
31
|
+
try {
|
|
32
|
+
return parseEntries(io.readDir(cwd), filter);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parse raw directory listing into sorted `FileEntry[]`.
|
|
40
|
+
*
|
|
41
|
+
* @param names - Names returned by `IOPort.readDir()` (dirs have trailing `/`).
|
|
42
|
+
* @param filter - Optional extension suffix (e.g. `".ts"`) — only files whose
|
|
43
|
+
* name ends with this suffix are included. Directories are
|
|
44
|
+
* always included regardless of the filter.
|
|
45
|
+
*/
|
|
46
|
+
function parseEntries(names, filter) {
|
|
47
|
+
const dirs = [];
|
|
48
|
+
const files = [];
|
|
49
|
+
for (const name of names) {
|
|
50
|
+
if (name.endsWith('/')) {
|
|
51
|
+
dirs.push({ name: name.slice(0, -1), isDirectory: true });
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
if (filter && !name.endsWith(filter))
|
|
55
|
+
continue;
|
|
56
|
+
files.push({ name, isDirectory: false });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Sort dirs first, then files, both alphabetically
|
|
60
|
+
dirs.sort((a, b) => a.name.localeCompare(b.name));
|
|
61
|
+
files.sort((a, b) => a.name.localeCompare(b.name));
|
|
62
|
+
return [...dirs, ...files];
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// State creation
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
/**
|
|
68
|
+
* Create initial file picker state for the given directory.
|
|
69
|
+
*
|
|
70
|
+
* Reads directory contents via `io.readDir()` and parses entries into
|
|
71
|
+
* directories (trailing `/`) and files. Directories are sorted first,
|
|
72
|
+
* then files, both alphabetically.
|
|
73
|
+
*/
|
|
74
|
+
export function createFilePickerState(options) {
|
|
75
|
+
const height = Math.max(1, options.height ?? 10);
|
|
76
|
+
const entries = safeReadEntries(options.io, options.cwd, options.filter);
|
|
77
|
+
return {
|
|
78
|
+
cwd: options.cwd,
|
|
79
|
+
entries,
|
|
80
|
+
focusIndex: 0,
|
|
81
|
+
scrollY: 0,
|
|
82
|
+
height,
|
|
83
|
+
filter: options.filter,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// State transformers
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
/** Move focus to the next entry (wraps around). */
|
|
90
|
+
export function fpFocusNext(state) {
|
|
91
|
+
if (state.entries.length === 0)
|
|
92
|
+
return state;
|
|
93
|
+
const focusIndex = (state.focusIndex + 1) % state.entries.length;
|
|
94
|
+
return { ...state, focusIndex, scrollY: adjustScroll(focusIndex, state.scrollY, state.height, state.entries.length) };
|
|
95
|
+
}
|
|
96
|
+
/** Move focus to the previous entry (wraps around). */
|
|
97
|
+
export function fpFocusPrev(state) {
|
|
98
|
+
if (state.entries.length === 0)
|
|
99
|
+
return state;
|
|
100
|
+
const focusIndex = (state.focusIndex - 1 + state.entries.length) % state.entries.length;
|
|
101
|
+
return { ...state, focusIndex, scrollY: adjustScroll(focusIndex, state.scrollY, state.height, state.entries.length) };
|
|
102
|
+
}
|
|
103
|
+
/** Enter the focused directory, refreshing the entry list. No-op on files. */
|
|
104
|
+
export function fpEnter(state, io) {
|
|
105
|
+
if (state.entries.length === 0)
|
|
106
|
+
return state;
|
|
107
|
+
const entry = state.entries[state.focusIndex];
|
|
108
|
+
if (!entry || !entry.isDirectory)
|
|
109
|
+
return state;
|
|
110
|
+
const newCwd = io.joinPath(state.cwd, entry.name);
|
|
111
|
+
const entries = safeReadEntries(io, newCwd, state.filter);
|
|
112
|
+
return {
|
|
113
|
+
...state,
|
|
114
|
+
cwd: newCwd,
|
|
115
|
+
entries,
|
|
116
|
+
focusIndex: 0,
|
|
117
|
+
scrollY: 0,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Navigate to the parent directory. No-op at filesystem root. */
|
|
121
|
+
export function fpBack(state, io) {
|
|
122
|
+
const newCwd = io.joinPath(state.cwd, '..');
|
|
123
|
+
if (newCwd === state.cwd)
|
|
124
|
+
return state;
|
|
125
|
+
const entries = safeReadEntries(io, newCwd, state.filter);
|
|
126
|
+
return {
|
|
127
|
+
...state,
|
|
128
|
+
cwd: newCwd,
|
|
129
|
+
entries,
|
|
130
|
+
focusIndex: 0,
|
|
131
|
+
scrollY: 0,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Scroll helper
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
function adjustScroll(focusIndex, scrollY, height, totalItems) {
|
|
138
|
+
let newScrollY = scrollY;
|
|
139
|
+
if (focusIndex < newScrollY) {
|
|
140
|
+
newScrollY = focusIndex;
|
|
141
|
+
}
|
|
142
|
+
else if (focusIndex >= newScrollY + height) {
|
|
143
|
+
newScrollY = focusIndex - height + 1;
|
|
144
|
+
}
|
|
145
|
+
const maxScroll = Math.max(0, totalItems - height);
|
|
146
|
+
return Math.min(newScrollY, maxScroll);
|
|
147
|
+
}
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Render
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
/**
|
|
152
|
+
* Render the file picker — cwd header followed by the visible entry list.
|
|
153
|
+
*
|
|
154
|
+
* Each entry is prefixed with a focus indicator and a type indicator
|
|
155
|
+
* (directory or file). Directories are suffixed with `/`.
|
|
156
|
+
*/
|
|
157
|
+
export function filePicker(state, options) {
|
|
158
|
+
const indicator = options?.focusIndicator ?? '\u25b8';
|
|
159
|
+
const dirIcon = options?.dirIndicator ?? 'd';
|
|
160
|
+
const fileIcon = options?.fileIndicator ?? '-';
|
|
161
|
+
const pad = ' '.repeat(indicator.length);
|
|
162
|
+
const lines = [];
|
|
163
|
+
lines.push(state.cwd);
|
|
164
|
+
if (state.entries.length === 0) {
|
|
165
|
+
lines.push(' (empty)');
|
|
166
|
+
return lines.join('\n');
|
|
167
|
+
}
|
|
168
|
+
const visibleEntries = state.entries.slice(state.scrollY, state.scrollY + state.height);
|
|
169
|
+
for (let i = 0; i < visibleEntries.length; i++) {
|
|
170
|
+
const entry = visibleEntries[i];
|
|
171
|
+
const globalIndex = state.scrollY + i;
|
|
172
|
+
const prefix = globalIndex === state.focusIndex ? indicator : pad;
|
|
173
|
+
const icon = entry.isDirectory ? dirIcon : fileIcon;
|
|
174
|
+
const suffix = entry.isDirectory ? '/' : '';
|
|
175
|
+
lines.push(`${prefix} ${icon} ${entry.name}${suffix}`);
|
|
176
|
+
}
|
|
177
|
+
return lines.join('\n');
|
|
178
|
+
}
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Convenience keymap
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
/**
|
|
183
|
+
* Create a preconfigured KeyMap for file picker navigation.
|
|
184
|
+
*
|
|
185
|
+
* The caller provides their own message types for each action:
|
|
186
|
+
* ```ts
|
|
187
|
+
* const keys = filePickerKeyMap({
|
|
188
|
+
* focusNext: { type: 'next' },
|
|
189
|
+
* focusPrev: { type: 'prev' },
|
|
190
|
+
* enter: { type: 'enter' },
|
|
191
|
+
* back: { type: 'back' },
|
|
192
|
+
* quit: { type: 'quit' },
|
|
193
|
+
* });
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export function filePickerKeyMap(actions) {
|
|
197
|
+
return createKeyMap()
|
|
198
|
+
.group('Navigation', (g) => g
|
|
199
|
+
.bind('j', 'Next entry', actions.focusNext)
|
|
200
|
+
.bind('down', 'Next entry', actions.focusNext)
|
|
201
|
+
.bind('k', 'Previous entry', actions.focusPrev)
|
|
202
|
+
.bind('up', 'Previous entry', actions.focusPrev))
|
|
203
|
+
.group('Actions', (g) => g
|
|
204
|
+
.bind('enter', 'Enter directory / select file', actions.enter)
|
|
205
|
+
.bind('backspace', 'Parent directory', actions.back)
|
|
206
|
+
.bind('left', 'Parent directory', actions.back))
|
|
207
|
+
.bind('q', 'Quit', actions.quit)
|
|
208
|
+
.bind('ctrl+c', 'Quit', actions.quit);
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=file-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-picker.js","sourceRoot":"","sources":["../src/file-picker.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,4 +18,7 @@ export { type PagerState, type PagerOptions, type PagerRenderOptions, createPage
|
|
|
18
18
|
export { type PanelDef, type PanelGroupOptions, type PanelGroup, createPanelGroup, } from './panels.js';
|
|
19
19
|
export { type Overlay, type CompositeOptions, type ModalOptions, type ToastVariant, type ToastAnchor, type ToastOptions, composite, modal, toast, } from './overlay.js';
|
|
20
20
|
export { type AccordionState, type InteractiveAccordionOptions, createAccordionState, interactiveAccordion, focusNext, focusPrev, toggleFocused, expandAll, collapseAll, accordionKeyMap, } from './accordion.js';
|
|
21
|
+
export { type NavigableTableState, type NavigableTableOptions, type NavTableRenderOptions, createNavigableTableState, navigableTable, navTableFocusNext, navTableFocusPrev, navTablePageDown, navTablePageUp, navTableKeyMap, } from './navigable-table.js';
|
|
22
|
+
export { type BrowsableListItem, type BrowsableListState, type BrowsableListOptions, type BrowsableListRenderOptions, createBrowsableListState, browsableList, listFocusNext, listFocusPrev, listPageDown, listPageUp, browsableListKeyMap, } from './browsable-list.js';
|
|
23
|
+
export { type FileEntry, type FilePickerState, type FilePickerOptions, type FilePickerRenderOptions, createFilePickerState, filePicker, fpFocusNext, fpFocusPrev, fpEnter, fpBack, filePickerKeyMap, } from './file-picker.js';
|
|
21
24
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -34,4 +34,10 @@ export { createPanelGroup, } from './panels.js';
|
|
|
34
34
|
export { composite, modal, toast, } from './overlay.js';
|
|
35
35
|
// Interactive accordion
|
|
36
36
|
export { createAccordionState, interactiveAccordion, focusNext, focusPrev, toggleFocused, expandAll, collapseAll, accordionKeyMap, } from './accordion.js';
|
|
37
|
+
// Navigable table
|
|
38
|
+
export { createNavigableTableState, navigableTable, navTableFocusNext, navTableFocusPrev, navTablePageDown, navTablePageUp, navTableKeyMap, } from './navigable-table.js';
|
|
39
|
+
// Browsable list
|
|
40
|
+
export { createBrowsableListState, browsableList, listFocusNext, listFocusPrev, listPageDown, listPageUp, browsableListKeyMap, } from './browsable-list.js';
|
|
41
|
+
// File picker
|
|
42
|
+
export { createFilePickerState, filePicker, fpFocusNext, fpFocusPrev, fpEnter, fpBack, filePickerKeyMap, } from './file-picker.js';
|
|
37
43
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,cAAc;AACd,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,iBAAiB;AACjB,OAAO,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,IAAI,GACL,MAAM,aAAa,CAAC;AAErB,WAAW;AACX,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAElD,UAAU;AACV,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,YAAY;AACZ,OAAO,EAGL,cAAc,GACf,MAAM,eAAe,CAAC;AAEvB,SAAS;AACT,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE7C,sCAAsC;AACtC,OAAO,EAIL,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,mBAAmB,EAEnB,OAAO,EAGP,SAAS,EACT,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EAIL,OAAO,EACP,QAAQ,GACT,MAAM,cAAc,CAAC;AAEtB,qCAAqC;AACrC,OAAO,EAGL,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,cAAc,EACd,QAAQ,EACR,MAAM,EACN,SAAS,EACT,aAAa,EACb,WAAW,EACX,SAAS,EACT,SAAS,EACT,SAAS,GACV,MAAM,eAAe,CAAC;AAEvB,gDAAgD;AAChD,OAAO,EAQL,QAAQ,GACT,MAAM,eAAe,CAAC;AAEvB,iBAAiB;AACjB,OAAO,EAGL,IAAI,GACL,MAAM,WAAW,CAAC;AAEnB,qBAAqB;AACrB,OAAO,EAKL,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,kBAAkB,CAAC;AAE1B,kBAAkB;AAClB,OAAO,EAGL,QAAQ,EACR,SAAS,EACT,OAAO,GACR,MAAM,WAAW,CAAC;AAEnB,cAAc;AACd,OAAO,EAKL,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAEzB,iCAAiC;AACjC,OAAO,EAIL,gBAAgB,EAChB,KAAK,EACL,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,eAAe,EACf,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,4CAA4C;AAC5C,OAAO,EAIL,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,sBAAsB;AACtB,OAAO,EAOL,SAAS,EACT,KAAK,EACL,KAAK,GACN,MAAM,cAAc,CAAC;AAEtB,wBAAwB;AACxB,OAAO,EAGL,oBAAoB,EACpB,oBAAoB,EACpB,SAAS,EACT,SAAS,EACT,aAAa,EACb,SAAS,EACT,WAAW,EACX,eAAe,GAChB,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,cAAc;AACd,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,iBAAiB;AACjB,OAAO,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,IAAI,GACL,MAAM,aAAa,CAAC;AAErB,WAAW;AACX,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAElD,UAAU;AACV,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,YAAY;AACZ,OAAO,EAGL,cAAc,GACf,MAAM,eAAe,CAAC;AAEvB,SAAS;AACT,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE7C,sCAAsC;AACtC,OAAO,EAIL,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,mBAAmB,EAEnB,OAAO,EAGP,SAAS,EACT,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EAIL,OAAO,EACP,QAAQ,GACT,MAAM,cAAc,CAAC;AAEtB,qCAAqC;AACrC,OAAO,EAGL,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,cAAc,EACd,QAAQ,EACR,MAAM,EACN,SAAS,EACT,aAAa,EACb,WAAW,EACX,SAAS,EACT,SAAS,EACT,SAAS,GACV,MAAM,eAAe,CAAC;AAEvB,gDAAgD;AAChD,OAAO,EAQL,QAAQ,GACT,MAAM,eAAe,CAAC;AAEvB,iBAAiB;AACjB,OAAO,EAGL,IAAI,GACL,MAAM,WAAW,CAAC;AAEnB,qBAAqB;AACrB,OAAO,EAKL,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,kBAAkB,CAAC;AAE1B,kBAAkB;AAClB,OAAO,EAGL,QAAQ,EACR,SAAS,EACT,OAAO,GACR,MAAM,WAAW,CAAC;AAEnB,cAAc;AACd,OAAO,EAKL,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAEzB,iCAAiC;AACjC,OAAO,EAIL,gBAAgB,EAChB,KAAK,EACL,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,eAAe,EACf,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,4CAA4C;AAC5C,OAAO,EAIL,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,sBAAsB;AACtB,OAAO,EAOL,SAAS,EACT,KAAK,EACL,KAAK,GACN,MAAM,cAAc,CAAC;AAEtB,wBAAwB;AACxB,OAAO,EAGL,oBAAoB,EACpB,oBAAoB,EACpB,SAAS,EACT,SAAS,EACT,aAAa,EACb,SAAS,EACT,WAAW,EACX,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,kBAAkB;AAClB,OAAO,EAIL,yBAAyB,EACzB,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,cAAc,GACf,MAAM,sBAAsB,CAAC;AAE9B,iBAAiB;AACjB,OAAO,EAKL,wBAAwB,EACxB,aAAa,EACb,aAAa,EACb,aAAa,EACb,YAAY,EACZ,UAAU,EACV,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAE7B,cAAc;AACd,OAAO,EAKL,qBAAqB,EACrB,UAAU,EACV,WAAW,EACX,WAAW,EACX,OAAO,EACP,MAAM,EACN,gBAAgB,GACjB,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigable table building block.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the static `table()` from bijou core with focus management,
|
|
5
|
+
* keyboard navigation, and vertical scrolling.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* // In TEA init:
|
|
9
|
+
* const tableState = createNavigableTableState({ columns, rows, height: 10 });
|
|
10
|
+
*
|
|
11
|
+
* // In TEA view:
|
|
12
|
+
* const output = navigableTable(model.tableState, { ctx });
|
|
13
|
+
*
|
|
14
|
+
* // In TEA update:
|
|
15
|
+
* case 'focus-next':
|
|
16
|
+
* return [{ ...model, tableState: navTableFocusNext(model.tableState) }, []];
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { type TableColumn, type BijouContext } from '@flyingrobots/bijou';
|
|
20
|
+
import { type KeyMap } from './keybindings.js';
|
|
21
|
+
export interface NavigableTableState {
|
|
22
|
+
readonly columns: TableColumn[];
|
|
23
|
+
readonly rows: readonly (readonly string[])[];
|
|
24
|
+
readonly focusRow: number;
|
|
25
|
+
readonly scrollY: number;
|
|
26
|
+
readonly height: number;
|
|
27
|
+
}
|
|
28
|
+
export interface NavigableTableOptions {
|
|
29
|
+
readonly columns: TableColumn[];
|
|
30
|
+
readonly rows: readonly (readonly string[])[];
|
|
31
|
+
readonly height?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface NavTableRenderOptions {
|
|
34
|
+
readonly focusIndicator?: string;
|
|
35
|
+
readonly ctx?: BijouContext;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create initial navigable table state.
|
|
39
|
+
*
|
|
40
|
+
* `height` defaults to 10 rows when not provided.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createNavigableTableState(options: NavigableTableOptions): NavigableTableState;
|
|
43
|
+
/** Move focus to the next row (wraps around). */
|
|
44
|
+
export declare function navTableFocusNext(state: NavigableTableState): NavigableTableState;
|
|
45
|
+
/** Move focus to the previous row (wraps around). */
|
|
46
|
+
export declare function navTableFocusPrev(state: NavigableTableState): NavigableTableState;
|
|
47
|
+
/** Move focus down by one page (clamps to last row). */
|
|
48
|
+
export declare function navTablePageDown(state: NavigableTableState): NavigableTableState;
|
|
49
|
+
/** Move focus up by one page (clamps to first row). */
|
|
50
|
+
export declare function navTablePageUp(state: NavigableTableState): NavigableTableState;
|
|
51
|
+
/**
|
|
52
|
+
* Render the navigable table with a focus indicator on the focused row.
|
|
53
|
+
*
|
|
54
|
+
* Slices visible rows based on `scrollY` and `height`, prepends a focus
|
|
55
|
+
* indicator to the first column, then delegates to core `table()`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function navigableTable(state: NavigableTableState, options?: NavTableRenderOptions): string;
|
|
58
|
+
/**
|
|
59
|
+
* Create a preconfigured KeyMap for navigable table navigation.
|
|
60
|
+
*
|
|
61
|
+
* The caller provides their own message types for each action:
|
|
62
|
+
* ```ts
|
|
63
|
+
* const keys = navTableKeyMap({
|
|
64
|
+
* focusNext: { type: 'next-row' },
|
|
65
|
+
* focusPrev: { type: 'prev-row' },
|
|
66
|
+
* pageDown: { type: 'page-down' },
|
|
67
|
+
* pageUp: { type: 'page-up' },
|
|
68
|
+
* quit: { type: 'quit' },
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function navTableKeyMap<Msg>(actions: {
|
|
73
|
+
focusNext: Msg;
|
|
74
|
+
focusPrev: Msg;
|
|
75
|
+
pageDown: Msg;
|
|
76
|
+
pageUp: Msg;
|
|
77
|
+
quit: Msg;
|
|
78
|
+
}): KeyMap<Msg>;
|
|
79
|
+
//# sourceMappingURL=navigable-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigable-table.d.ts","sourceRoot":"","sources":["../src/navigable-table.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM7D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,YAAY,CAAC;CAC7B;AAMD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,qBAAqB,GAAG,mBAAmB,CAS7F;AAMD,iDAAiD;AACjD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,GAAG,mBAAmB,CAIjF;AAED,qDAAqD;AACrD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,GAAG,mBAAmB,CAIjF;AAED,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,mBAAmB,GAAG,mBAAmB,CAIhF;AAED,uDAAuD;AACvD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,mBAAmB,CAI9E;AAqBD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAwBlG;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE;IAC3C,SAAS,EAAE,GAAG,CAAC;IACf,SAAS,EAAE,GAAG,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC;IACd,MAAM,EAAE,GAAG,CAAC;IACZ,IAAI,EAAE,GAAG,CAAC;CACX,GAAG,MAAM,CAAC,GAAG,CAAC,CAcd"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigable table building block.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the static `table()` from bijou core with focus management,
|
|
5
|
+
* keyboard navigation, and vertical scrolling.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* // In TEA init:
|
|
9
|
+
* const tableState = createNavigableTableState({ columns, rows, height: 10 });
|
|
10
|
+
*
|
|
11
|
+
* // In TEA view:
|
|
12
|
+
* const output = navigableTable(model.tableState, { ctx });
|
|
13
|
+
*
|
|
14
|
+
* // In TEA update:
|
|
15
|
+
* case 'focus-next':
|
|
16
|
+
* return [{ ...model, tableState: navTableFocusNext(model.tableState) }, []];
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { table, } from '@flyingrobots/bijou';
|
|
20
|
+
import { createKeyMap } from './keybindings.js';
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// State creation
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Create initial navigable table state.
|
|
26
|
+
*
|
|
27
|
+
* `height` defaults to 10 rows when not provided.
|
|
28
|
+
*/
|
|
29
|
+
export function createNavigableTableState(options) {
|
|
30
|
+
const height = Math.max(1, options.height ?? 10);
|
|
31
|
+
return {
|
|
32
|
+
columns: [...options.columns],
|
|
33
|
+
rows: options.rows.map((row) => [...row]),
|
|
34
|
+
focusRow: 0,
|
|
35
|
+
scrollY: 0,
|
|
36
|
+
height,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// State transformers
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
/** Move focus to the next row (wraps around). */
|
|
43
|
+
export function navTableFocusNext(state) {
|
|
44
|
+
if (state.rows.length === 0)
|
|
45
|
+
return state;
|
|
46
|
+
const focusRow = (state.focusRow + 1) % state.rows.length;
|
|
47
|
+
return { ...state, focusRow, scrollY: adjustScroll(focusRow, state.scrollY, state.height, state.rows.length) };
|
|
48
|
+
}
|
|
49
|
+
/** Move focus to the previous row (wraps around). */
|
|
50
|
+
export function navTableFocusPrev(state) {
|
|
51
|
+
if (state.rows.length === 0)
|
|
52
|
+
return state;
|
|
53
|
+
const focusRow = (state.focusRow - 1 + state.rows.length) % state.rows.length;
|
|
54
|
+
return { ...state, focusRow, scrollY: adjustScroll(focusRow, state.scrollY, state.height, state.rows.length) };
|
|
55
|
+
}
|
|
56
|
+
/** Move focus down by one page (clamps to last row). */
|
|
57
|
+
export function navTablePageDown(state) {
|
|
58
|
+
if (state.rows.length === 0)
|
|
59
|
+
return state;
|
|
60
|
+
const focusRow = Math.min(state.focusRow + state.height, state.rows.length - 1);
|
|
61
|
+
return { ...state, focusRow, scrollY: adjustScroll(focusRow, state.scrollY, state.height, state.rows.length) };
|
|
62
|
+
}
|
|
63
|
+
/** Move focus up by one page (clamps to first row). */
|
|
64
|
+
export function navTablePageUp(state) {
|
|
65
|
+
if (state.rows.length === 0)
|
|
66
|
+
return state;
|
|
67
|
+
const focusRow = Math.max(state.focusRow - state.height, 0);
|
|
68
|
+
return { ...state, focusRow, scrollY: adjustScroll(focusRow, state.scrollY, state.height, state.rows.length) };
|
|
69
|
+
}
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Scroll helper
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
function adjustScroll(focusRow, scrollY, height, totalRows) {
|
|
74
|
+
let newScrollY = scrollY;
|
|
75
|
+
if (focusRow < newScrollY) {
|
|
76
|
+
newScrollY = focusRow;
|
|
77
|
+
}
|
|
78
|
+
else if (focusRow >= newScrollY + height) {
|
|
79
|
+
newScrollY = focusRow - height + 1;
|
|
80
|
+
}
|
|
81
|
+
const maxScroll = Math.max(0, totalRows - height);
|
|
82
|
+
return Math.min(newScrollY, maxScroll);
|
|
83
|
+
}
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Render
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
/**
|
|
88
|
+
* Render the navigable table with a focus indicator on the focused row.
|
|
89
|
+
*
|
|
90
|
+
* Slices visible rows based on `scrollY` and `height`, prepends a focus
|
|
91
|
+
* indicator to the first column, then delegates to core `table()`.
|
|
92
|
+
*/
|
|
93
|
+
export function navigableTable(state, options) {
|
|
94
|
+
if (state.rows.length === 0) {
|
|
95
|
+
return table({ columns: state.columns, rows: [], ctx: options?.ctx });
|
|
96
|
+
}
|
|
97
|
+
const indicator = options?.focusIndicator ?? '\u25b8';
|
|
98
|
+
const pad = ' '.repeat(indicator.length);
|
|
99
|
+
// Slice visible window
|
|
100
|
+
const visibleRows = state.rows.slice(state.scrollY, state.scrollY + state.height);
|
|
101
|
+
// Prepend focus indicator to first column
|
|
102
|
+
const decoratedRows = visibleRows.map((row, i) => {
|
|
103
|
+
const globalIndex = state.scrollY + i;
|
|
104
|
+
const prefix = globalIndex === state.focusRow ? indicator : pad;
|
|
105
|
+
const firstCol = `${prefix} ${row[0] ?? ''}`;
|
|
106
|
+
return [firstCol, ...row.slice(1)];
|
|
107
|
+
});
|
|
108
|
+
return table({
|
|
109
|
+
columns: state.columns,
|
|
110
|
+
rows: decoratedRows,
|
|
111
|
+
ctx: options?.ctx,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Convenience keymap
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
/**
|
|
118
|
+
* Create a preconfigured KeyMap for navigable table navigation.
|
|
119
|
+
*
|
|
120
|
+
* The caller provides their own message types for each action:
|
|
121
|
+
* ```ts
|
|
122
|
+
* const keys = navTableKeyMap({
|
|
123
|
+
* focusNext: { type: 'next-row' },
|
|
124
|
+
* focusPrev: { type: 'prev-row' },
|
|
125
|
+
* pageDown: { type: 'page-down' },
|
|
126
|
+
* pageUp: { type: 'page-up' },
|
|
127
|
+
* quit: { type: 'quit' },
|
|
128
|
+
* });
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function navTableKeyMap(actions) {
|
|
132
|
+
return createKeyMap()
|
|
133
|
+
.group('Navigation', (g) => g
|
|
134
|
+
.bind('j', 'Next row', actions.focusNext)
|
|
135
|
+
.bind('down', 'Next row', actions.focusNext)
|
|
136
|
+
.bind('k', 'Previous row', actions.focusPrev)
|
|
137
|
+
.bind('up', 'Previous row', actions.focusPrev)
|
|
138
|
+
.bind('d', 'Page down', actions.pageDown)
|
|
139
|
+
.bind('pagedown', 'Page down', actions.pageDown)
|
|
140
|
+
.bind('u', 'Page up', actions.pageUp)
|
|
141
|
+
.bind('pageup', 'Page up', actions.pageUp))
|
|
142
|
+
.bind('q', 'Quit', actions.quit)
|
|
143
|
+
.bind('ctrl+c', 'Quit', actions.quit);
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=navigable-table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigable-table.js","sourceRoot":"","sources":["../src/navigable-table.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flyingrobots/bijou-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "TEA runtime for terminal UIs — model/update/view with keyboard input, alt screen, and layout helpers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"prepack": "tsc -b",
|
|
24
24
|
"lint": "tsc --noEmit"
|
|
25
25
|
},
|
|
26
|
-
"
|
|
27
|
-
"@flyingrobots/bijou": "0.
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@flyingrobots/bijou": "0.6.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^22.0.0",
|