@flowtty/core 1.0.0-alpha.1
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 +35 -0
- package/dist/backend-BcB7VR87.d.ts +74 -0
- package/dist/cells-CaXEx4lH.d.ts +31 -0
- package/dist/chunk-D6HW2BNM.js +106 -0
- package/dist/host/index.d.ts +8 -0
- package/dist/host/index.js +357 -0
- package/dist/host-DttBG-OZ.d.ts +151 -0
- package/dist/index.d.ts +143 -0
- package/dist/index.js +382 -0
- package/dist/testing/index.d.ts +63 -0
- package/dist/testing/index.js +72 -0
- package/package.json +53 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { loadYoga } from 'yoga-layout/load';
|
|
2
|
+
|
|
3
|
+
type Yoga = Awaited<ReturnType<typeof loadYoga>>;
|
|
4
|
+
type YogaNode = ReturnType<Yoga['Node']['create']>;
|
|
5
|
+
declare function getYoga(): Promise<Yoga>;
|
|
6
|
+
|
|
7
|
+
type BorderStyle = 'single' | 'double' | 'round' | 'bold' | 'classic';
|
|
8
|
+
/** The library-wide default for components that draw a box-border chrome
|
|
9
|
+
* (DialogHost wrappers, Menu panels, Table grids). Declared once so the
|
|
10
|
+
* default is a single edit, not one per component. */
|
|
11
|
+
declare const DEFAULT_BORDER_STYLE: BorderStyle;
|
|
12
|
+
interface GridChars {
|
|
13
|
+
h: string;
|
|
14
|
+
v: string;
|
|
15
|
+
tl: string;
|
|
16
|
+
tr: string;
|
|
17
|
+
bl: string;
|
|
18
|
+
br: string;
|
|
19
|
+
tDown: string;
|
|
20
|
+
tUp: string;
|
|
21
|
+
tRight: string;
|
|
22
|
+
tLeft: string;
|
|
23
|
+
cross: string;
|
|
24
|
+
}
|
|
25
|
+
declare const GRID_CHARS: Record<BorderStyle, GridChars>;
|
|
26
|
+
type BorderChars = Pick<GridChars, 'h' | 'v' | 'tl' | 'tr' | 'bl' | 'br'>;
|
|
27
|
+
declare const BORDER_CHARS: Record<BorderStyle, BorderChars>;
|
|
28
|
+
|
|
29
|
+
interface Rect {
|
|
30
|
+
left: number;
|
|
31
|
+
top: number;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
}
|
|
35
|
+
declare function computeLayout(container: Container, width: number, height: number): void;
|
|
36
|
+
declare function layoutOf(inst: Instance, offsetX?: number, offsetY?: number): Rect;
|
|
37
|
+
|
|
38
|
+
type HostType = 'flowtty-box';
|
|
39
|
+
interface BoxProps {
|
|
40
|
+
/** Fixed size in cells. Strings like '100%' use Yoga's percentage sizing. */
|
|
41
|
+
width?: number | string;
|
|
42
|
+
height?: number | string;
|
|
43
|
+
flexDirection?: 'row' | 'column';
|
|
44
|
+
/** Default 'static' (Yoga's stack flow). 'absolute' positions via top/left/right/bottom relative to the nearest non-static ancestor. */
|
|
45
|
+
position?: 'static' | 'absolute';
|
|
46
|
+
top?: number;
|
|
47
|
+
left?: number;
|
|
48
|
+
right?: number;
|
|
49
|
+
bottom?: number;
|
|
50
|
+
/** Main-axis alignment of children (flexbox). */
|
|
51
|
+
justifyContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly';
|
|
52
|
+
/** Cross-axis alignment of children. */
|
|
53
|
+
alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch';
|
|
54
|
+
wrap?: 'wrap' | 'truncate' | 'none';
|
|
55
|
+
color?: string;
|
|
56
|
+
bold?: boolean;
|
|
57
|
+
dim?: boolean;
|
|
58
|
+
underline?: boolean;
|
|
59
|
+
inverse?: boolean;
|
|
60
|
+
strikethrough?: boolean;
|
|
61
|
+
link?: string;
|
|
62
|
+
backgroundColor?: string;
|
|
63
|
+
border?: BorderStyle;
|
|
64
|
+
borderColor?: string;
|
|
65
|
+
borderTitle?: string;
|
|
66
|
+
padding?: number;
|
|
67
|
+
paddingX?: number;
|
|
68
|
+
paddingY?: number;
|
|
69
|
+
paddingTop?: number;
|
|
70
|
+
paddingRight?: number;
|
|
71
|
+
paddingBottom?: number;
|
|
72
|
+
paddingLeft?: number;
|
|
73
|
+
margin?: number;
|
|
74
|
+
marginX?: number;
|
|
75
|
+
marginY?: number;
|
|
76
|
+
marginTop?: number;
|
|
77
|
+
marginRight?: number;
|
|
78
|
+
marginBottom?: number;
|
|
79
|
+
marginLeft?: number;
|
|
80
|
+
gap?: number;
|
|
81
|
+
rowGap?: number;
|
|
82
|
+
columnGap?: number;
|
|
83
|
+
flexGrow?: number;
|
|
84
|
+
flexShrink?: number;
|
|
85
|
+
flexBasis?: number | 'auto' | `${number}%`;
|
|
86
|
+
flexWrap?: 'nowrap' | 'wrap' | 'wrap-reverse';
|
|
87
|
+
/** Cross-axis distribution of wrap lines. Only effective when flexWrap is 'wrap' or 'wrap-reverse'
|
|
88
|
+
* AND the parent has extra cross-axis space. Default 'flex-start'. */
|
|
89
|
+
alignContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' | 'stretch';
|
|
90
|
+
/** Minimum cell size — Yoga prevents the box from shrinking below this.
|
|
91
|
+
* Accepts a number (cells) or a percent string (e.g. '50%'). Undefined = no minimum. */
|
|
92
|
+
minWidth?: number | `${number}%`;
|
|
93
|
+
maxWidth?: number | `${number}%`;
|
|
94
|
+
minHeight?: number | `${number}%`;
|
|
95
|
+
maxHeight?: number | `${number}%`;
|
|
96
|
+
/** Width / height ratio. Yoga derives the missing dimension from the constrained one.
|
|
97
|
+
* CSS convention: `aspectRatio: 2` = twice as wide as tall; `0.5` = twice as tall as wide; `1` = square. */
|
|
98
|
+
aspectRatio?: number;
|
|
99
|
+
/** 'none' removes this box and all descendants from layout (siblings don't reserve space for it,
|
|
100
|
+
* and paint skips the subtree). Default 'flex'. Useful for conditional UI without unmounting. */
|
|
101
|
+
display?: 'flex' | 'none';
|
|
102
|
+
/** Fires after layout with this box's computed rect. Use to read allocated dimensions
|
|
103
|
+
* for responsive rendering (e.g. paginating an article reader). **Diff before setState** —
|
|
104
|
+
* this fires on EVERY paint, and unconditionally calling setState with a new object
|
|
105
|
+
* will infinite-loop. Pattern:
|
|
106
|
+
* onLayout={(r) => { if (!size || size.width !== r.width || size.height !== r.height) setSize(r); }} */
|
|
107
|
+
onLayout?: (rect: Rect) => void;
|
|
108
|
+
/** Stacking order within the same paint pass. Higher values paint on top.
|
|
109
|
+
* Default 0. Tree order is the tiebreaker. Does NOT cross pass boundaries:
|
|
110
|
+
* absolutes always overlay stack-flow regardless of zIndex. */
|
|
111
|
+
zIndex?: number;
|
|
112
|
+
/** Clip descendants to this box's content rect. Default 'visible' (no clipping).
|
|
113
|
+
* 'hidden' clips ALL descendant writes including their backgrounds and borders.
|
|
114
|
+
* Does NOT clip this box's own background or border (those are this box's own area). */
|
|
115
|
+
overflow?: 'visible' | 'hidden';
|
|
116
|
+
}
|
|
117
|
+
interface Instance {
|
|
118
|
+
type: 'box';
|
|
119
|
+
props: BoxProps;
|
|
120
|
+
yogaNode: YogaNode;
|
|
121
|
+
children: Array<Instance | TextInstance>;
|
|
122
|
+
}
|
|
123
|
+
interface TextInstance {
|
|
124
|
+
type: 'text';
|
|
125
|
+
text: string;
|
|
126
|
+
parent?: Instance;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Adapter-facing root container: a list of top-level Instances plus the loaded
|
|
130
|
+
* Yoga module. Each framework adapter (React reconciler, Vue, …) builds one of
|
|
131
|
+
* these and passes it to `computeLayout` + `paint`. Lives in host so layout /
|
|
132
|
+
* paint (in core) can depend on it without pulling in any adapter code.
|
|
133
|
+
*/
|
|
134
|
+
interface Container {
|
|
135
|
+
children: Instance[];
|
|
136
|
+
Yoga: Yoga;
|
|
137
|
+
}
|
|
138
|
+
declare function createInstance(type: HostType, props: BoxProps, Yoga: Yoga): Instance;
|
|
139
|
+
declare function createTextInstance(text: string, _Yoga: Yoga): TextInstance;
|
|
140
|
+
declare function applyProps(inst: Instance, props: BoxProps, _Yoga: Yoga): void;
|
|
141
|
+
declare function measureText(text: string): {
|
|
142
|
+
width: number;
|
|
143
|
+
height: number;
|
|
144
|
+
};
|
|
145
|
+
declare function ownText(inst: Instance): string;
|
|
146
|
+
declare function refreshMeasure(inst: Instance, _Yoga: Yoga): void;
|
|
147
|
+
declare function appendChild(parent: Instance, child: Instance | TextInstance, Yoga: Yoga): void;
|
|
148
|
+
declare function removeChild(parent: Instance, child: Instance | TextInstance, Yoga: Yoga): void;
|
|
149
|
+
declare function insertBefore(parent: Instance, child: Instance | TextInstance, before: Instance | TextInstance, Yoga: Yoga): void;
|
|
150
|
+
|
|
151
|
+
export { BORDER_CHARS as B, type Container as C, DEFAULT_BORDER_STYLE as D, GRID_CHARS as G, type HostType as H, type Instance as I, type Rect as R, type TextInstance as T, type Yoga as Y, type BorderChars as a, type BorderStyle as b, type BoxProps as c, type GridChars as d, type YogaNode as e, appendChild as f, applyProps as g, computeLayout as h, createInstance as i, createTextInstance as j, getYoga as k, insertBefore as l, layoutOf as m, measureText as n, ownText as o, removeChild as p, refreshMeasure as r };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export { B as Buffer, C as Cell, S as Style } from './cells-CaXEx4lH.js';
|
|
2
|
+
import { K as Key } from './backend-BcB7VR87.js';
|
|
3
|
+
export { B as Backend } from './backend-BcB7VR87.js';
|
|
4
|
+
export { a as BorderChars, b as BorderStyle, c as BoxProps, D as DEFAULT_BORDER_STYLE, G as GRID_CHARS, d as GridChars } from './host-DttBG-OZ.js';
|
|
5
|
+
import 'yoga-layout/load';
|
|
6
|
+
|
|
7
|
+
type WrapMode = 'wrap' | 'truncate' | 'none';
|
|
8
|
+
/**
|
|
9
|
+
* Lay out `text` into display lines fitting within `width` cells.
|
|
10
|
+
* Assumes 1 code point = 1 cell (no CJK/emoji width awareness in M1d).
|
|
11
|
+
*
|
|
12
|
+
* - 'wrap' — word-wrap at spaces; any single word longer than width is char-wrapped.
|
|
13
|
+
* - 'truncate' — each source line truncated to width, with `…` in the last cell when truncated.
|
|
14
|
+
* - 'none' — each source line preserved unchanged (caller is responsible for overflow).
|
|
15
|
+
*
|
|
16
|
+
* Always returns at least one line (empty input → `['']`, matching measureText's height=1 default).
|
|
17
|
+
*/
|
|
18
|
+
declare function wrapText(text: string, width: number, mode: WrapMode): string[];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Split a multi-line source text into visual lines for paginated rendering
|
|
22
|
+
* with an optional line-number gutter.
|
|
23
|
+
*
|
|
24
|
+
* - 'nowrap': one visual line per source line (caller is responsible for
|
|
25
|
+
* horizontal clipping — typically via wrap='truncate' on the rendering Box).
|
|
26
|
+
* - 'wrap': source lines longer than `width` are hard-wrapped at the cell
|
|
27
|
+
* boundary; continuation visual-lines carry `lineNum: null` so the gutter
|
|
28
|
+
* stays blank for them and only the first visual line shows the source line
|
|
29
|
+
* number.
|
|
30
|
+
*
|
|
31
|
+
* Width is in cells (counted via `[...line]` for grapheme-naive codepoint
|
|
32
|
+
* iteration — matches flowtty's paint-side width assumptions).
|
|
33
|
+
*
|
|
34
|
+
* Typical use:
|
|
35
|
+
* ```ts
|
|
36
|
+
* const lines = splitVisualLines(bodyText, wrapMode, effectiveBodyWidth);
|
|
37
|
+
* const page = lines.slice(pageIdx * pageH, (pageIdx + 1) * pageH);
|
|
38
|
+
* page.map(({ text, lineNum }) => ...);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
interface VisualLine {
|
|
42
|
+
text: string;
|
|
43
|
+
/** Source-file line number for the first visual line of a wrapped source line; null for continuation lines. 1-based. */
|
|
44
|
+
lineNum: number | null;
|
|
45
|
+
}
|
|
46
|
+
declare function splitVisualLines(text: string, mode: 'wrap' | 'nowrap', width: number): VisualLine[];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Center a `visible`-sized window of items around `cursor`, clamped so the
|
|
50
|
+
* window never starts before 0 or extends past the end of the list. Returns
|
|
51
|
+
* the start index and the sliced items.
|
|
52
|
+
*
|
|
53
|
+
* Typical use — list views that scroll a cursor through long data:
|
|
54
|
+
* ```ts
|
|
55
|
+
* const { start, items } = windowAround(rows, cursor, termHeight - chrome);
|
|
56
|
+
* items.map((row, i) => {
|
|
57
|
+
* const sel = (start + i) === cursor;
|
|
58
|
+
* ...
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* The window prefers to center the cursor; near the edges it sticks to the
|
|
63
|
+
* top/bottom so the cursor remains visible without empty padding.
|
|
64
|
+
*/
|
|
65
|
+
declare function windowAround<T>(items: readonly T[], cursor: number, visible: number): {
|
|
66
|
+
start: number;
|
|
67
|
+
items: T[];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Display width in terminal cells of a single Unicode code point: 0 for control
|
|
72
|
+
* / combining / zero-width, 2 for East Asian Wide & Fullwidth (and most emoji),
|
|
73
|
+
* 1 otherwise. Pass a code point (e.g. from `String#codePointAt`), not a string.
|
|
74
|
+
*/
|
|
75
|
+
declare function charWidth(cp: number): 0 | 1 | 2;
|
|
76
|
+
/**
|
|
77
|
+
* Display width in terminal cells of a string, summing `charWidth` over its
|
|
78
|
+
* code points (surrogate pairs counted once). See the file header for the
|
|
79
|
+
* grapheme-cluster caveat. Expects plain text, not ANSI-styled.
|
|
80
|
+
*/
|
|
81
|
+
declare function stringWidth(str: string): number;
|
|
82
|
+
|
|
83
|
+
interface EditorState {
|
|
84
|
+
value: string;
|
|
85
|
+
cursor: number;
|
|
86
|
+
}
|
|
87
|
+
type EditorAction = {
|
|
88
|
+
kind: 'edit';
|
|
89
|
+
state: EditorState;
|
|
90
|
+
} | {
|
|
91
|
+
kind: 'submit';
|
|
92
|
+
} | {
|
|
93
|
+
kind: 'cancel';
|
|
94
|
+
} | {
|
|
95
|
+
kind: 'noop';
|
|
96
|
+
};
|
|
97
|
+
declare function reduce$2(state: EditorState, key: Key): EditorAction;
|
|
98
|
+
|
|
99
|
+
interface SelectItem<T> {
|
|
100
|
+
label: string;
|
|
101
|
+
value: T;
|
|
102
|
+
}
|
|
103
|
+
interface SelectState {
|
|
104
|
+
cursor: number;
|
|
105
|
+
filter: string;
|
|
106
|
+
}
|
|
107
|
+
type SelectAction = {
|
|
108
|
+
kind: 'state';
|
|
109
|
+
state: SelectState;
|
|
110
|
+
} | {
|
|
111
|
+
kind: 'submit';
|
|
112
|
+
index: number;
|
|
113
|
+
} | {
|
|
114
|
+
kind: 'cancel';
|
|
115
|
+
} | {
|
|
116
|
+
kind: 'noop';
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Indices into `items` of items whose label contains `filter` (case-insensitive
|
|
120
|
+
* substring). Empty filter → all indices.
|
|
121
|
+
*/
|
|
122
|
+
declare function visibleIndices<T>(items: SelectItem<T>[], filter: string): number[];
|
|
123
|
+
declare function reduce$1<T>(items: SelectItem<T>[], state: SelectState, key: Key): SelectAction;
|
|
124
|
+
|
|
125
|
+
interface MultiSelectState {
|
|
126
|
+
cursor: number;
|
|
127
|
+
}
|
|
128
|
+
type MultiSelectAction = {
|
|
129
|
+
kind: 'state';
|
|
130
|
+
state: MultiSelectState;
|
|
131
|
+
} | {
|
|
132
|
+
kind: 'toggle';
|
|
133
|
+
index: number;
|
|
134
|
+
} | {
|
|
135
|
+
kind: 'submit';
|
|
136
|
+
} | {
|
|
137
|
+
kind: 'cancel';
|
|
138
|
+
} | {
|
|
139
|
+
kind: 'noop';
|
|
140
|
+
};
|
|
141
|
+
declare function reduce<T>(items: SelectItem<T>[], state: MultiSelectState, key: Key): MultiSelectAction;
|
|
142
|
+
|
|
143
|
+
export { type EditorAction, type EditorState, Key, type MultiSelectAction, type MultiSelectState, type SelectAction, type SelectItem, type SelectState, type VisualLine, type WrapMode, charWidth, reduce$2 as editorReducer, reduce as multiSelectReducer, reduce$1 as selectReducer, splitVisualLines, stringWidth, visibleIndices, windowAround, wrapText };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
export { Buffer, DEFAULT_BORDER_STYLE, GRID_CHARS, wrapText } from './chunk-D6HW2BNM.js';
|
|
2
|
+
|
|
3
|
+
// src/visualLines.ts
|
|
4
|
+
function splitVisualLines(text, mode, width) {
|
|
5
|
+
const sources = text.split("\n");
|
|
6
|
+
if (mode === "nowrap" || width <= 0) {
|
|
7
|
+
return sources.map((line, i) => ({ text: line, lineNum: i + 1 }));
|
|
8
|
+
}
|
|
9
|
+
const out = [];
|
|
10
|
+
for (let i = 0; i < sources.length; i++) {
|
|
11
|
+
const line = sources[i];
|
|
12
|
+
const chars = [...line];
|
|
13
|
+
if (chars.length <= width) {
|
|
14
|
+
out.push({ text: line, lineNum: i + 1 });
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
let first = true;
|
|
18
|
+
for (let j = 0; j < chars.length; j += width) {
|
|
19
|
+
out.push({
|
|
20
|
+
text: chars.slice(j, j + width).join(""),
|
|
21
|
+
lineNum: first ? i + 1 : null
|
|
22
|
+
});
|
|
23
|
+
first = false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/windowAround.ts
|
|
30
|
+
function windowAround(items, cursor, visible) {
|
|
31
|
+
const v = Math.max(1, visible);
|
|
32
|
+
const start = Math.max(0, Math.min(items.length - v, cursor - Math.floor(v / 2)));
|
|
33
|
+
return { start, items: items.slice(start, start + v) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/displayWidth.ts
|
|
37
|
+
var ZERO_WIDTH = [
|
|
38
|
+
[768, 879],
|
|
39
|
+
[1155, 1158],
|
|
40
|
+
[1160, 1161],
|
|
41
|
+
[1425, 1469],
|
|
42
|
+
[1471, 1471],
|
|
43
|
+
[1473, 1474],
|
|
44
|
+
[1476, 1477],
|
|
45
|
+
[1479, 1479],
|
|
46
|
+
[1536, 1539],
|
|
47
|
+
[1552, 1557],
|
|
48
|
+
[1611, 1630],
|
|
49
|
+
[1648, 1648],
|
|
50
|
+
[1750, 1764],
|
|
51
|
+
[1767, 1768],
|
|
52
|
+
[1770, 1773],
|
|
53
|
+
[1807, 1807],
|
|
54
|
+
[1809, 1809],
|
|
55
|
+
[1840, 1866],
|
|
56
|
+
[1958, 1968],
|
|
57
|
+
[2027, 2035],
|
|
58
|
+
[2305, 2306],
|
|
59
|
+
[2364, 2364],
|
|
60
|
+
[2369, 2376],
|
|
61
|
+
[2381, 2381],
|
|
62
|
+
[2385, 2388],
|
|
63
|
+
[2402, 2403],
|
|
64
|
+
[2433, 2433],
|
|
65
|
+
[2492, 2492],
|
|
66
|
+
[2497, 2500],
|
|
67
|
+
[2509, 2509],
|
|
68
|
+
[2530, 2531],
|
|
69
|
+
[2561, 2562],
|
|
70
|
+
[2620, 2620],
|
|
71
|
+
[2625, 2626],
|
|
72
|
+
[2631, 2632],
|
|
73
|
+
[2635, 2637],
|
|
74
|
+
[2672, 2673],
|
|
75
|
+
[2689, 2690],
|
|
76
|
+
[2748, 2748],
|
|
77
|
+
[2753, 2757],
|
|
78
|
+
[2759, 2760],
|
|
79
|
+
[2765, 2765],
|
|
80
|
+
[2786, 2787],
|
|
81
|
+
[2817, 2817],
|
|
82
|
+
[2876, 2876],
|
|
83
|
+
[2879, 2879],
|
|
84
|
+
[2881, 2883],
|
|
85
|
+
[2893, 2893],
|
|
86
|
+
[2902, 2902],
|
|
87
|
+
[2946, 2946],
|
|
88
|
+
[3008, 3008],
|
|
89
|
+
[3021, 3021],
|
|
90
|
+
[3134, 3136],
|
|
91
|
+
[3142, 3144],
|
|
92
|
+
[3146, 3149],
|
|
93
|
+
[3157, 3158],
|
|
94
|
+
[3260, 3260],
|
|
95
|
+
[3263, 3263],
|
|
96
|
+
[3270, 3270],
|
|
97
|
+
[3276, 3277],
|
|
98
|
+
[3298, 3299],
|
|
99
|
+
[3393, 3395],
|
|
100
|
+
[3405, 3405],
|
|
101
|
+
[3530, 3530],
|
|
102
|
+
[3538, 3540],
|
|
103
|
+
[3542, 3542],
|
|
104
|
+
[3633, 3633],
|
|
105
|
+
[3636, 3642],
|
|
106
|
+
[3655, 3662],
|
|
107
|
+
[3761, 3761],
|
|
108
|
+
[3764, 3769],
|
|
109
|
+
[3771, 3772],
|
|
110
|
+
[3784, 3789],
|
|
111
|
+
[3864, 3865],
|
|
112
|
+
[3893, 3893],
|
|
113
|
+
[3895, 3895],
|
|
114
|
+
[3897, 3897],
|
|
115
|
+
[3953, 3966],
|
|
116
|
+
[3968, 3972],
|
|
117
|
+
[3974, 3975],
|
|
118
|
+
[3984, 3991],
|
|
119
|
+
[3993, 4028],
|
|
120
|
+
[4038, 4038],
|
|
121
|
+
[4141, 4144],
|
|
122
|
+
[4146, 4146],
|
|
123
|
+
[4150, 4151],
|
|
124
|
+
[4153, 4153],
|
|
125
|
+
[4184, 4185],
|
|
126
|
+
[4448, 4607],
|
|
127
|
+
[4959, 4959],
|
|
128
|
+
[5906, 5908],
|
|
129
|
+
[5938, 5940],
|
|
130
|
+
[5970, 5971],
|
|
131
|
+
[6002, 6003],
|
|
132
|
+
[6068, 6069],
|
|
133
|
+
[6071, 6077],
|
|
134
|
+
[6086, 6086],
|
|
135
|
+
[6089, 6099],
|
|
136
|
+
[6109, 6109],
|
|
137
|
+
[6155, 6157],
|
|
138
|
+
[6313, 6313],
|
|
139
|
+
[6432, 6434],
|
|
140
|
+
[6439, 6440],
|
|
141
|
+
[6450, 6450],
|
|
142
|
+
[6457, 6459],
|
|
143
|
+
[6679, 6680],
|
|
144
|
+
[6912, 6915],
|
|
145
|
+
[6964, 6964],
|
|
146
|
+
[6966, 6970],
|
|
147
|
+
[6972, 6972],
|
|
148
|
+
[6978, 6978],
|
|
149
|
+
[7019, 7027],
|
|
150
|
+
[7616, 7626],
|
|
151
|
+
[7678, 7679],
|
|
152
|
+
[8203, 8207],
|
|
153
|
+
[8234, 8238],
|
|
154
|
+
[8288, 8291],
|
|
155
|
+
[8298, 8303],
|
|
156
|
+
[8400, 8431],
|
|
157
|
+
[12330, 12335],
|
|
158
|
+
[12441, 12442],
|
|
159
|
+
[43014, 43014],
|
|
160
|
+
[43019, 43019],
|
|
161
|
+
[43045, 43046],
|
|
162
|
+
[64286, 64286],
|
|
163
|
+
[65024, 65039],
|
|
164
|
+
[65056, 65059],
|
|
165
|
+
[65279, 65279],
|
|
166
|
+
[65529, 65531],
|
|
167
|
+
[68097, 68099],
|
|
168
|
+
[68101, 68102],
|
|
169
|
+
[68108, 68111],
|
|
170
|
+
[68152, 68154],
|
|
171
|
+
[68159, 68159],
|
|
172
|
+
[119143, 119145],
|
|
173
|
+
[119155, 119170],
|
|
174
|
+
[119173, 119179],
|
|
175
|
+
[119210, 119213],
|
|
176
|
+
[119362, 119364],
|
|
177
|
+
[917505, 917505],
|
|
178
|
+
[917536, 917631],
|
|
179
|
+
[917760, 917999]
|
|
180
|
+
];
|
|
181
|
+
var WIDE = [
|
|
182
|
+
[4352, 4447],
|
|
183
|
+
// Hangul Jamo (leading consonants)
|
|
184
|
+
[9001, 9002],
|
|
185
|
+
// angle brackets
|
|
186
|
+
[11904, 12350],
|
|
187
|
+
// CJK Radicals … Kangxi … CJK Symbols (stops before U+303F)
|
|
188
|
+
[12353, 13311],
|
|
189
|
+
// Hiragana … Katakana … CJK compatibility
|
|
190
|
+
[13312, 19903],
|
|
191
|
+
// CJK Unified Ideographs Extension A
|
|
192
|
+
[19968, 40959],
|
|
193
|
+
// CJK Unified Ideographs
|
|
194
|
+
[40960, 42191],
|
|
195
|
+
// Yi Syllables / Radicals
|
|
196
|
+
[43360, 43391],
|
|
197
|
+
// Hangul Jamo Extended-A
|
|
198
|
+
[44032, 55203],
|
|
199
|
+
// Hangul Syllables
|
|
200
|
+
[63744, 64255],
|
|
201
|
+
// CJK Compatibility Ideographs
|
|
202
|
+
[65040, 65049],
|
|
203
|
+
// Vertical Forms
|
|
204
|
+
[65072, 65135],
|
|
205
|
+
// CJK Compatibility Forms / Small Form Variants
|
|
206
|
+
[65280, 65376],
|
|
207
|
+
// Fullwidth Forms (stops before halfwidth katakana U+FF61)
|
|
208
|
+
[65504, 65510],
|
|
209
|
+
// Fullwidth signs
|
|
210
|
+
[110592, 110593],
|
|
211
|
+
// Kana Supplement
|
|
212
|
+
[127488, 127569],
|
|
213
|
+
// Enclosed Ideographic Supplement
|
|
214
|
+
[127744, 128591],
|
|
215
|
+
// Misc Symbols & Pictographs + Emoticons
|
|
216
|
+
[129280, 129535],
|
|
217
|
+
// Supplemental Symbols and Pictographs
|
|
218
|
+
[131072, 262141]
|
|
219
|
+
// CJK Unified Ideographs Extension B and beyond
|
|
220
|
+
];
|
|
221
|
+
function inRanges(cp, table) {
|
|
222
|
+
let lo = 0;
|
|
223
|
+
let hi = table.length - 1;
|
|
224
|
+
while (lo <= hi) {
|
|
225
|
+
const mid = lo + hi >> 1;
|
|
226
|
+
const [start, end] = table[mid];
|
|
227
|
+
if (cp < start) hi = mid - 1;
|
|
228
|
+
else if (cp > end) lo = mid + 1;
|
|
229
|
+
else return true;
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
function charWidth(cp) {
|
|
234
|
+
if (cp < 32 || cp >= 127 && cp < 160) return 0;
|
|
235
|
+
if (inRanges(cp, ZERO_WIDTH)) return 0;
|
|
236
|
+
if (inRanges(cp, WIDE)) return 2;
|
|
237
|
+
return 1;
|
|
238
|
+
}
|
|
239
|
+
function stringWidth(str) {
|
|
240
|
+
let w = 0;
|
|
241
|
+
for (const ch of str) w += charWidth(ch.codePointAt(0));
|
|
242
|
+
return w;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/editor.ts
|
|
246
|
+
var OPT_MAP = {
|
|
247
|
+
" ": ["\xA0"],
|
|
248
|
+
// U+00A0 NBSP
|
|
249
|
+
"-": ["\u2013", "\u2014"],
|
|
250
|
+
// en-dash U+2013, em-dash U+2014
|
|
251
|
+
"[": ["\u201C", "\u201D"],
|
|
252
|
+
// left/right double quote
|
|
253
|
+
"]": ["\u2018", "\u2019"]
|
|
254
|
+
// left/right single quote
|
|
255
|
+
};
|
|
256
|
+
var isWord = (c) => /[\p{L}\p{N}_]/u.test(c);
|
|
257
|
+
function wordLeft(value, cursor) {
|
|
258
|
+
let c = cursor;
|
|
259
|
+
while (c > 0 && !isWord(value[c - 1])) c--;
|
|
260
|
+
while (c > 0 && isWord(value[c - 1])) c--;
|
|
261
|
+
return c;
|
|
262
|
+
}
|
|
263
|
+
function wordRight(value, cursor) {
|
|
264
|
+
let c = cursor;
|
|
265
|
+
while (c < value.length && !isWord(value[c])) c++;
|
|
266
|
+
while (c < value.length && isWord(value[c])) c++;
|
|
267
|
+
return c;
|
|
268
|
+
}
|
|
269
|
+
function reduce(state, key) {
|
|
270
|
+
const { value, cursor } = state;
|
|
271
|
+
const clamp = (n) => Math.max(0, Math.min(value.length, n));
|
|
272
|
+
if (key.name === "left" && key.meta) return { kind: "edit", state: { value, cursor: wordLeft(value, cursor) } };
|
|
273
|
+
if (key.name === "b" && key.meta) return { kind: "edit", state: { value, cursor: wordLeft(value, cursor) } };
|
|
274
|
+
if (key.name === "right" && key.meta) return { kind: "edit", state: { value, cursor: wordRight(value, cursor) } };
|
|
275
|
+
if (key.name === "f" && key.meta) return { kind: "edit", state: { value, cursor: wordRight(value, cursor) } };
|
|
276
|
+
if (key.name === "left") return { kind: "edit", state: { value, cursor: clamp(cursor - 1) } };
|
|
277
|
+
if (key.name === "right") return { kind: "edit", state: { value, cursor: clamp(cursor + 1) } };
|
|
278
|
+
if (key.name === "home") return { kind: "edit", state: { value, cursor: 0 } };
|
|
279
|
+
if (key.name === "end") return { kind: "edit", state: { value, cursor: value.length } };
|
|
280
|
+
if (key.name === "a" && key.ctrl) return { kind: "edit", state: { value, cursor: 0 } };
|
|
281
|
+
if (key.name === "e" && key.ctrl) return { kind: "edit", state: { value, cursor: value.length } };
|
|
282
|
+
if (key.name === "backspace" && key.meta) {
|
|
283
|
+
const start = wordLeft(value, cursor);
|
|
284
|
+
return { kind: "edit", state: { value: value.slice(0, start) + value.slice(cursor), cursor: start } };
|
|
285
|
+
}
|
|
286
|
+
if (key.name === "w" && key.ctrl) {
|
|
287
|
+
const start = wordLeft(value, cursor);
|
|
288
|
+
return { kind: "edit", state: { value: value.slice(0, start) + value.slice(cursor), cursor: start } };
|
|
289
|
+
}
|
|
290
|
+
if (key.name === "d" && key.meta) {
|
|
291
|
+
const end = wordRight(value, cursor);
|
|
292
|
+
return { kind: "edit", state: { value: value.slice(0, cursor) + value.slice(end), cursor } };
|
|
293
|
+
}
|
|
294
|
+
if (key.name === "delete" && key.meta) {
|
|
295
|
+
const end = wordRight(value, cursor);
|
|
296
|
+
return { kind: "edit", state: { value: value.slice(0, cursor) + value.slice(end), cursor } };
|
|
297
|
+
}
|
|
298
|
+
if (key.name === "k" && key.ctrl) {
|
|
299
|
+
return { kind: "edit", state: { value: value.slice(0, cursor), cursor } };
|
|
300
|
+
}
|
|
301
|
+
if (key.name === "u" && key.ctrl) {
|
|
302
|
+
return { kind: "edit", state: { value: value.slice(cursor), cursor: 0 } };
|
|
303
|
+
}
|
|
304
|
+
if (key.meta && OPT_MAP[key.name]) {
|
|
305
|
+
const entry = OPT_MAP[key.name];
|
|
306
|
+
const ch = key.shift && entry[1] !== void 0 ? entry[1] : entry[0];
|
|
307
|
+
return { kind: "edit", state: { value: value.slice(0, cursor) + ch + value.slice(cursor), cursor: cursor + 1 } };
|
|
308
|
+
}
|
|
309
|
+
if (key.name === "backspace") {
|
|
310
|
+
if (cursor === 0) return { kind: "edit", state };
|
|
311
|
+
return { kind: "edit", state: { value: value.slice(0, cursor - 1) + value.slice(cursor), cursor: cursor - 1 } };
|
|
312
|
+
}
|
|
313
|
+
if (key.name === "delete" || key.name === "d" && key.ctrl) {
|
|
314
|
+
if (cursor === value.length) return { kind: "edit", state };
|
|
315
|
+
return { kind: "edit", state: { value: value.slice(0, cursor) + value.slice(cursor + 1), cursor } };
|
|
316
|
+
}
|
|
317
|
+
if (key.name === "return") return { kind: "submit" };
|
|
318
|
+
if (key.name === "escape") return { kind: "cancel" };
|
|
319
|
+
if (!key.ctrl && !key.meta && key.name.length === 1) {
|
|
320
|
+
const ch = key.name;
|
|
321
|
+
return { kind: "edit", state: { value: value.slice(0, cursor) + ch + value.slice(cursor), cursor: cursor + 1 } };
|
|
322
|
+
}
|
|
323
|
+
return { kind: "noop" };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/selectReducer.ts
|
|
327
|
+
function visibleIndices(items, filter) {
|
|
328
|
+
if (filter === "") return items.map((_, i) => i);
|
|
329
|
+
const q = filter.toLowerCase();
|
|
330
|
+
const out = [];
|
|
331
|
+
for (let i = 0; i < items.length; i++) {
|
|
332
|
+
if (items[i].label.toLowerCase().includes(q)) out.push(i);
|
|
333
|
+
}
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
function reduce2(items, state, key) {
|
|
337
|
+
const visible = visibleIndices(items, state.filter);
|
|
338
|
+
const n = visible.length;
|
|
339
|
+
if (key.name === "escape") return { kind: "cancel" };
|
|
340
|
+
if (key.name === "return") {
|
|
341
|
+
if (n === 0) return { kind: "noop" };
|
|
342
|
+
const cursor = Math.min(state.cursor, n - 1);
|
|
343
|
+
return { kind: "submit", index: visible[cursor] };
|
|
344
|
+
}
|
|
345
|
+
if (key.name === "down" || key.name === "j" && !key.ctrl && !key.meta) {
|
|
346
|
+
if (n === 0) return { kind: "noop" };
|
|
347
|
+
return { kind: "state", state: { cursor: (state.cursor + 1) % n, filter: state.filter } };
|
|
348
|
+
}
|
|
349
|
+
if (key.name === "up" || key.name === "k" && !key.ctrl && !key.meta) {
|
|
350
|
+
if (n === 0) return { kind: "noop" };
|
|
351
|
+
return { kind: "state", state: { cursor: (state.cursor - 1 + n) % n, filter: state.filter } };
|
|
352
|
+
}
|
|
353
|
+
if (key.name === "backspace") {
|
|
354
|
+
if (state.filter === "") return { kind: "noop" };
|
|
355
|
+
return { kind: "state", state: { cursor: 0, filter: state.filter.slice(0, -1) } };
|
|
356
|
+
}
|
|
357
|
+
if (!key.ctrl && !key.meta && key.name.length === 1) {
|
|
358
|
+
return { kind: "state", state: { cursor: 0, filter: state.filter + key.name } };
|
|
359
|
+
}
|
|
360
|
+
return { kind: "noop" };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/multiSelectReducer.ts
|
|
364
|
+
function reduce3(items, state, key) {
|
|
365
|
+
const n = items.length;
|
|
366
|
+
if (key.name === "escape") return { kind: "cancel" };
|
|
367
|
+
if (key.name === "return") return { kind: "submit" };
|
|
368
|
+
if (n === 0) return { kind: "noop" };
|
|
369
|
+
if (key.name === "down" || key.name === "j" && !key.ctrl && !key.meta) {
|
|
370
|
+
return { kind: "state", state: { cursor: (state.cursor + 1) % n } };
|
|
371
|
+
}
|
|
372
|
+
if (key.name === "up" || key.name === "k" && !key.ctrl && !key.meta) {
|
|
373
|
+
return { kind: "state", state: { cursor: (state.cursor - 1 + n) % n } };
|
|
374
|
+
}
|
|
375
|
+
if (key.name === " ") {
|
|
376
|
+
const index = Math.max(0, Math.min(state.cursor, n - 1));
|
|
377
|
+
return { kind: "toggle", index };
|
|
378
|
+
}
|
|
379
|
+
return { kind: "noop" };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export { charWidth, reduce as editorReducer, reduce3 as multiSelectReducer, reduce2 as selectReducer, splitVisualLines, stringWidth, visibleIndices, windowAround };
|