chat-layout 1.0.0-2 → 1.0.0-4
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 +64 -203
- package/example/build.ts +12 -0
- package/example/chat.ts +403 -0
- package/example/test.ts +215 -0
- package/index.d.mts +240 -1
- package/index.mjs +141 -0
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -74,30 +74,48 @@ function measureNodeMinContent(ctx, node, constraints = ctx.constraints) {
|
|
|
74
74
|
if (node.measureMinContent != null) return node.measureMinContent(nextCtx);
|
|
75
75
|
return node.measure(nextCtx);
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* A node that owns an ordered list of child nodes.
|
|
79
|
+
*/
|
|
77
80
|
var Group = class {
|
|
78
81
|
#children;
|
|
82
|
+
/**
|
|
83
|
+
* @param children Initial child nodes, in layout order.
|
|
84
|
+
*/
|
|
79
85
|
constructor(children) {
|
|
80
86
|
this.#children = [...children];
|
|
81
87
|
replaceNodesParent([], this.#children, this);
|
|
82
88
|
}
|
|
89
|
+
/** Child nodes managed by this group. */
|
|
83
90
|
get children() {
|
|
84
91
|
return this.#children;
|
|
85
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Replaces the full child list while updating parent links.
|
|
95
|
+
*/
|
|
86
96
|
replaceChildren(nextChildren) {
|
|
87
97
|
const nextSnapshot = [...nextChildren];
|
|
88
98
|
replaceNodesParent(this.#children, nextSnapshot, this);
|
|
89
99
|
this.#children = nextSnapshot;
|
|
90
100
|
}
|
|
91
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* A node that forwards layout and drawing to a single inner node.
|
|
104
|
+
*/
|
|
92
105
|
var Wrapper = class {
|
|
93
106
|
#inner;
|
|
107
|
+
/**
|
|
108
|
+
* @param inner Wrapped child node.
|
|
109
|
+
*/
|
|
94
110
|
constructor(inner) {
|
|
95
111
|
this.#inner = inner;
|
|
96
112
|
attachNodeToParent(this.#inner, this);
|
|
97
113
|
}
|
|
114
|
+
/** The wrapped child node. */
|
|
98
115
|
get inner() {
|
|
99
116
|
return this.#inner;
|
|
100
117
|
}
|
|
118
|
+
/** Replaces the wrapped child node. */
|
|
101
119
|
set inner(newNode) {
|
|
102
120
|
if (newNode === this.#inner) return;
|
|
103
121
|
replaceNodeParent(this.#inner, newNode, this);
|
|
@@ -227,7 +245,14 @@ function shrinkConstraint(value, padding) {
|
|
|
227
245
|
if (value == null) return;
|
|
228
246
|
return Math.max(0, value - padding);
|
|
229
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Adds padding around a single child node.
|
|
250
|
+
*/
|
|
230
251
|
var PaddingBox = class extends Wrapper {
|
|
252
|
+
/**
|
|
253
|
+
* @param inner Wrapped child node.
|
|
254
|
+
* @param padding Padding in CSS pixels on each side.
|
|
255
|
+
*/
|
|
231
256
|
constructor(inner, padding = {}) {
|
|
232
257
|
super(inner);
|
|
233
258
|
this.padding = padding;
|
|
@@ -315,7 +340,14 @@ var PaddingBox = class extends Wrapper {
|
|
|
315
340
|
}));
|
|
316
341
|
}
|
|
317
342
|
};
|
|
343
|
+
/**
|
|
344
|
+
* A leaf node with a fixed size and no drawing behavior.
|
|
345
|
+
*/
|
|
318
346
|
var Fixed = class {
|
|
347
|
+
/**
|
|
348
|
+
* @param width Fixed width in CSS pixels.
|
|
349
|
+
* @param height Fixed height in CSS pixels.
|
|
350
|
+
*/
|
|
319
351
|
constructor(width, height) {
|
|
320
352
|
this.width = width;
|
|
321
353
|
this.height = height;
|
|
@@ -652,13 +684,27 @@ function computeFlexLayout(children, options, constraints, measureChild, measure
|
|
|
652
684
|
}
|
|
653
685
|
};
|
|
654
686
|
}
|
|
687
|
+
/**
|
|
688
|
+
* Wraps a child node with per-item flex options.
|
|
689
|
+
*/
|
|
655
690
|
var FlexItem = class extends Wrapper {
|
|
691
|
+
/**
|
|
692
|
+
* @param inner Wrapped child node.
|
|
693
|
+
* @param item Flex behavior overrides for the child.
|
|
694
|
+
*/
|
|
656
695
|
constructor(inner, item = {}) {
|
|
657
696
|
super(inner);
|
|
658
697
|
this.item = item;
|
|
659
698
|
}
|
|
660
699
|
};
|
|
700
|
+
/**
|
|
701
|
+
* Lays out children in a single flex row or column.
|
|
702
|
+
*/
|
|
661
703
|
var Flex = class extends Group {
|
|
704
|
+
/**
|
|
705
|
+
* @param children Child nodes in visual order.
|
|
706
|
+
* @param options Flex container configuration.
|
|
707
|
+
*/
|
|
662
708
|
constructor(children, options = {}) {
|
|
663
709
|
super(children);
|
|
664
710
|
this.options = options;
|
|
@@ -713,7 +759,14 @@ function resolveHorizontalOffset(align, availableWidth, childWidth) {
|
|
|
713
759
|
case "start": return 0;
|
|
714
760
|
}
|
|
715
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Aligns a single child horizontally within the available width.
|
|
764
|
+
*/
|
|
716
765
|
var Place = class extends Wrapper {
|
|
766
|
+
/**
|
|
767
|
+
* @param inner Wrapped child node.
|
|
768
|
+
* @param options Alignment behavior for the child.
|
|
769
|
+
*/
|
|
717
770
|
constructor(inner, options = {}) {
|
|
718
771
|
super(inner);
|
|
719
772
|
this.options = options;
|
|
@@ -1048,7 +1101,14 @@ function getSingleLineMinContentLayout(node, ctx, text, whitespace) {
|
|
|
1048
1101
|
function getMultiLineMinContentLayout(node, ctx, text, whitespace) {
|
|
1049
1102
|
return readCachedTextLayout(node, ctx, getMultiLineMinContentLayoutKey(), () => measureTextMinContent(ctx, text, whitespace));
|
|
1050
1103
|
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Draws wrapped text using the configured line height and alignment.
|
|
1106
|
+
*/
|
|
1051
1107
|
var MultilineText = class {
|
|
1108
|
+
/**
|
|
1109
|
+
* @param text Source text to measure and draw.
|
|
1110
|
+
* @param options Text layout and drawing options.
|
|
1111
|
+
*/
|
|
1052
1112
|
constructor(text, options) {
|
|
1053
1113
|
this.text = text;
|
|
1054
1114
|
this.options = options;
|
|
@@ -1109,7 +1169,14 @@ var MultilineText = class {
|
|
|
1109
1169
|
return false;
|
|
1110
1170
|
}
|
|
1111
1171
|
};
|
|
1172
|
+
/**
|
|
1173
|
+
* Draws a single line of text, clipped logically by measurement width.
|
|
1174
|
+
*/
|
|
1112
1175
|
var Text = class {
|
|
1176
|
+
/**
|
|
1177
|
+
* @param text Source text to measure and draw.
|
|
1178
|
+
* @param options Text layout and drawing options.
|
|
1179
|
+
*/
|
|
1113
1180
|
constructor(text, options) {
|
|
1114
1181
|
this.text = text;
|
|
1115
1182
|
this.options = options;
|
|
@@ -1154,7 +1221,11 @@ function constraintKey(constraints) {
|
|
|
1154
1221
|
if (constraints == null) return "";
|
|
1155
1222
|
return `${constraints.minWidth ?? ""},${constraints.maxWidth ?? ""},${constraints.minHeight ?? ""},${constraints.maxHeight ?? ""}`;
|
|
1156
1223
|
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Base renderer that provides measurement, layout caching, and drawing helpers.
|
|
1226
|
+
*/
|
|
1157
1227
|
var BaseRenderer = class {
|
|
1228
|
+
/** Canvas rendering context used by this renderer. */
|
|
1158
1229
|
graphics;
|
|
1159
1230
|
#ctx;
|
|
1160
1231
|
#lastWidth;
|
|
@@ -1164,6 +1235,10 @@ var BaseRenderer = class {
|
|
|
1164
1235
|
get context() {
|
|
1165
1236
|
return shallow(this.#ctx);
|
|
1166
1237
|
}
|
|
1238
|
+
/**
|
|
1239
|
+
* @param graphics Canvas rendering context used for all layout and drawing.
|
|
1240
|
+
* @param options Renderer-specific options.
|
|
1241
|
+
*/
|
|
1167
1242
|
constructor(graphics, options) {
|
|
1168
1243
|
this.options = options;
|
|
1169
1244
|
this.graphics = graphics;
|
|
@@ -1232,6 +1307,9 @@ var BaseRenderer = class {
|
|
|
1232
1307
|
this.measureRootNode(node);
|
|
1233
1308
|
return node.hittest(this.getRootContext(), test);
|
|
1234
1309
|
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Drops cached measurements for a node and every ancestor that depends on it.
|
|
1312
|
+
*/
|
|
1235
1313
|
invalidateNode(node) {
|
|
1236
1314
|
this.#syncCachesToViewportWidth();
|
|
1237
1315
|
this.#cache.delete(node);
|
|
@@ -1243,6 +1321,9 @@ var BaseRenderer = class {
|
|
|
1243
1321
|
this.#textLayoutCache.delete(ancestor);
|
|
1244
1322
|
});
|
|
1245
1323
|
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Returns the cached layout result for a node under the given constraints, if available.
|
|
1326
|
+
*/
|
|
1246
1327
|
getLayoutResult(node, constraints) {
|
|
1247
1328
|
this.#syncCachesToViewportWidth();
|
|
1248
1329
|
const nodeCache = this.#layoutCache.get(node);
|
|
@@ -1256,6 +1337,9 @@ var BaseRenderer = class {
|
|
|
1256
1337
|
}
|
|
1257
1338
|
return cached.layout;
|
|
1258
1339
|
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Stores a layout result for later draw and hit-test passes.
|
|
1342
|
+
*/
|
|
1259
1343
|
setLayoutResult(node, result, constraints) {
|
|
1260
1344
|
this.#syncCachesToViewportWidth();
|
|
1261
1345
|
let nodeCache = this.#layoutCache.get(node);
|
|
@@ -1298,6 +1382,9 @@ var BaseRenderer = class {
|
|
|
1298
1382
|
layout
|
|
1299
1383
|
});
|
|
1300
1384
|
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Measures a node under optional constraints, using cached results when possible.
|
|
1387
|
+
*/
|
|
1301
1388
|
measureNode(node, constraints) {
|
|
1302
1389
|
this.#syncCachesToViewportWidth();
|
|
1303
1390
|
{
|
|
@@ -1330,51 +1417,81 @@ var BaseRenderer = class {
|
|
|
1330
1417
|
return result;
|
|
1331
1418
|
}
|
|
1332
1419
|
};
|
|
1420
|
+
/**
|
|
1421
|
+
* Immediate-mode renderer for a single root node.
|
|
1422
|
+
*/
|
|
1333
1423
|
var DebugRenderer = class extends BaseRenderer {
|
|
1424
|
+
/**
|
|
1425
|
+
* Clears the viewport and draws the provided root node.
|
|
1426
|
+
*/
|
|
1334
1427
|
draw(node) {
|
|
1335
1428
|
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
1336
1429
|
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
1337
1430
|
return this.drawRootNode(node);
|
|
1338
1431
|
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Hit-tests the provided root node using viewport-relative coordinates.
|
|
1434
|
+
*/
|
|
1339
1435
|
hittest(node, test) {
|
|
1340
1436
|
return this.hittestRootNode(node, test);
|
|
1341
1437
|
}
|
|
1342
1438
|
};
|
|
1343
1439
|
//#endregion
|
|
1344
1440
|
//#region src/renderer/list-state.ts
|
|
1441
|
+
/**
|
|
1442
|
+
* Mutable list state shared with virtualized renderers.
|
|
1443
|
+
*/
|
|
1345
1444
|
var ListState = class {
|
|
1445
|
+
/** Pixel offset from the anchored item edge. */
|
|
1346
1446
|
offset = 0;
|
|
1447
|
+
/** Anchor item index, or `undefined` to use the renderer default. */
|
|
1347
1448
|
position;
|
|
1449
|
+
/** Items currently managed by the renderer. */
|
|
1348
1450
|
items = [];
|
|
1451
|
+
/**
|
|
1452
|
+
* @param items Initial list items.
|
|
1453
|
+
*/
|
|
1349
1454
|
constructor(items = []) {
|
|
1350
1455
|
this.items = [...items];
|
|
1351
1456
|
}
|
|
1457
|
+
/** Prepends one or more items. */
|
|
1352
1458
|
unshift(...items) {
|
|
1353
1459
|
this.unshiftAll(items);
|
|
1354
1460
|
}
|
|
1461
|
+
/** Prepends an array of items. */
|
|
1355
1462
|
unshiftAll(items) {
|
|
1356
1463
|
if (this.position != null) this.position += items.length;
|
|
1357
1464
|
this.items = items.concat(this.items);
|
|
1358
1465
|
}
|
|
1466
|
+
/** Appends one or more items. */
|
|
1359
1467
|
push(...items) {
|
|
1360
1468
|
this.pushAll(items);
|
|
1361
1469
|
}
|
|
1470
|
+
/** Appends an array of items. */
|
|
1362
1471
|
pushAll(items) {
|
|
1363
1472
|
this.items.push(...items);
|
|
1364
1473
|
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Sets the current anchor item and pixel offset.
|
|
1476
|
+
*/
|
|
1365
1477
|
setAnchor(position, offset = 0) {
|
|
1366
1478
|
this.position = Number.isFinite(position) ? Math.trunc(position) : void 0;
|
|
1367
1479
|
this.offset = Number.isFinite(offset) ? offset : 0;
|
|
1368
1480
|
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Replaces all items and clears scroll state.
|
|
1483
|
+
*/
|
|
1369
1484
|
reset(items = []) {
|
|
1370
1485
|
this.items = [...items];
|
|
1371
1486
|
this.offset = 0;
|
|
1372
1487
|
this.position = void 0;
|
|
1373
1488
|
}
|
|
1489
|
+
/** Clears the current scroll anchor while keeping the items. */
|
|
1374
1490
|
resetScroll() {
|
|
1375
1491
|
this.offset = 0;
|
|
1376
1492
|
this.position = void 0;
|
|
1377
1493
|
}
|
|
1494
|
+
/** Applies a relative pixel scroll delta. */
|
|
1378
1495
|
applyScroll(delta) {
|
|
1379
1496
|
this.offset += delta;
|
|
1380
1497
|
}
|
|
@@ -1384,6 +1501,9 @@ var ListState = class {
|
|
|
1384
1501
|
function isWeakMapKey(value) {
|
|
1385
1502
|
return typeof value === "object" && value !== null || typeof value === "function";
|
|
1386
1503
|
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Memoizes `renderItem` by object identity.
|
|
1506
|
+
*/
|
|
1387
1507
|
function memoRenderItem(renderItem) {
|
|
1388
1508
|
const cache = /* @__PURE__ */ new WeakMap();
|
|
1389
1509
|
function fn(item) {
|
|
@@ -1397,6 +1517,9 @@ function memoRenderItem(renderItem) {
|
|
|
1397
1517
|
}
|
|
1398
1518
|
return Object.assign(fn, { reset: (key) => cache.delete(key) });
|
|
1399
1519
|
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Memoizes `renderItem` by a caller-provided cache key.
|
|
1522
|
+
*/
|
|
1400
1523
|
function memoRenderItemBy(keyOf, renderItem) {
|
|
1401
1524
|
const cache = /* @__PURE__ */ new Map();
|
|
1402
1525
|
function fn(item) {
|
|
@@ -1426,27 +1549,36 @@ function smoothstep(value) {
|
|
|
1426
1549
|
function getNow() {
|
|
1427
1550
|
return globalThis.performance?.now() ?? Date.now();
|
|
1428
1551
|
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Shared base class for virtualized list renderers.
|
|
1554
|
+
*/
|
|
1429
1555
|
var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
1430
1556
|
static MIN_JUMP_DURATION = 160;
|
|
1431
1557
|
static MAX_JUMP_DURATION = 420;
|
|
1432
1558
|
static JUMP_DURATION_PER_ITEM = 28;
|
|
1433
1559
|
#controlledState;
|
|
1434
1560
|
#jumpAnimation;
|
|
1561
|
+
/** Current anchor item index. */
|
|
1435
1562
|
get position() {
|
|
1436
1563
|
return this.options.list.position;
|
|
1437
1564
|
}
|
|
1565
|
+
/** Updates the current anchor item index. */
|
|
1438
1566
|
set position(value) {
|
|
1439
1567
|
this.options.list.position = value;
|
|
1440
1568
|
}
|
|
1569
|
+
/** Pixel offset from the anchored item edge. */
|
|
1441
1570
|
get offset() {
|
|
1442
1571
|
return this.options.list.offset;
|
|
1443
1572
|
}
|
|
1573
|
+
/** Updates the pixel offset from the anchored item edge. */
|
|
1444
1574
|
set offset(value) {
|
|
1445
1575
|
this.options.list.offset = value;
|
|
1446
1576
|
}
|
|
1577
|
+
/** Items currently available to the renderer. */
|
|
1447
1578
|
get items() {
|
|
1448
1579
|
return this.options.list.items;
|
|
1449
1580
|
}
|
|
1581
|
+
/** Replaces the current item collection. */
|
|
1450
1582
|
set items(value) {
|
|
1451
1583
|
this.options.list.items = value;
|
|
1452
1584
|
}
|
|
@@ -1460,6 +1592,9 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
1460
1592
|
this.position = state.position;
|
|
1461
1593
|
this.offset = state.offset;
|
|
1462
1594
|
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Scrolls the viewport to the requested item index.
|
|
1597
|
+
*/
|
|
1463
1598
|
jumpTo(index, options = {}) {
|
|
1464
1599
|
if (this.items.length === 0) {
|
|
1465
1600
|
this.#cancelJumpAnimation();
|
|
@@ -1795,6 +1930,9 @@ function resolveChatVisibleWindow(items, state, viewportHeight, resolveItem) {
|
|
|
1795
1930
|
function clamp$1(value, min, max) {
|
|
1796
1931
|
return Math.min(Math.max(value, min), max);
|
|
1797
1932
|
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Virtualized renderer anchored to the bottom, suitable for chat-style UIs.
|
|
1935
|
+
*/
|
|
1798
1936
|
var ChatRenderer = class extends VirtualizedRenderer {
|
|
1799
1937
|
#resolveVisibleWindow() {
|
|
1800
1938
|
return resolveChatVisibleWindow(this.items, this._readListState(), this.graphics.canvas.clientHeight, (item) => {
|
|
@@ -1854,6 +1992,9 @@ var ChatRenderer = class extends VirtualizedRenderer {
|
|
|
1854
1992
|
function clamp(value, min, max) {
|
|
1855
1993
|
return Math.min(Math.max(value, min), max);
|
|
1856
1994
|
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Virtualized renderer anchored to the top, suitable for timeline-style UIs.
|
|
1997
|
+
*/
|
|
1857
1998
|
var TimelineRenderer = class extends VirtualizedRenderer {
|
|
1858
1999
|
#resolveVisibleWindow() {
|
|
1859
2000
|
return resolveTimelineVisibleWindow(this.items, this._readListState(), this.graphics.canvas.clientHeight, (item) => {
|