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/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) => {