chat-layout 0.0.19 → 0.1.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/index.mjs ADDED
@@ -0,0 +1,799 @@
1
+ import { layoutWithLines, prepareWithSegments } from "@chenglou/pretext";
2
+ //#region src/text.ts
3
+ function preprocessSegments(text) {
4
+ return text.split("\n").map((line) => line.trim()).filter(Boolean);
5
+ }
6
+ function layoutFirstLine(ctx, text, maxWidth) {
7
+ if (maxWidth < 0) maxWidth = 0;
8
+ const segment = preprocessSegments(text)[0];
9
+ if (!segment) return {
10
+ width: 0,
11
+ text: "",
12
+ shift: 0
13
+ };
14
+ const { fontBoundingBoxAscent: ascent = 0, fontBoundingBoxDescent: descent = 0 } = ctx.graphics.measureText(segment);
15
+ const shift = ascent - descent;
16
+ if (maxWidth === 0) return {
17
+ width: 0,
18
+ text: "",
19
+ shift
20
+ };
21
+ const { lines } = layoutWithLines(prepareWithSegments(segment, ctx.graphics.font), maxWidth, 0);
22
+ if (lines.length === 0) return {
23
+ width: 0,
24
+ text: "",
25
+ shift
26
+ };
27
+ return {
28
+ width: lines[0].width,
29
+ text: lines[0].text,
30
+ shift
31
+ };
32
+ }
33
+ function layoutText(ctx, text, maxWidth) {
34
+ if (maxWidth < 0) maxWidth = 0;
35
+ const segments = preprocessSegments(text);
36
+ if (segments.length === 0 || maxWidth === 0) return {
37
+ width: 0,
38
+ lines: []
39
+ };
40
+ const font = ctx.graphics.font;
41
+ let width = 0;
42
+ const lines = [];
43
+ for (const segment of segments) {
44
+ const { fontBoundingBoxAscent: ascent = 0, fontBoundingBoxDescent: descent = 0 } = ctx.graphics.measureText(segment);
45
+ const shift = ascent - descent;
46
+ const { lines: segLines } = layoutWithLines(prepareWithSegments(segment, font), maxWidth, 0);
47
+ for (const segLine of segLines) {
48
+ width = Math.max(width, segLine.width);
49
+ lines.push({
50
+ width: segLine.width,
51
+ text: segLine.text,
52
+ shift
53
+ });
54
+ }
55
+ }
56
+ return {
57
+ width,
58
+ lines
59
+ };
60
+ }
61
+ //#endregion
62
+ //#region src/utils.ts
63
+ function shallow(object) {
64
+ return Object.create(object);
65
+ }
66
+ function shallowMerge(object, other) {
67
+ return {
68
+ __proto__: object,
69
+ ...other
70
+ };
71
+ }
72
+ //#endregion
73
+ //#region src/registry.ts
74
+ const registry = /* @__PURE__ */ new WeakMap();
75
+ function registerNodeParent(node, parent) {
76
+ registry.set(node, parent);
77
+ }
78
+ function unregisterNodeParent(node) {
79
+ registry.delete(node);
80
+ }
81
+ function getNodeParent(node) {
82
+ return registry.get(node);
83
+ }
84
+ //#endregion
85
+ //#region src/nodes.ts
86
+ var Group = class {
87
+ constructor(children) {
88
+ this.children = children;
89
+ for (const child of children) registerNodeParent(child, this);
90
+ }
91
+ get flex() {
92
+ return this.children.some((item) => item.flex);
93
+ }
94
+ };
95
+ var VStack = class extends Group {
96
+ constructor(children, options = {}) {
97
+ super(children);
98
+ this.options = options;
99
+ }
100
+ measure(ctx) {
101
+ let width = 0;
102
+ let height = 0;
103
+ if (this.options.alignment != null) ctx.alignment = this.options.alignment;
104
+ for (const [index, child] of this.children.entries()) {
105
+ if (this.options.gap != null && index !== 0) height += this.options.gap;
106
+ const result = shallow(ctx).measureNode(child);
107
+ height += result.height;
108
+ width = Math.max(width, result.width);
109
+ }
110
+ ctx.remainingWidth -= width;
111
+ return {
112
+ width,
113
+ height
114
+ };
115
+ }
116
+ draw(ctx, x, y) {
117
+ let result = false;
118
+ const fullWidth = ctx.measureNode(this).width;
119
+ const alignment = this.options.alignment ?? ctx.alignment;
120
+ if (this.options.alignment != null) ctx.alignment = this.options.alignment;
121
+ for (const [index, child] of this.children.entries()) {
122
+ if (this.options.gap != null && index !== 0) y += this.options.gap;
123
+ const { width, height } = shallow(ctx).measureNode(child);
124
+ const curCtx = shallow(ctx);
125
+ let requestRedraw;
126
+ if (alignment === "right") requestRedraw = child.draw(curCtx, x + fullWidth - width, y);
127
+ else if (alignment === "center") requestRedraw = child.draw(curCtx, x + (fullWidth - width) / 2, y);
128
+ else requestRedraw = child.draw(curCtx, x, y);
129
+ result ||= requestRedraw;
130
+ y += height;
131
+ }
132
+ return result;
133
+ }
134
+ hittest(ctx, test) {
135
+ let y = 0;
136
+ const fullWidth = ctx.measureNode(this).width;
137
+ const alignment = this.options.alignment ?? ctx.alignment;
138
+ if (this.options.alignment != null) ctx.alignment = this.options.alignment;
139
+ for (const [index, child] of this.children.entries()) {
140
+ if (this.options.gap != null && index !== 0) y += this.options.gap;
141
+ const { width, height } = shallow(ctx).measureNode(child);
142
+ const curCtx = shallow(ctx);
143
+ if (test.y >= y && test.y < y + height) {
144
+ let x;
145
+ if (alignment === "right") x = test.x - fullWidth + width;
146
+ else if (alignment === "center") x = test.x - (fullWidth - width) / 2;
147
+ else x = test.x;
148
+ if (x < 0 || x >= width) return false;
149
+ return child.hittest(curCtx, shallowMerge(test, {
150
+ x,
151
+ y: test.y - y
152
+ }));
153
+ }
154
+ y += height;
155
+ }
156
+ return false;
157
+ }
158
+ };
159
+ var HStack = class extends Group {
160
+ constructor(children, options = {}) {
161
+ super(children);
162
+ this.children = children;
163
+ this.options = options;
164
+ }
165
+ measure(ctx) {
166
+ let width = 0;
167
+ let height = 0;
168
+ let firstFlex;
169
+ for (const [index, child] of this.children.entries()) {
170
+ if (this.options.gap != null && index !== 0) width += this.options.gap;
171
+ if (firstFlex == null && child.flex) {
172
+ firstFlex = child;
173
+ continue;
174
+ }
175
+ const curCtx = shallow(ctx);
176
+ curCtx.remainingWidth = ctx.remainingWidth - width;
177
+ const result = curCtx.measureNode(child);
178
+ width += result.width;
179
+ height = Math.max(height, result.height);
180
+ }
181
+ if (firstFlex != null) {
182
+ const curCtx = shallow(ctx);
183
+ curCtx.remainingWidth = ctx.remainingWidth - width;
184
+ const result = curCtx.measureNode(firstFlex);
185
+ width += result.width;
186
+ height = Math.max(height, result.height);
187
+ }
188
+ return {
189
+ width,
190
+ height
191
+ };
192
+ }
193
+ draw(ctx, x, y) {
194
+ let result = false;
195
+ const reverse = this.options.reverse ?? ctx.reverse;
196
+ if (this.options.reverse) ctx.reverse = this.options.reverse;
197
+ if (reverse) {
198
+ x += ctx.measureNode(this).width;
199
+ for (const [index, child] of this.children.entries()) {
200
+ const gap = this.options.gap != null && index !== 0 ? this.options.gap : void 0;
201
+ if (gap) {
202
+ x -= gap;
203
+ ctx.remainingWidth -= gap;
204
+ }
205
+ const { width } = shallow(ctx).measureNode(child);
206
+ x -= width;
207
+ const requestRedraw = child.draw(shallow(ctx), x, y);
208
+ result ||= requestRedraw;
209
+ ctx.remainingWidth -= width;
210
+ }
211
+ } else for (const [index, child] of this.children.entries()) {
212
+ const gap = this.options.gap != null && index !== 0 ? this.options.gap : void 0;
213
+ if (gap) {
214
+ x += gap;
215
+ ctx.remainingWidth -= gap;
216
+ }
217
+ const requestRedraw = child.draw(shallow(ctx), x, y);
218
+ result ||= requestRedraw;
219
+ const { width } = shallow(ctx).measureNode(child);
220
+ ctx.remainingWidth -= width;
221
+ x += width;
222
+ }
223
+ return result;
224
+ }
225
+ hittest(ctx, test) {
226
+ const reverse = this.options.reverse ?? ctx.reverse;
227
+ if (this.options.reverse) ctx.reverse = this.options.reverse;
228
+ if (reverse) {
229
+ let x = ctx.measureNode(this).width;
230
+ for (const [index, child] of this.children.entries()) {
231
+ const gap = this.options.gap != null && index !== 0 ? this.options.gap : void 0;
232
+ if (gap) {
233
+ x -= gap;
234
+ ctx.remainingWidth -= gap;
235
+ }
236
+ const { width, height } = shallow(ctx).measureNode(child);
237
+ x -= width;
238
+ if (x <= test.x && test.x < x + width) {
239
+ if (test.y >= height) return false;
240
+ return child.hittest(shallow(ctx), shallowMerge(test, { x: test.x - x }));
241
+ }
242
+ ctx.remainingWidth -= width;
243
+ }
244
+ } else {
245
+ let x = 0;
246
+ for (const [index, child] of this.children.entries()) {
247
+ const gap = this.options.gap != null && index !== 0 ? this.options.gap : void 0;
248
+ if (gap) {
249
+ x += gap;
250
+ ctx.remainingWidth -= gap;
251
+ }
252
+ const { width, height } = shallow(ctx).measureNode(child);
253
+ if (x <= test.x && test.x < x + width) {
254
+ if (test.y >= height) return false;
255
+ return child.hittest(shallow(ctx), shallowMerge(test, { x: test.x - x }));
256
+ }
257
+ x += width;
258
+ ctx.remainingWidth -= width;
259
+ }
260
+ }
261
+ return false;
262
+ }
263
+ };
264
+ var Wrapper = class {
265
+ #inner;
266
+ constructor(inner) {
267
+ this.#inner = inner;
268
+ registerNodeParent(this.#inner, this);
269
+ }
270
+ get inner() {
271
+ return this.#inner;
272
+ }
273
+ set inner(newNode) {
274
+ if (newNode === this.#inner) return;
275
+ unregisterNodeParent(this.#inner);
276
+ this.#inner = newNode;
277
+ registerNodeParent(newNode, this);
278
+ }
279
+ get flex() {
280
+ return this.inner.flex;
281
+ }
282
+ measure(ctx) {
283
+ return this.inner.measure(ctx);
284
+ }
285
+ draw(ctx, x, y) {
286
+ return this.inner.draw(ctx, x, y);
287
+ }
288
+ hittest(ctx, test) {
289
+ return this.inner.hittest(ctx, test);
290
+ }
291
+ };
292
+ var PaddingBox = class extends Wrapper {
293
+ constructor(inner, padding = {}) {
294
+ super(inner);
295
+ this.padding = padding;
296
+ }
297
+ get #top() {
298
+ return this.padding.top ?? 0;
299
+ }
300
+ get #bottom() {
301
+ return this.padding.bottom ?? 0;
302
+ }
303
+ get #left() {
304
+ return this.padding.left ?? 0;
305
+ }
306
+ get #right() {
307
+ return this.padding.right ?? 0;
308
+ }
309
+ measure(ctx) {
310
+ ctx.remainingWidth -= this.#left + this.#right;
311
+ const { width, height } = ctx.measureNode(this.inner);
312
+ return {
313
+ width: width + this.#left + this.#right,
314
+ height: height + this.#top + this.#bottom
315
+ };
316
+ }
317
+ draw(ctx, x, y) {
318
+ ctx.remainingWidth -= this.#left + this.#right;
319
+ return this.inner.draw(ctx, x + this.#left, y + this.#top);
320
+ }
321
+ hittest(ctx, test) {
322
+ ctx.remainingWidth -= this.#left + this.#right;
323
+ const { width, height } = shallow(ctx).measureNode(this.inner);
324
+ if (0 <= test.x - this.#left && test.x - this.#left < width && 0 <= test.y - this.#top && test.y - this.#top < height) return this.inner.hittest(shallow(ctx), shallowMerge(test, {
325
+ x: test.x - this.#left,
326
+ y: test.y - this.#top
327
+ }));
328
+ return false;
329
+ }
330
+ };
331
+ var AlignBox = class extends Wrapper {
332
+ #shift = 0;
333
+ constructor(inner, options) {
334
+ super(inner);
335
+ this.options = options;
336
+ }
337
+ measure(ctx) {
338
+ ctx.alignment = this.options.alignment;
339
+ const { width, height } = ctx.measureNode(this.inner);
340
+ switch (this.options.alignment) {
341
+ case "center":
342
+ this.#shift = (ctx.remainingWidth - width) / 2;
343
+ break;
344
+ case "right":
345
+ this.#shift = ctx.remainingWidth - width;
346
+ break;
347
+ default: this.#shift = 0;
348
+ }
349
+ return {
350
+ width: ctx.remainingWidth,
351
+ height
352
+ };
353
+ }
354
+ draw(ctx, x, y) {
355
+ ctx.alignment = this.options.alignment;
356
+ return this.inner.draw(ctx, x + this.#shift, y);
357
+ }
358
+ hittest(ctx, test) {
359
+ ctx.alignment = this.options.alignment;
360
+ const { width } = shallow(ctx).measureNode(this.inner);
361
+ if (0 <= test.x - this.#shift && test.x - this.#shift < width) return this.inner.hittest(shallow(ctx), shallowMerge(test, { x: test.x - this.#shift }));
362
+ return false;
363
+ }
364
+ };
365
+ var MultilineText = class {
366
+ #width = 0;
367
+ #lines = [];
368
+ constructor(text, options) {
369
+ this.text = text;
370
+ this.options = options;
371
+ }
372
+ get flex() {
373
+ return true;
374
+ }
375
+ measure(ctx) {
376
+ return ctx.with((g) => {
377
+ g.font = this.options.font;
378
+ const { width, lines } = layoutText(ctx, this.text, ctx.remainingWidth);
379
+ this.#width = width;
380
+ this.#lines = lines;
381
+ return {
382
+ width: this.#width,
383
+ height: this.#lines.length * this.options.lineHeight
384
+ };
385
+ });
386
+ }
387
+ draw(ctx, x, y) {
388
+ return ctx.with((g) => {
389
+ g.font = this.options.font;
390
+ g.fillStyle = ctx.resolveDynValue(this.options.style);
391
+ switch (this.options.alignment) {
392
+ case "left":
393
+ for (const { text, shift } of this.#lines) {
394
+ g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
395
+ y += this.options.lineHeight;
396
+ }
397
+ break;
398
+ case "right":
399
+ x += this.#width;
400
+ g.textAlign = "right";
401
+ for (const { text, shift } of this.#lines) {
402
+ g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
403
+ y += this.options.lineHeight;
404
+ }
405
+ break;
406
+ case "center":
407
+ x += this.#width / 2;
408
+ g.textAlign = "center";
409
+ for (const { text, shift } of this.#lines) {
410
+ g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
411
+ y += this.options.lineHeight;
412
+ }
413
+ break;
414
+ }
415
+ return false;
416
+ });
417
+ }
418
+ hittest(_ctx, _test) {
419
+ return false;
420
+ }
421
+ };
422
+ var Text = class {
423
+ #width = 0;
424
+ #text = "";
425
+ #shift = 0;
426
+ constructor(text, options) {
427
+ this.text = text;
428
+ this.options = options;
429
+ }
430
+ get flex() {
431
+ return false;
432
+ }
433
+ measure(ctx) {
434
+ return ctx.with((g) => {
435
+ g.font = this.options.font;
436
+ const { width, text, shift } = layoutFirstLine(ctx, this.text, ctx.remainingWidth);
437
+ this.#width = width;
438
+ this.#text = text;
439
+ this.#shift = shift;
440
+ return {
441
+ width: this.#width,
442
+ height: this.options.lineHeight
443
+ };
444
+ });
445
+ }
446
+ draw(ctx, x, y) {
447
+ return ctx.with((g) => {
448
+ g.font = this.options.font;
449
+ g.fillStyle = ctx.resolveDynValue(this.options.style);
450
+ g.fillText(this.#text, x, y + (this.options.lineHeight + this.#shift) / 2);
451
+ return false;
452
+ });
453
+ }
454
+ hittest(_ctx, _test) {
455
+ return false;
456
+ }
457
+ };
458
+ var Fixed = class {
459
+ constructor(width, height) {
460
+ this.width = width;
461
+ this.height = height;
462
+ }
463
+ get flex() {
464
+ return false;
465
+ }
466
+ measure(_ctx) {
467
+ return {
468
+ width: this.width,
469
+ height: this.height
470
+ };
471
+ }
472
+ draw(_ctx, _x, _y) {
473
+ return false;
474
+ }
475
+ hittest(_ctx, _test) {
476
+ return false;
477
+ }
478
+ };
479
+ //#endregion
480
+ //#region src/renderer.ts
481
+ var BaseRenderer = class {
482
+ graphics;
483
+ #ctx;
484
+ #lastWidth;
485
+ #cache = /* @__PURE__ */ new WeakMap();
486
+ get context() {
487
+ return shallow(this.#ctx);
488
+ }
489
+ constructor(graphics, options) {
490
+ this.options = options;
491
+ this.graphics = graphics;
492
+ this.graphics.textRendering = "optimizeLegibility";
493
+ const self = this;
494
+ this.#ctx = {
495
+ graphics: this.graphics,
496
+ get remainingWidth() {
497
+ return this.graphics.canvas.clientWidth;
498
+ },
499
+ set remainingWidth(value) {
500
+ Object.defineProperty(this, "remainingWidth", {
501
+ value,
502
+ writable: true
503
+ });
504
+ },
505
+ alignment: "left",
506
+ reverse: false,
507
+ measureNode(node) {
508
+ return self.measureNode(node, this);
509
+ },
510
+ invalidateNode: this.invalidateNode.bind(this),
511
+ resolveDynValue(value) {
512
+ if (typeof value === "function") return value(this.graphics);
513
+ return value;
514
+ },
515
+ with(cb) {
516
+ this.graphics.save();
517
+ try {
518
+ return cb(this.graphics);
519
+ } finally {
520
+ this.graphics.restore();
521
+ }
522
+ }
523
+ };
524
+ this.#lastWidth = this.graphics.canvas.clientWidth;
525
+ }
526
+ invalidateNode(node) {
527
+ this.#cache.delete(node);
528
+ let it = node;
529
+ while (it = getNodeParent(it)) this.#cache.delete(it);
530
+ }
531
+ measureNode(node, ctx) {
532
+ if (this.#lastWidth !== this.graphics.canvas.clientWidth) {
533
+ this.#cache = /* @__PURE__ */ new WeakMap();
534
+ this.#lastWidth = this.graphics.canvas.clientWidth;
535
+ } else {
536
+ const result = this.#cache.get(node);
537
+ if (result != null) return result;
538
+ }
539
+ const result = node.measure(ctx ?? this.context);
540
+ this.#cache.set(node, result);
541
+ return result;
542
+ }
543
+ };
544
+ var DebugRenderer = class extends BaseRenderer {
545
+ draw(node) {
546
+ const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
547
+ this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
548
+ return node.draw(this.context, 0, 0);
549
+ }
550
+ hittest(node, test) {
551
+ return node.hittest(this.context, test);
552
+ }
553
+ };
554
+ function memoRenderItem(renderItem) {
555
+ const cache = /* @__PURE__ */ new WeakMap();
556
+ function fn(item) {
557
+ const key = item;
558
+ const cached = cache.get(key);
559
+ if (cached != null) return cached;
560
+ const result = renderItem(item);
561
+ cache.set(key, result);
562
+ return result;
563
+ }
564
+ return Object.assign(fn, { reset: (key) => cache.delete(key) });
565
+ }
566
+ var ListState = class {
567
+ offset = 0;
568
+ position = NaN;
569
+ items = [];
570
+ unshift(...items) {
571
+ this.unshiftAll(items);
572
+ }
573
+ unshiftAll(items) {
574
+ this.position += items.length;
575
+ this.items = items.concat(this.items);
576
+ }
577
+ push(...items) {
578
+ this.pushAll(items);
579
+ }
580
+ pushAll(items) {
581
+ this.items.push(...items);
582
+ }
583
+ reset() {
584
+ this.items = [];
585
+ this.offset = 0;
586
+ this.position = NaN;
587
+ }
588
+ resetScroll() {
589
+ this.offset = 0;
590
+ this.position = NaN;
591
+ }
592
+ applyScroll(delta) {
593
+ this.offset += delta;
594
+ }
595
+ };
596
+ var VirtualizedRenderer = class extends BaseRenderer {
597
+ get position() {
598
+ return this.options.list.position;
599
+ }
600
+ set position(value) {
601
+ this.options.list.position = value;
602
+ }
603
+ get offset() {
604
+ return this.options.list.offset;
605
+ }
606
+ set offset(value) {
607
+ this.options.list.offset = value;
608
+ }
609
+ get items() {
610
+ return this.options.list.items;
611
+ }
612
+ set items(value) {
613
+ this.options.list.items = value;
614
+ }
615
+ _renderDrawList(list, shift, feedback) {
616
+ let result = false;
617
+ const viewportHeight = this.graphics.canvas.clientHeight;
618
+ for (const { idx, node, offset, height } of list) {
619
+ const y = offset + shift;
620
+ if (y + height < 0 || y > viewportHeight) continue;
621
+ if (feedback != null) {
622
+ feedback.minIdx = Number.isNaN(feedback.minIdx) ? idx : Math.min(idx, feedback.minIdx);
623
+ feedback.maxIdx = Number.isNaN(feedback.maxIdx) ? idx : Math.max(idx, feedback.maxIdx);
624
+ if (feedback.minIdx === idx) feedback.min = idx - Math.min(0, y) / height;
625
+ if (feedback.maxIdx === idx) feedback.max = idx - Math.max(0, y + height - viewportHeight) / height;
626
+ }
627
+ if (node.draw(this.context, 0, y)) result = true;
628
+ }
629
+ return result;
630
+ }
631
+ };
632
+ var TimelineRenderer = class extends VirtualizedRenderer {
633
+ render(feedback) {
634
+ const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
635
+ this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
636
+ let drawLength = 0;
637
+ if (Number.isNaN(this.position)) this.position = 0;
638
+ if (this.offset > 0) if (this.position === 0) this.offset = 0;
639
+ else {
640
+ for (let i = this.position - 1; i >= 0; i -= 1) {
641
+ const item = this.items[i];
642
+ const node = this.options.renderItem(item);
643
+ const { height } = this.measureNode(node);
644
+ this.position = i;
645
+ this.offset -= height;
646
+ if (this.offset <= 0) break;
647
+ }
648
+ if (this.position === 0 && this.offset > 0) this.offset = 0;
649
+ }
650
+ let y = this.offset;
651
+ const drawList = [];
652
+ for (let i = this.position; i < this.items.length; i += 1) {
653
+ const item = this.items[i];
654
+ const node = this.options.renderItem(item);
655
+ const { height } = this.measureNode(node);
656
+ if (y + height > 0) {
657
+ drawList.push({
658
+ idx: i,
659
+ node,
660
+ offset: y,
661
+ height
662
+ });
663
+ drawLength += height;
664
+ } else {
665
+ this.offset += height;
666
+ this.position = i + 1;
667
+ }
668
+ y += height;
669
+ if (y >= viewportHeight) break;
670
+ }
671
+ let shift = 0;
672
+ if (y < viewportHeight) if (this.position === 0 && drawLength < viewportHeight) {
673
+ shift = -this.offset;
674
+ this.offset = 0;
675
+ } else {
676
+ shift = viewportHeight - y;
677
+ y = this.offset += shift;
678
+ let lastIdx = -1;
679
+ for (let i = this.position - 1; i >= 0; i -= 1) {
680
+ const item = this.items[lastIdx = i];
681
+ const node = this.options.renderItem(item);
682
+ const { height } = this.measureNode(node);
683
+ drawLength += height;
684
+ y -= height;
685
+ drawList.push({
686
+ idx: i,
687
+ node,
688
+ offset: y - shift,
689
+ height
690
+ });
691
+ if (y < 0) break;
692
+ }
693
+ if (lastIdx === 0 && drawLength < viewportHeight) {
694
+ shift = -drawList[drawList.length - 1].offset;
695
+ this.position = 0;
696
+ this.offset = 0;
697
+ }
698
+ }
699
+ return this._renderDrawList(drawList, shift, feedback);
700
+ }
701
+ hittest(test) {
702
+ const viewportHeight = this.graphics.canvas.clientHeight;
703
+ let y = this.offset;
704
+ for (let i = this.position; i < this.items.length; i += 1) {
705
+ const item = this.items[i];
706
+ const node = this.options.renderItem(item);
707
+ const { height } = this.measureNode(node);
708
+ if (test.y < y + height) return node.hittest(this.context, shallowMerge(test, { y: test.y - y }));
709
+ y += height;
710
+ if (y >= viewportHeight) break;
711
+ }
712
+ return false;
713
+ }
714
+ };
715
+ var ChatRenderer = class extends VirtualizedRenderer {
716
+ render(feedback) {
717
+ const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
718
+ this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
719
+ let drawLength = 0;
720
+ if (Number.isNaN(this.position)) this.position = this.items.length - 1;
721
+ if (this.offset < 0) if (this.position === this.items.length - 1) this.offset = 0;
722
+ else for (let i = this.position + 1; i < this.items.length; i += 1) {
723
+ const item = this.items[i];
724
+ const node = this.options.renderItem(item);
725
+ const { height } = this.measureNode(node);
726
+ this.position = i;
727
+ this.offset += height;
728
+ if (this.offset > 0) break;
729
+ }
730
+ let y = viewportHeight + this.offset;
731
+ const drawList = [];
732
+ for (let i = this.position; i >= 0; i -= 1) {
733
+ const item = this.items[i];
734
+ const node = this.options.renderItem(item);
735
+ const { height } = this.measureNode(node);
736
+ y -= height;
737
+ if (y <= viewportHeight) {
738
+ drawList.push({
739
+ idx: i,
740
+ node,
741
+ offset: y,
742
+ height
743
+ });
744
+ drawLength += height;
745
+ } else {
746
+ this.offset -= height;
747
+ this.position = i - 1;
748
+ }
749
+ if (y < 0) break;
750
+ }
751
+ let shift = 0;
752
+ if (y > 0) {
753
+ shift = -y;
754
+ if (drawLength < viewportHeight) {
755
+ y = drawLength;
756
+ for (let i = this.position + 1; i < this.items.length; i += 1) {
757
+ const item = this.items[i];
758
+ const node = this.options.renderItem(item);
759
+ const { height } = this.measureNode(node);
760
+ drawList.push({
761
+ idx: i,
762
+ node,
763
+ offset: y - shift,
764
+ height
765
+ });
766
+ y = drawLength += height;
767
+ this.position = i;
768
+ if (y >= viewportHeight) break;
769
+ }
770
+ if (drawLength < viewportHeight) this.offset = 0;
771
+ else this.offset = drawLength - viewportHeight;
772
+ } else this.offset = drawLength - viewportHeight;
773
+ }
774
+ return this._renderDrawList(drawList, shift, feedback);
775
+ }
776
+ hittest(test) {
777
+ const viewportHeight = this.graphics.canvas.clientHeight;
778
+ let drawLength = 0;
779
+ const heights = [];
780
+ for (let i = this.position; i >= 0; i -= 1) {
781
+ const item = this.items[i];
782
+ const node = this.options.renderItem(item);
783
+ const { height } = this.measureNode(node);
784
+ drawLength += height;
785
+ heights.push([node, height]);
786
+ }
787
+ let y = drawLength < viewportHeight ? drawLength : viewportHeight + this.offset;
788
+ if (test.y > y) return false;
789
+ for (const [node, height] of heights) {
790
+ y -= height;
791
+ if (test.y > y) return node.hittest(this.context, shallowMerge(test, { y: test.y - y }));
792
+ }
793
+ return false;
794
+ }
795
+ };
796
+ //#endregion
797
+ export { AlignBox, BaseRenderer, ChatRenderer, DebugRenderer, Fixed, Group, HStack, ListState, MultilineText, PaddingBox, Text, TimelineRenderer, VStack, VirtualizedRenderer, Wrapper, getNodeParent, memoRenderItem, registerNodeParent, unregisterNodeParent };
798
+
799
+ //# sourceMappingURL=index.mjs.map