@textui/widgets 0.3.0 → 0.4.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/dist/control/select.d.ts.map +1 -1
- package/dist/control/select.js +10 -0
- package/dist/data/feed.d.ts.map +1 -1
- package/dist/data/feed.js +56 -8
- package/dist/data/shared.d.ts +0 -11
- package/dist/data/shared.d.ts.map +1 -1
- package/dist/data/shared.js +3 -9
- package/package.json +2 -2
- package/src/control/select.ts +10 -0
- package/src/data/feed.ts +54 -8
- package/src/data/shared.ts +3 -19
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/control/select.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAiB,YAAY,EAAE,MAAM,cAAc,CAAC;AAK1E,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC3C,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;CACxC;AAED,eAAO,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/control/select.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAiB,YAAY,EAAE,MAAM,cAAc,CAAC;AAK1E,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC3C,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;CACxC;AAED,eAAO,MAAM,MAAM,sCAiMjB,CAAC"}
|
package/dist/control/select.js
CHANGED
|
@@ -146,6 +146,16 @@ export const Select = defineComponent('Select', (props) => {
|
|
|
146
146
|
direction: 'column',
|
|
147
147
|
border: { style: theme.border, color: focus.focused ? 'focus' : 'border' },
|
|
148
148
|
padding: [0, 1],
|
|
149
|
+
/*
|
|
150
|
+
* The terminal cursor, parked at the start of the value.
|
|
151
|
+
*
|
|
152
|
+
* There is nothing to type here, so this is not a caret - it is the
|
|
153
|
+
* strongest "you are here" a terminal has, and a control that gives it up
|
|
154
|
+
* on focus is one whose only remaining signal is the colour of its border.
|
|
155
|
+
* Tabbing from a text field used to look like the focus had stayed behind
|
|
156
|
+
* in the field, because the cursor had.
|
|
157
|
+
*/
|
|
158
|
+
cursor: focus.focused ? 0 : undefined,
|
|
149
159
|
...rest,
|
|
150
160
|
}, h('box', {
|
|
151
161
|
direction: 'row',
|
package/dist/data/feed.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feed.d.ts","sourceRoot":"","sources":["../../src/data/feed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"feed.d.ts","sourceRoot":"","sources":["../../src/data/feed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAkB7C,MAAM,WAAW,SAAU,SAAQ,QAAQ;IACzC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,IAAI,2DAmRf,CAAC"}
|
package/dist/data/feed.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chorded, defineComponent, h, useFocus, useInput, useMeasure, useRef, useScrollExtent, useState, } from '@textui/core';
|
|
2
2
|
import { sizedByLayout } from '../viewport.js';
|
|
3
|
-
import { FeedEntry
|
|
3
|
+
import { FeedEntry } from './shared.js';
|
|
4
|
+
import { ScrollThumb } from './scroll-thumb.js';
|
|
4
5
|
/**
|
|
5
6
|
* A viewport over entries of any height.
|
|
6
7
|
*
|
|
@@ -225,12 +226,46 @@ export const Feed = defineComponent('Feed', (props) => {
|
|
|
225
226
|
y += height;
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
229
|
+
/*
|
|
230
|
+
* A run of skipped entries is one box, not one box each.
|
|
231
|
+
*
|
|
232
|
+
* Skipping the *contents* of an entry took the wrapping away, which was the
|
|
233
|
+
* expensive part, and left everything else: an entry standing in for itself
|
|
234
|
+
* is still a component to reconcile and still a box to measure, four times a
|
|
235
|
+
* frame, at every level of the tree above it. Six hundred entries with
|
|
236
|
+
* thirty on screen was five hundred and seventy of those, and they cost the
|
|
237
|
+
* same whether or not anything in them had changed.
|
|
238
|
+
*
|
|
239
|
+
* The window is contiguous, so there are at most two runs - everything above
|
|
240
|
+
* it and everything below - and each collapses to a single box of exactly
|
|
241
|
+
* the height its entries had. The entries that *are* drawn keep their own
|
|
242
|
+
* index as their key, so scrolling moves the boundary without disturbing the
|
|
243
|
+
* identity of anything still on screen.
|
|
244
|
+
*/
|
|
245
|
+
const drawn = [];
|
|
246
|
+
let gap = 0;
|
|
247
|
+
let gapFrom = 0;
|
|
248
|
+
const closeGap = () => {
|
|
249
|
+
if (gap <= 0)
|
|
250
|
+
return;
|
|
251
|
+
drawn.push(h('box', { key: `gap:${String(gapFrom)}`, height: gap }));
|
|
252
|
+
gap = 0;
|
|
253
|
+
};
|
|
254
|
+
for (let i = 0; i < count; i++) {
|
|
255
|
+
if (skipped[i]) {
|
|
256
|
+
if (gap === 0)
|
|
257
|
+
gapFrom = i;
|
|
258
|
+
gap += heights.current[i];
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
closeGap();
|
|
262
|
+
drawn.push(h(FeedEntry, {
|
|
263
|
+
key: i,
|
|
264
|
+
index: i,
|
|
265
|
+
onHeight: report.current,
|
|
266
|
+
}, entries[i]));
|
|
267
|
+
}
|
|
268
|
+
closeGap();
|
|
234
269
|
// Content-sized: draw everything and let the box grow. Clamping to a
|
|
235
270
|
// measurement here would clamp to how tall this happened to be last frame,
|
|
236
271
|
// which for a box sized *by* its own content is nothing at all - the entries
|
|
@@ -256,7 +291,20 @@ export const Feed = defineComponent('Feed', (props) => {
|
|
|
256
291
|
return true;
|
|
257
292
|
},
|
|
258
293
|
}, h('box', { flex: 1, direction: 'column', scrollTop: top, overflow: 'scroll' }, ...drawn),
|
|
294
|
+
// `ScrollThumb`, the one every other viewport in the library draws. This
|
|
295
|
+
// one had its own: a column filled with the border glyph, taking no offset
|
|
296
|
+
// and saying nothing about where in the conversation you were or how much
|
|
297
|
+
// of it there is. Against the panel it sits inside, it read as a second
|
|
298
|
+
// border - and a bar that cannot be told from a frame is not a bar.
|
|
299
|
+
//
|
|
259
300
|
// Only when there is somewhere to scroll. A track down the side of a feed
|
|
260
301
|
// that fits is chrome that states something untrue.
|
|
261
|
-
scrollbar && limit > 0
|
|
302
|
+
scrollbar && limit > 0
|
|
303
|
+
? h(ScrollThumb, {
|
|
304
|
+
total: total || measured.height,
|
|
305
|
+
rows: measured.height,
|
|
306
|
+
offset: top,
|
|
307
|
+
focused: focus.focused,
|
|
308
|
+
})
|
|
309
|
+
: null);
|
|
262
310
|
});
|
package/dist/data/shared.d.ts
CHANGED
|
@@ -13,17 +13,6 @@ import type { BoxProps } from '@textui/core';
|
|
|
13
13
|
*/
|
|
14
14
|
export declare const FeedEntry: (props: BoxProps & {
|
|
15
15
|
index: number;
|
|
16
|
-
/**
|
|
17
|
-
* Standing in for an entry that is not drawn, at the height it had.
|
|
18
|
-
*
|
|
19
|
-
* A stand-in must not answer the question it is an answer to. Its height is
|
|
20
|
-
* one the feed already knows - it is where it came from - and reporting it
|
|
21
|
-
* back is how a measurement gets to depend on itself: a round where a
|
|
22
|
-
* stand-in came out any shorter would teach the feed that it *was* shorter,
|
|
23
|
-
* and the next round would place the rest of them on top of each other.
|
|
24
|
-
*/
|
|
25
|
-
placeholder?: boolean;
|
|
26
16
|
onHeight(index: number, height: number): void;
|
|
27
17
|
}) => import("@textui/core").RenderOutput;
|
|
28
|
-
export declare const FeedScrollbar: (props: Record<string, never>) => import("@textui/core").RenderOutput;
|
|
29
18
|
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/data/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,SAAS;WACb,MAAM;
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/data/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,SAAS;WACb,MAAM;oBACG,MAAM,UAAU,MAAM,GAAG,IAAI;yCAW9C,CAAC"}
|
package/dist/data/shared.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent, h, useEffect, useMeasure
|
|
1
|
+
import { defineComponent, h, useEffect, useMeasure } from '@textui/core';
|
|
2
2
|
/**
|
|
3
3
|
* An entry that reports how tall it turned out to be.
|
|
4
4
|
*
|
|
@@ -12,16 +12,10 @@ import { defineComponent, h, useEffect, useMeasure, useTheme } from '@textui/cor
|
|
|
12
12
|
* along with it, however little any of them had changed.
|
|
13
13
|
*/
|
|
14
14
|
export const FeedEntry = defineComponent('FeedEntry', (props) => {
|
|
15
|
-
const { index,
|
|
15
|
+
const { index, onHeight, children, ...rest } = props;
|
|
16
16
|
const measured = useMeasure();
|
|
17
17
|
useEffect(() => {
|
|
18
|
-
if (placeholder)
|
|
19
|
-
return;
|
|
20
18
|
onHeight(index, measured.height);
|
|
21
|
-
}, [measured.height
|
|
19
|
+
}, [measured.height]);
|
|
22
20
|
return h('box', { direction: 'column', ...rest }, children);
|
|
23
21
|
});
|
|
24
|
-
export const FeedScrollbar = defineComponent('FeedScrollbar', () => {
|
|
25
|
-
const theme = useTheme();
|
|
26
|
-
return h('box', { width: 1, fill: theme.borderChars().left, fg: 'borderSubtle' });
|
|
27
|
-
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@textui/widgets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The TextUI component catalog - layout, display, controls, data, overlays and charts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"terminal",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"test": "vitest run"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@textui/core": "^0.
|
|
49
|
+
"@textui/core": "^0.4.0"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public"
|
package/src/control/select.ts
CHANGED
|
@@ -202,6 +202,16 @@ export const Select = defineComponent<SelectProps>('Select', (props) => {
|
|
|
202
202
|
direction: 'column',
|
|
203
203
|
border: { style: theme.border, color: focus.focused ? 'focus' : 'border' },
|
|
204
204
|
padding: [0, 1],
|
|
205
|
+
/*
|
|
206
|
+
* The terminal cursor, parked at the start of the value.
|
|
207
|
+
*
|
|
208
|
+
* There is nothing to type here, so this is not a caret - it is the
|
|
209
|
+
* strongest "you are here" a terminal has, and a control that gives it up
|
|
210
|
+
* on focus is one whose only remaining signal is the colour of its border.
|
|
211
|
+
* Tabbing from a text field used to look like the focus had stayed behind
|
|
212
|
+
* in the field, because the cursor had.
|
|
213
|
+
*/
|
|
214
|
+
cursor: focus.focused ? 0 : undefined,
|
|
205
215
|
...rest,
|
|
206
216
|
},
|
|
207
217
|
h('box', {
|
package/src/data/feed.ts
CHANGED
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
useState,
|
|
12
12
|
} from '@textui/core';
|
|
13
13
|
import { sizedByLayout } from '../viewport.js';
|
|
14
|
-
import { FeedEntry
|
|
14
|
+
import { FeedEntry } from './shared.js';
|
|
15
|
+
import { ScrollThumb } from './scroll-thumb.js';
|
|
15
16
|
|
|
16
17
|
// ---------------------------------------------------------------------- feed
|
|
17
18
|
|
|
@@ -263,12 +264,44 @@ export const Feed = defineComponent<FeedProps>('Feed', (props) => {
|
|
|
263
264
|
y += height;
|
|
264
265
|
}
|
|
265
266
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
267
|
+
/*
|
|
268
|
+
* A run of skipped entries is one box, not one box each.
|
|
269
|
+
*
|
|
270
|
+
* Skipping the *contents* of an entry took the wrapping away, which was the
|
|
271
|
+
* expensive part, and left everything else: an entry standing in for itself
|
|
272
|
+
* is still a component to reconcile and still a box to measure, four times a
|
|
273
|
+
* frame, at every level of the tree above it. Six hundred entries with
|
|
274
|
+
* thirty on screen was five hundred and seventy of those, and they cost the
|
|
275
|
+
* same whether or not anything in them had changed.
|
|
276
|
+
*
|
|
277
|
+
* The window is contiguous, so there are at most two runs - everything above
|
|
278
|
+
* it and everything below - and each collapses to a single box of exactly
|
|
279
|
+
* the height its entries had. The entries that *are* drawn keep their own
|
|
280
|
+
* index as their key, so scrolling moves the boundary without disturbing the
|
|
281
|
+
* identity of anything still on screen.
|
|
282
|
+
*/
|
|
283
|
+
const drawn: unknown[] = [];
|
|
284
|
+
let gap = 0;
|
|
285
|
+
let gapFrom = 0;
|
|
286
|
+
const closeGap = (): void => {
|
|
287
|
+
if (gap <= 0) return;
|
|
288
|
+
drawn.push(h('box', { key: `gap:${String(gapFrom)}`, height: gap }));
|
|
289
|
+
gap = 0;
|
|
290
|
+
};
|
|
291
|
+
for (let i = 0; i < count; i++) {
|
|
292
|
+
if (skipped[i]) {
|
|
293
|
+
if (gap === 0) gapFrom = i;
|
|
294
|
+
gap += heights.current[i] as number;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
closeGap();
|
|
298
|
+
drawn.push(h(FeedEntry, {
|
|
299
|
+
key: i,
|
|
300
|
+
index: i,
|
|
301
|
+
onHeight: report.current,
|
|
302
|
+
}, entries[i]));
|
|
303
|
+
}
|
|
304
|
+
closeGap();
|
|
272
305
|
|
|
273
306
|
// Content-sized: draw everything and let the box grow. Clamping to a
|
|
274
307
|
// measurement here would clamp to how tall this happened to be last frame,
|
|
@@ -296,8 +329,21 @@ export const Feed = defineComponent<FeedProps>('Feed', (props) => {
|
|
|
296
329
|
},
|
|
297
330
|
},
|
|
298
331
|
h('box', { flex: 1, direction: 'column', scrollTop: top, overflow: 'scroll' }, ...drawn),
|
|
332
|
+
// `ScrollThumb`, the one every other viewport in the library draws. This
|
|
333
|
+
// one had its own: a column filled with the border glyph, taking no offset
|
|
334
|
+
// and saying nothing about where in the conversation you were or how much
|
|
335
|
+
// of it there is. Against the panel it sits inside, it read as a second
|
|
336
|
+
// border - and a bar that cannot be told from a frame is not a bar.
|
|
337
|
+
//
|
|
299
338
|
// Only when there is somewhere to scroll. A track down the side of a feed
|
|
300
339
|
// that fits is chrome that states something untrue.
|
|
301
|
-
scrollbar && limit > 0
|
|
340
|
+
scrollbar && limit > 0
|
|
341
|
+
? h(ScrollThumb, {
|
|
342
|
+
total: total || measured.height,
|
|
343
|
+
rows: measured.height,
|
|
344
|
+
offset: top,
|
|
345
|
+
focused: focus.focused,
|
|
346
|
+
})
|
|
347
|
+
: null,
|
|
302
348
|
);
|
|
303
349
|
});
|
package/src/data/shared.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BoxProps } from '@textui/core';
|
|
2
|
-
import { defineComponent, h, useEffect, useMeasure
|
|
2
|
+
import { defineComponent, h, useEffect, useMeasure } from '@textui/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* An entry that reports how tall it turned out to be.
|
|
@@ -15,31 +15,15 @@ import { defineComponent, h, useEffect, useMeasure, useTheme } from '@textui/cor
|
|
|
15
15
|
*/
|
|
16
16
|
export const FeedEntry = defineComponent<BoxProps & {
|
|
17
17
|
index: number;
|
|
18
|
-
/**
|
|
19
|
-
* Standing in for an entry that is not drawn, at the height it had.
|
|
20
|
-
*
|
|
21
|
-
* A stand-in must not answer the question it is an answer to. Its height is
|
|
22
|
-
* one the feed already knows - it is where it came from - and reporting it
|
|
23
|
-
* back is how a measurement gets to depend on itself: a round where a
|
|
24
|
-
* stand-in came out any shorter would teach the feed that it *was* shorter,
|
|
25
|
-
* and the next round would place the rest of them on top of each other.
|
|
26
|
-
*/
|
|
27
|
-
placeholder?: boolean;
|
|
28
18
|
onHeight(index: number, height: number): void;
|
|
29
19
|
}>(
|
|
30
20
|
'FeedEntry',
|
|
31
21
|
(props) => {
|
|
32
|
-
const { index,
|
|
22
|
+
const { index, onHeight, children, ...rest } = props;
|
|
33
23
|
const measured = useMeasure();
|
|
34
24
|
useEffect(() => {
|
|
35
|
-
if (placeholder) return;
|
|
36
25
|
onHeight(index, measured.height);
|
|
37
|
-
}, [measured.height
|
|
26
|
+
}, [measured.height]);
|
|
38
27
|
return h('box', { direction: 'column', ...rest }, children);
|
|
39
28
|
},
|
|
40
29
|
);
|
|
41
|
-
|
|
42
|
-
export const FeedScrollbar = defineComponent<Record<string, never>>('FeedScrollbar', () => {
|
|
43
|
-
const theme = useTheme();
|
|
44
|
-
return h('box', { width: 1, fill: theme.borderChars().left, fg: 'borderSubtle' });
|
|
45
|
-
});
|