@textui/widgets 0.1.0 → 0.2.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 +17 -15
- package/dist/control/checkbox.d.ts +2 -0
- package/dist/control/checkbox.d.ts.map +1 -1
- package/dist/control/checkbox.js +2 -2
- package/dist/control/radio-group.d.ts +2 -0
- package/dist/control/radio-group.d.ts.map +1 -1
- package/dist/control/radio-group.js +2 -2
- package/dist/control/text-area.d.ts +15 -2
- package/dist/control/text-area.d.ts.map +1 -1
- package/dist/control/text-area.js +402 -22
- package/dist/control/text-input.d.ts.map +1 -1
- package/dist/control/text-input.js +40 -0
- package/dist/data/feed.d.ts +14 -0
- package/dist/data/feed.d.ts.map +1 -1
- package/dist/data/feed.js +28 -2
- package/dist/data/list.d.ts +60 -7
- package/dist/data/list.d.ts.map +1 -1
- package/dist/data/list.js +66 -10
- package/dist/data/markdown-view.d.ts.map +1 -1
- package/dist/data/markdown-view.js +49 -0
- package/dist/display/color-text.d.ts +158 -0
- package/dist/display/color-text.d.ts.map +1 -0
- package/dist/display/color-text.js +246 -0
- package/dist/display/index.d.ts +1 -0
- package/dist/display/index.d.ts.map +1 -1
- package/dist/display/index.js +3 -0
- package/dist/layout/divider.d.ts +6 -1
- package/dist/layout/divider.d.ts.map +1 -1
- package/dist/layout/divider.js +5 -5
- package/dist/layout/scroll-view.d.ts +10 -0
- package/dist/layout/scroll-view.d.ts.map +1 -1
- package/dist/layout/scroll-view.js +2 -2
- package/dist/navigation/menu.d.ts +24 -0
- package/dist/navigation/menu.d.ts.map +1 -1
- package/dist/navigation/menu.js +58 -15
- package/dist/overlay/command-palette.d.ts +32 -1
- package/dist/overlay/command-palette.d.ts.map +1 -1
- package/dist/overlay/command-palette.js +84 -10
- package/dist/shells/workbench-shell.d.ts.map +1 -1
- package/dist/shells/workbench-shell.js +16 -2
- package/package.json +8 -8
- package/src/control/checkbox.ts +4 -2
- package/src/control/radio-group.ts +4 -2
- package/src/control/text-area.ts +394 -31
- package/src/control/text-input.ts +38 -1
- package/src/data/feed.ts +44 -1
- package/src/data/list.ts +125 -18
- package/src/data/markdown-view.ts +56 -0
- package/src/display/color-text.ts +386 -0
- package/src/display/index.ts +3 -0
- package/src/layout/divider.ts +15 -7
- package/src/layout/scroll-view.ts +12 -2
- package/src/navigation/menu.ts +89 -14
- package/src/overlay/command-palette.ts +125 -11
- package/src/shells/workbench-shell.ts +16 -3
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { attrsFromStyle, defineComponent, flattenStyleInput, graphemeWidth, graphemes, h, mergeStyles, mix, packColor, resolveColor, sanitize, stringWidth, styleFromProps, truncate, truncateSideOf, unpackColor, useMeasure, wrapModeOf, wrapText, } from '@textui/core';
|
|
2
|
+
/**
|
|
3
|
+
* Multiline text coloured cell by cell.
|
|
4
|
+
*
|
|
5
|
+
* A `text` takes one colour for the whole run, which is the right answer for
|
|
6
|
+
* nearly everything and no answer at all for the cases where the colour *is*
|
|
7
|
+
* the content - a banner, a ramp across a title, a palette walked down a
|
|
8
|
+
* block of ascii art. This is that case, and only that case: it is still just
|
|
9
|
+
* text, and the alternative to it is a `text` node.
|
|
10
|
+
*
|
|
11
|
+
* It paints on a `canvas`, and the price of the escape hatch is that inherited
|
|
12
|
+
* colour stops here. A cell the ink declines takes this component's own `fg`
|
|
13
|
+
* rather than the one a parent row would have handed a `text`, so a
|
|
14
|
+
* `ColorText` inside something that recolours its children when selected has
|
|
15
|
+
* to be told about it. Everything an ink does paint is unaffected.
|
|
16
|
+
*
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <ColorText ink={{ gradient: ['cyan', 'magenta'] }}>{banner}</ColorText>
|
|
19
|
+
* <ColorText ink={['danger', 'warning', 'success']}>{lines}</ColorText>
|
|
20
|
+
* <ColorText ink={{ cycle: palette, every: [4, 3] }}>{title}</ColorText>
|
|
21
|
+
* <ColorText ink={(cell) => (cell.line === cell.col ? 'accent' : undefined)}>{grid}</ColorText>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* The colour is decoration and never the message: a 16-colour session flattens
|
|
25
|
+
* a three-stop ramp into a couple of bands, and a piped log loses all of it.
|
|
26
|
+
*/
|
|
27
|
+
export const ColorText = defineComponent('ColorText', (props) => {
|
|
28
|
+
const { content, children, ink, ellipsis, alignBlock, ...rest } = props;
|
|
29
|
+
const source = sanitize(textOf(content, children));
|
|
30
|
+
const wrap = (props.wrap ?? 'none');
|
|
31
|
+
// The width layout settled on last frame, for the height this frame asks
|
|
32
|
+
// for. Painting uses the surface's own width instead - that one is current -
|
|
33
|
+
// so the lag here costs a line count and never a wrong line break.
|
|
34
|
+
const measured = useMeasure().width;
|
|
35
|
+
const natural = source.split('\n').reduce((w, line) => Math.max(w, stringWidth(line)), 0);
|
|
36
|
+
const height = linesOf(source, wrap, measured > 0 ? measured : natural).length;
|
|
37
|
+
// A block that does not wrap is as wide as its widest line and says so -
|
|
38
|
+
// that is what a banner is. One that wraps or truncates asks for nothing and
|
|
39
|
+
// takes what it is given, because a paragraph that reported the width of its
|
|
40
|
+
// unbroken text would push every sibling off the row to get it.
|
|
41
|
+
const width = wrap === 'none' ? natural : 0;
|
|
42
|
+
const draw = (surface, ctx) => {
|
|
43
|
+
const { width: area, height: rows } = surface.rect;
|
|
44
|
+
if (area <= 0 || rows <= 0 || source === '')
|
|
45
|
+
return;
|
|
46
|
+
// The component's own style, resolved for the state it is in. Not the
|
|
47
|
+
// inherited one - a canvas is not told what it was nested in.
|
|
48
|
+
const own = mergeStyles(styleFromProps(props), flattenStyleInput(props.style, stateOf(ctx)));
|
|
49
|
+
const rest = {
|
|
50
|
+
fg: resolveColor(own.fg, ctx.theme, 'default'),
|
|
51
|
+
attrs: attrsFromStyle(own),
|
|
52
|
+
link: typeof props.link === 'string' ? props.link : undefined,
|
|
53
|
+
};
|
|
54
|
+
const cut = props.truncate ?? truncateSideOf(wrap) ?? 'end';
|
|
55
|
+
const dots = ellipsis ?? ctx.theme.glyphs.ellipsis;
|
|
56
|
+
const lines = linesOf(source, wrap, area).map((line) => (stringWidth(line) > area && cut !== false ? truncate(line, area, dots, cut) : line));
|
|
57
|
+
// After truncating, not before: the width the block is placed by is the
|
|
58
|
+
// width it is going to occupy.
|
|
59
|
+
const blockWidth = lines.reduce((w, line) => Math.max(w, stringWidth(line)), 0);
|
|
60
|
+
const paint = painterOf(ink, ctx);
|
|
61
|
+
const align = own.textAlign ?? 'left';
|
|
62
|
+
const origin = alignBlock ? indentFor(align, area, blockWidth) : undefined;
|
|
63
|
+
let offset = 0;
|
|
64
|
+
for (let y = 0; y < lines.length && y < rows; y++) {
|
|
65
|
+
const line = lines[y];
|
|
66
|
+
const lineWidth = stringWidth(line);
|
|
67
|
+
const indent = origin ?? indentFor(align, area, lineWidth);
|
|
68
|
+
let col = 0;
|
|
69
|
+
let index = 0;
|
|
70
|
+
for (const char of graphemes(line)) {
|
|
71
|
+
const advance = graphemeWidth(char);
|
|
72
|
+
if (advance === 0)
|
|
73
|
+
continue;
|
|
74
|
+
const style = paint({
|
|
75
|
+
char, col, line: y, index, offset, width: lineWidth, height: lines.length, blockWidth,
|
|
76
|
+
});
|
|
77
|
+
// A space with nothing but a foreground has nothing to show, and
|
|
78
|
+
// painting it would wipe out whatever the box behind it drew there.
|
|
79
|
+
if (char !== ' ' || style?.bg !== undefined || rest.bg !== undefined) {
|
|
80
|
+
surface.put(indent + col, y, char, style ? { ...rest, ...style } : rest);
|
|
81
|
+
}
|
|
82
|
+
col += advance;
|
|
83
|
+
index++;
|
|
84
|
+
offset++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
return h('canvas', { draw, intrinsic: { width, height }, ...rest });
|
|
89
|
+
});
|
|
90
|
+
// ------------------------------------------------------------------- inks
|
|
91
|
+
/**
|
|
92
|
+
* A colour a fraction of the way along a set of stops.
|
|
93
|
+
*
|
|
94
|
+
* Exported because an ink written by hand wants it and the alternative is
|
|
95
|
+
* unpacking colours in application code. `t` outside 0..1 is clamped rather
|
|
96
|
+
* than extrapolated - a ramp has ends.
|
|
97
|
+
*/
|
|
98
|
+
export function gradientAt(stops, t) {
|
|
99
|
+
if (stops.length === 0)
|
|
100
|
+
return 'default';
|
|
101
|
+
if (stops.length === 1)
|
|
102
|
+
return stops[0];
|
|
103
|
+
const at = Math.min(1, Math.max(0, t)) * (stops.length - 1);
|
|
104
|
+
const i = Math.min(stops.length - 2, Math.floor(at));
|
|
105
|
+
return blend(stops[i], stops[i + 1], at - i);
|
|
106
|
+
}
|
|
107
|
+
/** Two colours mixed. `t` of 0 is `a`, 1 is `b`. Tokens must be resolved first. */
|
|
108
|
+
export function blend(a, b, t) {
|
|
109
|
+
return unpackColor(mix(packColor(a), packColor(b), Math.min(1, Math.max(0, t))));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* An ink in any of its spellings, as the one function paint uses.
|
|
113
|
+
*
|
|
114
|
+
* Exported so a component that paints its own cells - a chart, a viewer with a
|
|
115
|
+
* heat column - can take an `Ink` and mean the same thing by it.
|
|
116
|
+
*/
|
|
117
|
+
export function painterOf(ink, ctx) {
|
|
118
|
+
if (ink === undefined)
|
|
119
|
+
return () => undefined;
|
|
120
|
+
if (typeof ink === 'function') {
|
|
121
|
+
return (cell) => styleOf(ink(cell, ctx), ctx);
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(ink)) {
|
|
124
|
+
return painterOf({ cycle: ink, unit: 'line' }, ctx);
|
|
125
|
+
}
|
|
126
|
+
if ('gradient' in ink) {
|
|
127
|
+
const stops = ink.gradient.map((c) => resolveColor(c, ctx.theme, 'default'));
|
|
128
|
+
const axis = ink.axis ?? 'x';
|
|
129
|
+
const per = ink.per ?? 'block';
|
|
130
|
+
return (cell) => {
|
|
131
|
+
const span = per === 'line' ? cell.width : cell.blockWidth;
|
|
132
|
+
// A single column or a single line has no distance to ramp over, and
|
|
133
|
+
// dividing by zero would put every cell at the far end of the ramp.
|
|
134
|
+
const x = span > 1 ? cell.col / (span - 1) : 0;
|
|
135
|
+
const y = cell.height > 1 ? cell.line / (cell.height - 1) : 0;
|
|
136
|
+
const t = axis === 'x' ? x : axis === 'y' ? y : (x + y) / 2;
|
|
137
|
+
return { fg: gradientAt(stops, t) };
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const colors = ink.cycle.map((c) => resolveColor(c, ctx.theme, 'default'));
|
|
141
|
+
if (colors.length === 0)
|
|
142
|
+
return () => undefined;
|
|
143
|
+
const unit = ink.unit ?? 'cell';
|
|
144
|
+
const runs = (Array.isArray(ink.every) ? ink.every : [ink.every ?? 1])
|
|
145
|
+
.map((n) => Math.max(1, Math.floor(n)));
|
|
146
|
+
const continuous = ink.continuous ?? false;
|
|
147
|
+
// `letter` and `word` are counted rather than derived, because both depend
|
|
148
|
+
// on what came before them on the line and neither can be recovered from a
|
|
149
|
+
// column number. Kept per line so the run restarts where the line does.
|
|
150
|
+
let letters = 0;
|
|
151
|
+
let words = 0;
|
|
152
|
+
let wasBlank = true;
|
|
153
|
+
let at = -1;
|
|
154
|
+
return (cell) => {
|
|
155
|
+
if (cell.line !== at) {
|
|
156
|
+
at = cell.line;
|
|
157
|
+
if (!continuous) {
|
|
158
|
+
letters = 0;
|
|
159
|
+
words = 0;
|
|
160
|
+
}
|
|
161
|
+
wasBlank = true;
|
|
162
|
+
}
|
|
163
|
+
const blank = cell.char.trim() === '';
|
|
164
|
+
if (!blank && wasBlank)
|
|
165
|
+
words++;
|
|
166
|
+
if (!blank)
|
|
167
|
+
letters++;
|
|
168
|
+
wasBlank = blank;
|
|
169
|
+
const n = unit === 'cell' ? (continuous ? cell.offset : cell.col)
|
|
170
|
+
: unit === 'grapheme' ? (continuous ? cell.offset : cell.index)
|
|
171
|
+
: unit === 'letter' ? Math.max(0, letters - 1)
|
|
172
|
+
: unit === 'word' ? Math.max(0, words - 1)
|
|
173
|
+
: cell.line;
|
|
174
|
+
return { fg: colors[runIndex(n, runs) % colors.length] };
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** Which run of a repeating pattern a count falls in. `[4, 3]` over 8 gives 2. */
|
|
178
|
+
function runIndex(n, runs) {
|
|
179
|
+
const total = runs.reduce((a, b) => a + b, 0);
|
|
180
|
+
const cycles = Math.floor(n / total);
|
|
181
|
+
let rest = n % total;
|
|
182
|
+
let i = 0;
|
|
183
|
+
while (rest >= runs[i]) {
|
|
184
|
+
rest -= runs[i];
|
|
185
|
+
i++;
|
|
186
|
+
}
|
|
187
|
+
return cycles * runs.length + i;
|
|
188
|
+
}
|
|
189
|
+
/** A colour, a style, or nothing - as a style, with its tokens resolved. */
|
|
190
|
+
function styleOf(value, ctx) {
|
|
191
|
+
if (value === undefined)
|
|
192
|
+
return undefined;
|
|
193
|
+
if (typeof value === 'string')
|
|
194
|
+
return { fg: resolveColor(value, ctx.theme, 'default') };
|
|
195
|
+
if ('rgb' in value || 'palette' in value)
|
|
196
|
+
return { fg: value };
|
|
197
|
+
const style = value;
|
|
198
|
+
return {
|
|
199
|
+
...style,
|
|
200
|
+
fg: style.fg === undefined ? undefined : resolveColor(style.fg, ctx.theme, 'default'),
|
|
201
|
+
bg: style.bg === undefined ? undefined : resolveColor(style.bg, ctx.theme, 'default'),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
// ------------------------------------------------------------------ text
|
|
205
|
+
function textOf(content, children) {
|
|
206
|
+
if (typeof content === 'string')
|
|
207
|
+
return content;
|
|
208
|
+
if (typeof content === 'number')
|
|
209
|
+
return String(content);
|
|
210
|
+
if (typeof children === 'string')
|
|
211
|
+
return children;
|
|
212
|
+
if (typeof children === 'number')
|
|
213
|
+
return String(children);
|
|
214
|
+
if (Array.isArray(children)) {
|
|
215
|
+
return children.filter((c) => typeof c === 'string' || typeof c === 'number').join('');
|
|
216
|
+
}
|
|
217
|
+
return '';
|
|
218
|
+
}
|
|
219
|
+
/** Where a line of `width` starts, in a box of `area`. */
|
|
220
|
+
function indentFor(align, area, width) {
|
|
221
|
+
if (align === 'center')
|
|
222
|
+
return Math.max(0, Math.floor((area - width) / 2));
|
|
223
|
+
if (align === 'right')
|
|
224
|
+
return Math.max(0, area - width);
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
/** The same line-breaking `text` does, so the two agree about where a line ends. */
|
|
228
|
+
function linesOf(text, wrap, width) {
|
|
229
|
+
if (text === '')
|
|
230
|
+
return [];
|
|
231
|
+
// The truncating modes are one line by definition; a newline inside one has
|
|
232
|
+
// nowhere to go, so it becomes a space rather than silently taking the rest
|
|
233
|
+
// of the text with it.
|
|
234
|
+
if (truncateSideOf(wrap) !== undefined)
|
|
235
|
+
return [text.replace(/\n/g, ' ')];
|
|
236
|
+
const mode = wrapModeOf(wrap);
|
|
237
|
+
if (mode === 'none' || width <= 0)
|
|
238
|
+
return text.split('\n');
|
|
239
|
+
return wrapText(text, width, mode);
|
|
240
|
+
}
|
|
241
|
+
function stateOf(ctx) {
|
|
242
|
+
return {
|
|
243
|
+
focused: ctx.focused, hovered: ctx.hovered, active: ctx.active,
|
|
244
|
+
selected: ctx.selected, disabled: ctx.disabled,
|
|
245
|
+
};
|
|
246
|
+
}
|
package/dist/display/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { ComponentDefinition } from '@textui/core';
|
|
|
10
10
|
export * from './alert.js';
|
|
11
11
|
export * from './badge.js';
|
|
12
12
|
export * from './card.js';
|
|
13
|
+
export * from './color-text.js';
|
|
13
14
|
export * from './empty-state.js';
|
|
14
15
|
export * from './error-state.js';
|
|
15
16
|
export * from './heading.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/display/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/display/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAiBxD;;;;;;;GAOG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAE9B,eAAO,MAAM,kBAAkB,EAAE,mBAAmB,EAgBnD,CAAC"}
|
package/dist/display/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Alert } from './alert.js';
|
|
2
2
|
import { Badge } from './badge.js';
|
|
3
3
|
import { Card } from './card.js';
|
|
4
|
+
import { ColorText } from './color-text.js';
|
|
4
5
|
import { EmptyState } from './empty-state.js';
|
|
5
6
|
import { ErrorState } from './error-state.js';
|
|
6
7
|
import { Heading } from './heading.js';
|
|
@@ -23,6 +24,7 @@ import { Timeline } from './timeline.js';
|
|
|
23
24
|
export * from './alert.js';
|
|
24
25
|
export * from './badge.js';
|
|
25
26
|
export * from './card.js';
|
|
27
|
+
export * from './color-text.js';
|
|
26
28
|
export * from './empty-state.js';
|
|
27
29
|
export * from './error-state.js';
|
|
28
30
|
export * from './heading.js';
|
|
@@ -44,6 +46,7 @@ export const DISPLAY_COMPONENTS = [
|
|
|
44
46
|
{ component: 'KeyValue', category: 'data', renderer: { kind: 'function', render: KeyValue }, description: 'Aligned label/value pairs.' },
|
|
45
47
|
{ component: 'Progress', category: 'feedback', renderer: { kind: 'function', render: Progress }, role: 'progressbar', description: 'Determinate or indeterminate bar, sub-cell resolution.' },
|
|
46
48
|
{ component: 'Spinner', category: 'feedback', renderer: { kind: 'function', render: Spinner }, role: 'status', description: 'Animated activity indicator.' },
|
|
49
|
+
{ component: 'ColorText', category: 'display', renderer: { kind: 'function', render: ColorText }, description: 'Multiline text coloured cell by cell - a ramp, a palette per line, or a function.' },
|
|
47
50
|
{ component: 'Marquee', category: 'display', renderer: { kind: 'function', render: Marquee }, role: 'marquee', description: 'Text too long for its box, read by sliding it while it has the cursor.' },
|
|
48
51
|
{ component: 'Skeleton', category: 'feedback', renderer: { kind: 'function', render: Skeleton }, description: 'Loading placeholder.' },
|
|
49
52
|
{ component: 'EmptyState', category: 'feedback', renderer: { kind: 'function', render: EmptyState }, description: 'Nothing here, and what to do about it.' },
|
package/dist/layout/divider.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import type { BoxProps } from '@textui/core';
|
|
1
|
+
import type { BoxProps, DividerStyle } from '@textui/core';
|
|
2
2
|
export interface DividerProps extends Omit<BoxProps, 'direction'> {
|
|
3
3
|
/** A divider runs across the flow, so it names its own axis. */
|
|
4
4
|
direction?: 'horizontal' | 'vertical';
|
|
5
5
|
/** Text set into the rule. */
|
|
6
6
|
label?: string;
|
|
7
7
|
labelAlign?: 'left' | 'center' | 'right';
|
|
8
|
+
/**
|
|
9
|
+
* The rule style. The theme's own is the default, so a borderless theme
|
|
10
|
+
* still gets the line it asked for.
|
|
11
|
+
*/
|
|
12
|
+
rule?: DividerStyle;
|
|
8
13
|
char?: string;
|
|
9
14
|
}
|
|
10
15
|
export declare const Divider: (props: DividerProps) => import("@textui/core").RenderOutput;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"divider.d.ts","sourceRoot":"","sources":["../../src/layout/divider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"divider.d.ts","sourceRoot":"","sources":["../../src/layout/divider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG3D,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/D,gEAAgE;IAChE,SAAS,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACtC,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACzC;;;OAGG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,OAAO,8DAqBlB,CAAC"}
|
package/dist/layout/divider.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { defineComponent, h, useTheme } from '@textui/core';
|
|
2
2
|
export const Divider = defineComponent('Divider', (props) => {
|
|
3
3
|
const theme = useTheme();
|
|
4
|
-
const { direction = 'horizontal', label, labelAlign = 'left', char, ...rest } = props;
|
|
5
|
-
const
|
|
4
|
+
const { direction = 'horizontal', label, labelAlign = 'left', char, rule: ruleStyle, fg = 'divider', ...rest } = props;
|
|
5
|
+
const rule = theme.dividerChars(ruleStyle);
|
|
6
6
|
if (direction === 'vertical') {
|
|
7
|
-
return h('box', { role: 'separator', width: 1, fill: char ??
|
|
7
|
+
return h('box', { role: 'separator', width: 1, fill: char ?? rule.vertical, fg, ...rest });
|
|
8
8
|
}
|
|
9
9
|
if (!label) {
|
|
10
|
-
return h('box', { role: 'separator', height: 1, fill: char ??
|
|
10
|
+
return h('box', { role: 'separator', height: 1, fill: char ?? rule.horizontal, fg, ...rest });
|
|
11
11
|
}
|
|
12
|
-
return h('box', { direction: 'row', gap: 1, height: 1, ...rest }, labelAlign !== 'left' ? h('box', { flex: 1, fill: char ??
|
|
12
|
+
return h('box', { direction: 'row', gap: 1, height: 1, ...rest }, labelAlign !== 'left' ? h('box', { flex: 1, fill: char ?? rule.horizontal, fg }) : null, h('text', { content: label, fg: 'muted' }), labelAlign !== 'right' ? h('box', { flex: 1, fill: char ?? rule.horizontal, fg }) : null);
|
|
13
13
|
});
|
|
@@ -15,6 +15,16 @@ export interface ScrollViewProps extends BoxProps {
|
|
|
15
15
|
*/
|
|
16
16
|
focusable?: boolean;
|
|
17
17
|
autoFocus?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* A stable focus id, so a command - or the screen that owns this - can put
|
|
20
|
+
* the reader here by name.
|
|
21
|
+
*
|
|
22
|
+
* Without one the id comes from the instance, which nothing outside the
|
|
23
|
+
* render can know: "scroll the preview" has nothing to name and the key that
|
|
24
|
+
* would do it cannot be written. Every other focusable control takes one;
|
|
25
|
+
* this was the exception, and there was no reason for it.
|
|
26
|
+
*/
|
|
27
|
+
focusId?: string;
|
|
18
28
|
}
|
|
19
29
|
/**
|
|
20
30
|
* A scrolling viewport.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-view.d.ts","sourceRoot":"","sources":["../../src/layout/scroll-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAa7C,MAAM,WAAW,eAAgB,SAAQ,QAAQ;IAC/C,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"scroll-view.d.ts","sourceRoot":"","sources":["../../src/layout/scroll-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAa7C,MAAM,WAAW,eAAgB,SAAQ,QAAQ;IAC/C,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,iEA4ErB,CAAC"}
|
|
@@ -8,8 +8,8 @@ import { ScrollThumb } from '../data/scroll-thumb.js';
|
|
|
8
8
|
* rounds.
|
|
9
9
|
*/
|
|
10
10
|
export const ScrollView = defineComponent('ScrollView', (props) => {
|
|
11
|
-
const { offset, onScroll, scrollbar = true, focusable = true, autoFocus, id, children, ...rest } = props;
|
|
12
|
-
const focus = useFocus({ disabled: !focusable, autoFocus });
|
|
11
|
+
const { offset, onScroll, scrollbar = true, focusable = true, autoFocus, focusId, id, children, ...rest } = props;
|
|
12
|
+
const focus = useFocus({ ...(focusId ? { id: focusId } : {}), disabled: !focusable, autoFocus });
|
|
13
13
|
const [internal, setInternal] = useState(0);
|
|
14
14
|
const top = offset ?? internal;
|
|
15
15
|
// How far down this can go before the last line leaves the bottom. Written
|
|
@@ -9,6 +9,18 @@ export interface MenuItem {
|
|
|
9
9
|
disabled?: boolean;
|
|
10
10
|
tone?: SemanticVariant;
|
|
11
11
|
separatorBefore?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* A heading on the line above this row, naming the group it starts.
|
|
14
|
+
*
|
|
15
|
+
* The name of a group belongs to the group, so it is said once at the top
|
|
16
|
+
* of it rather than repeated on every row - a column reading "Screens,
|
|
17
|
+
* Screens, Screens" spends the width that the rows themselves need, and
|
|
18
|
+
* still does not say where one group ends.
|
|
19
|
+
*
|
|
20
|
+
* It takes the line a `separatorBefore` would have used rather than adding
|
|
21
|
+
* one, so a grouped menu is the same height either way.
|
|
22
|
+
*/
|
|
23
|
+
sectionBefore?: string;
|
|
12
24
|
/**
|
|
13
25
|
* A switch, and whether it is on. Absent means the row is not a switch, so
|
|
14
26
|
* a menu of ordinary commands keeps its left edge rather than indenting
|
|
@@ -24,6 +36,18 @@ export interface MenuProps extends BoxProps {
|
|
|
24
36
|
visibleRows?: number;
|
|
25
37
|
activeId?: string;
|
|
26
38
|
autoFocus?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Where a row's description goes.
|
|
41
|
+
*
|
|
42
|
+
* `inline` right-aligns it on the row, sharing the width with the label -
|
|
43
|
+
* which is the right shape for a word or two of state. `below` gives it a
|
|
44
|
+
* line of its own under the label, indented to it, which is the only shape
|
|
45
|
+
* that fits a sentence: inline, a list of modes whose whole difference is
|
|
46
|
+
* the sentence under each shows the same truncated half of every one.
|
|
47
|
+
*
|
|
48
|
+
* `below` makes every row two lines, so `visibleRows` buys half as much.
|
|
49
|
+
*/
|
|
50
|
+
descriptions?: 'inline' | 'below';
|
|
27
51
|
/**
|
|
28
52
|
* Take focus and handle keys. Off when something else drives the selection -
|
|
29
53
|
* a command palette, where typing belongs to the search field and the list
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../src/navigation/menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAK9D,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,SAAU,SAAQ,QAAQ;IACzC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5C,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../src/navigation/menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAK9D,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,SAAU,SAAQ,QAAQ;IACzC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5C,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,IAAI,2DAkIf,CAAC"}
|
package/dist/navigation/menu.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { defineComponent, h, useFocus, useInput, useState, useTheme } from '@textui/core';
|
|
1
|
+
import { defineComponent, h, stringWidth, useFocus, useInput, useState, useTheme } from '@textui/core';
|
|
2
2
|
import { Marquee } from '../display/index.js';
|
|
3
3
|
import { TONE } from '../tone.js';
|
|
4
4
|
export const Menu = defineComponent('Menu', (props) => {
|
|
5
5
|
const theme = useTheme();
|
|
6
|
-
const { items, onSelect, visibleRows, activeId, autoFocus, interactive = true, ...rest } = props;
|
|
6
|
+
const { items, onSelect, visibleRows, activeId, autoFocus, interactive = true, descriptions = 'inline', ...rest } = props;
|
|
7
7
|
const focus = useFocus({ autoFocus, disabled: !interactive });
|
|
8
8
|
const selectable = items.filter((i) => !i.disabled);
|
|
9
9
|
const [internalHighlight, setHighlight] = useState(Math.max(0, items.findIndex((i) => i.id === activeId)));
|
|
@@ -43,18 +43,8 @@ export const Menu = defineComponent('Menu', (props) => {
|
|
|
43
43
|
const switches = items.some((item) => item.checked !== undefined);
|
|
44
44
|
return h('box', { id: focus.id, role: 'menu', direction: 'column', ...rest }, ...window.flatMap((item, i) => {
|
|
45
45
|
const active = start + i === highlight;
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
role: 'menuitem',
|
|
49
|
-
label: item.label,
|
|
50
|
-
selected: active,
|
|
51
|
-
direction: 'row',
|
|
52
|
-
gap: 1,
|
|
53
|
-
bg: active ? 'selected' : undefined,
|
|
54
|
-
fg: item.disabled ? 'disabled' : active ? 'inverted' : item.tone ? TONE[item.tone] : undefined,
|
|
55
|
-
onClick: () => { if (!item.disabled)
|
|
56
|
-
onSelect?.(item.id, item); },
|
|
57
|
-
}, h('text', { content: active ? theme.glyphs.chevronRight : ' ', shrink: 0 }), switches
|
|
46
|
+
const below = descriptions === 'below' && item.description !== undefined;
|
|
47
|
+
const head = h('box', { direction: 'row', gap: 1 }, h('text', { content: active ? theme.glyphs.chevronRight : ' ', shrink: 0 }), switches
|
|
58
48
|
? h('text', { content: item.checked === true ? theme.glyphs.check : ' ', shrink: 0 })
|
|
59
49
|
: null,
|
|
60
50
|
// The mark never gives up room. It is the shortest thing on the row
|
|
@@ -67,7 +57,7 @@ export const Menu = defineComponent('Menu', (props) => {
|
|
|
67
57
|
// The description yields first, and by a lot. It is the elaboration;
|
|
68
58
|
// the label is the thing being chosen, and a row reading "Accept ed…"
|
|
69
59
|
// beside a full sentence has given up the wrong half.
|
|
70
|
-
item.description
|
|
60
|
+
item.description && !below
|
|
71
61
|
? h(Marquee, {
|
|
72
62
|
content: item.description,
|
|
73
63
|
active,
|
|
@@ -79,6 +69,43 @@ export const Menu = defineComponent('Menu', (props) => {
|
|
|
79
69
|
// which is what the command palette knows about an argument whose
|
|
80
70
|
// choices it has not resolved yet.
|
|
81
71
|
item.children ? h('text', { content: theme.glyphs.chevronRight }) : null);
|
|
72
|
+
const row = h('box', {
|
|
73
|
+
key: item.id,
|
|
74
|
+
role: 'menuitem',
|
|
75
|
+
label: item.label,
|
|
76
|
+
selected: active,
|
|
77
|
+
direction: 'column',
|
|
78
|
+
// One background over both lines: a highlight that stopped after the
|
|
79
|
+
// label would split the row it is highlighting in two.
|
|
80
|
+
bg: active ? 'selected' : undefined,
|
|
81
|
+
fg: item.disabled ? 'disabled' : active ? 'inverted' : item.tone ? TONE[item.tone] : undefined,
|
|
82
|
+
onClick: () => { if (!item.disabled)
|
|
83
|
+
onSelect?.(item.id, item); },
|
|
84
|
+
}, head,
|
|
85
|
+
// Under the label rather than under the cursor: the sentence is about
|
|
86
|
+
// the thing being chosen, so it starts where that thing starts.
|
|
87
|
+
below
|
|
88
|
+
? h('box', { direction: 'row' }, h('text', { content: ' '.repeat(leading(item, switches)), shrink: 0 }),
|
|
89
|
+
// A `Marquee`, like the inline one: a sentence too long for the
|
|
90
|
+
// panel is still readable on the row the cursor is on, by sliding
|
|
91
|
+
// it. Truncated and still is right for the rows being scanned past
|
|
92
|
+
// and useless for the one that has been stopped on.
|
|
93
|
+
h(Marquee, {
|
|
94
|
+
content: item.description,
|
|
95
|
+
active,
|
|
96
|
+
fg: active ? 'inverted' : 'muted',
|
|
97
|
+
flex: 1,
|
|
98
|
+
}))
|
|
99
|
+
: null);
|
|
100
|
+
if (item.sectionBefore) {
|
|
101
|
+
return [
|
|
102
|
+
h('box', { key: `${item.id}-sec`, role: 'heading', direction: 'row', gap: 1 },
|
|
103
|
+
// The same leading columns the rows have, so a heading sits over
|
|
104
|
+
// the labels it names rather than over the cursor's gutter.
|
|
105
|
+
h('text', { content: ' ', shrink: 0 }), switches ? h('text', { content: ' ', shrink: 0 }) : null, h('text', { content: item.sectionBefore, bold: true, fg: 'muted', truncate: 'end' })),
|
|
106
|
+
row,
|
|
107
|
+
];
|
|
108
|
+
}
|
|
82
109
|
return item.separatorBefore
|
|
83
110
|
? [h('box', { key: `${item.id}-sep`, height: 1, fill: theme.borderChars().top, fg: 'borderSubtle' }), row]
|
|
84
111
|
: [row];
|
|
@@ -86,3 +113,19 @@ export const Menu = defineComponent('Menu', (props) => {
|
|
|
86
113
|
? h('text', { content: ' no matches', fg: 'subtle' })
|
|
87
114
|
: null);
|
|
88
115
|
});
|
|
116
|
+
/**
|
|
117
|
+
* The columns before a row's label, so a second line can start under it.
|
|
118
|
+
*
|
|
119
|
+
* The cursor's column and the switch's are the same on every row - that is
|
|
120
|
+
* what keeps a menu's left edge straight - but the icon is per-row and may be
|
|
121
|
+
* two cells wide, so it has to be measured rather than assumed.
|
|
122
|
+
*/
|
|
123
|
+
function leading(item, switches) {
|
|
124
|
+
// The marker, plus the gap after it.
|
|
125
|
+
let width = 2;
|
|
126
|
+
if (switches)
|
|
127
|
+
width += 2;
|
|
128
|
+
if (item.icon)
|
|
129
|
+
width += stringWidth(item.icon) + 1;
|
|
130
|
+
return width;
|
|
131
|
+
}
|
|
@@ -14,10 +14,41 @@ export interface CommandPaletteProps extends BoxProps {
|
|
|
14
14
|
onClose?(): void;
|
|
15
15
|
/** Off makes this a picker: it reports the choice and runs nothing. */
|
|
16
16
|
execute?: boolean;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Group the list by `category`, with the category named above each group.
|
|
19
|
+
*
|
|
20
|
+
* Only while nothing is typed. A query sorts by relevance, which interleaves
|
|
21
|
+
* the categories - and a heading over one row is not a group.
|
|
22
|
+
*/
|
|
18
23
|
grouped?: boolean;
|
|
19
24
|
visibleRows?: number;
|
|
25
|
+
/**
|
|
26
|
+
* A fixed width, in cells.
|
|
27
|
+
*
|
|
28
|
+
* Left off, the panel is as wide as its widest row and no wider than
|
|
29
|
+
* `maxWidth` - which is what a list of five short answers wants, and what a
|
|
30
|
+
* list of five sentences needs. A number here is a number: the panel is that
|
|
31
|
+
* wide whether the rows fill it or overflow it.
|
|
32
|
+
*/
|
|
20
33
|
width?: number;
|
|
34
|
+
/**
|
|
35
|
+
* The widest the panel may grow when `width` is left off. 60 by default.
|
|
36
|
+
*
|
|
37
|
+
* There is always a limit: a description is prose, and prose has no width it
|
|
38
|
+
* stops at. Past this the rows truncate, and the row under the cursor slides
|
|
39
|
+
* what it truncated.
|
|
40
|
+
*/
|
|
41
|
+
maxWidth?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Where a row's description goes. `inline` right-aligns it beside the label;
|
|
44
|
+
* `below` gives it a line of its own.
|
|
45
|
+
*
|
|
46
|
+
* `below` for a question whose answers differ by a sentence rather than by a
|
|
47
|
+
* word - four approval modes named in two words each are told apart by the
|
|
48
|
+
* line under them, and inline that line is the half that gets truncated.
|
|
49
|
+
* Every row costs two lines, so `visibleRows` buys half as many.
|
|
50
|
+
*/
|
|
51
|
+
descriptions?: 'inline' | 'below';
|
|
21
52
|
/**
|
|
22
53
|
* Open already drilled into this command's choices.
|
|
23
54
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-palette.d.ts","sourceRoot":"","sources":["../../src/overlay/command-palette.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAa,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"command-palette.d.ts","sourceRoot":"","sources":["../../src/overlay/command-palette.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAa,MAAM,cAAc,CAAC;AAiB/F,MAAM,WAAW,mBAAoB,SAAQ,QAAQ;IACnD;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,iBAAiB,EAAE,GAAG,CAAC,MAAM,iBAAiB,EAAE,CAAC,CAAC;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,KAAK,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzD,OAAO,CAAC,IAAI,IAAI,CAAC;IACjB,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAClC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,qEAsZzB,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,iBAAiB,EAC1B,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACtC,OAAO,GAAG,SAAS,CAKrB;AAqBD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAehG"}
|