@graphrefly/graphrefly 0.24.0 → 0.25.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.
Files changed (33) hide show
  1. package/dist/{chunk-IPLKX3L2.js → chunk-EVR6UFUV.js} +2 -2
  2. package/dist/{chunk-5WGT55R4.js → chunk-IAHGTNOZ.js} +4 -2
  3. package/dist/{chunk-5WGT55R4.js.map → chunk-IAHGTNOZ.js.map} +1 -1
  4. package/dist/{chunk-AOCBDH4T.js → chunk-L2GLW2U7.js} +68 -1
  5. package/dist/chunk-L2GLW2U7.js.map +1 -0
  6. package/dist/{chunk-TDEXAMGO.js → chunk-TKE3JGOH.js} +488 -16
  7. package/dist/chunk-TKE3JGOH.js.map +1 -0
  8. package/dist/compat/nestjs/index.cjs.map +1 -1
  9. package/dist/compat/nestjs/index.js +2 -2
  10. package/dist/extra/index.cjs +68 -0
  11. package/dist/extra/index.cjs.map +1 -1
  12. package/dist/extra/index.d.cts +1 -1
  13. package/dist/extra/index.d.ts +1 -1
  14. package/dist/extra/index.js +4 -2
  15. package/dist/{index-1z8vRTCt.d.cts → index-Ch0IpIO0.d.cts} +29 -2
  16. package/dist/{index-b5BYtczN.d.cts → index-DKE1EATr.d.cts} +222 -2
  17. package/dist/{index-BysCTzJz.d.ts → index-Ds23Wvou.d.ts} +29 -2
  18. package/dist/{index-D7XgsUt7.d.ts → index-OXImXMq6.d.ts} +222 -2
  19. package/dist/index.cjs +550 -15
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +3 -3
  22. package/dist/index.d.ts +3 -3
  23. package/dist/index.js +6 -4
  24. package/dist/index.js.map +1 -1
  25. package/dist/patterns/reactive-layout/index.cjs +488 -16
  26. package/dist/patterns/reactive-layout/index.cjs.map +1 -1
  27. package/dist/patterns/reactive-layout/index.d.cts +1 -1
  28. package/dist/patterns/reactive-layout/index.d.ts +1 -1
  29. package/dist/patterns/reactive-layout/index.js +16 -4
  30. package/package.json +1 -1
  31. package/dist/chunk-AOCBDH4T.js.map +0 -1
  32. package/dist/chunk-TDEXAMGO.js.map +0 -1
  33. /package/dist/{chunk-IPLKX3L2.js.map → chunk-EVR6UFUV.js.map} +0 -0
@@ -22,14 +22,20 @@ __export(reactive_layout_exports, {
22
22
  PrecomputedAdapter: () => PrecomputedAdapter,
23
23
  SvgBoundsAdapter: () => SvgBoundsAdapter,
24
24
  analyzeAndMeasure: () => analyzeAndMeasure,
25
+ carveTextLineSlots: () => carveTextLineSlots,
26
+ circleIntervalForBand: () => circleIntervalForBand,
25
27
  computeBlockFlow: () => computeBlockFlow,
26
28
  computeCharPositions: () => computeCharPositions,
29
+ computeFlowLines: () => computeFlowLines,
27
30
  computeLineBreaks: () => computeLineBreaks,
28
31
  computeTotalHeight: () => computeTotalHeight,
32
+ layoutNextLine: () => layoutNextLine,
29
33
  measureBlock: () => measureBlock,
30
34
  measureBlocks: () => measureBlocks,
31
35
  reactiveBlockLayout: () => reactiveBlockLayout,
32
- reactiveLayout: () => reactiveLayout
36
+ reactiveFlowLayout: () => reactiveFlowLayout,
37
+ reactiveLayout: () => reactiveLayout,
38
+ rectIntervalForBand: () => rectIntervalForBand
33
39
  });
34
40
 
35
41
  // src/patterns/reactive-layout/measurement-adapters.ts
@@ -408,7 +414,7 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
408
414
  const normalized = normalizeWhitespace(text);
409
415
  if (normalized.length === 0) return [];
410
416
  const pieces = segmentText(normalized);
411
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
417
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
412
418
  granularity: "grapheme"
413
419
  });
414
420
  const rawTexts = [];
@@ -452,7 +458,8 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
452
458
  let w = fontCache.get(seg);
453
459
  if (w === void 0) {
454
460
  if (stats) stats.misses += 1;
455
- w = adapter.measureSegment(seg, font).width;
461
+ const raw = adapter.measureSegment(seg, font).width;
462
+ w = Number.isFinite(raw) && raw >= 0 ? raw : 0;
456
463
  fontCache.set(seg, w);
457
464
  } else if (stats) {
458
465
  stats.hits += 1;
@@ -474,7 +481,7 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
474
481
  }
475
482
  if (isCJK(t)) {
476
483
  let unitText = "";
477
- for (const gs of graphemeSegmenter.segment(t)) {
484
+ for (const gs of graphemeSegmenter2.segment(t)) {
478
485
  const grapheme = gs.segment;
479
486
  if (unitText.length > 0 && kinsokuStart.has(grapheme)) {
480
487
  unitText += grapheme;
@@ -506,7 +513,7 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
506
513
  let graphemeWidths = null;
507
514
  if (mergedWordLike[i] && t.length > 1) {
508
515
  const gWidths = [];
509
- for (const gs of graphemeSegmenter.segment(t)) {
516
+ for (const gs of graphemeSegmenter2.segment(t)) {
510
517
  gWidths.push(measureCached(gs.segment));
511
518
  }
512
519
  if (gWidths.length > 1) {
@@ -546,10 +553,10 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
546
553
  const seg = segments[i];
547
554
  if (seg.kind === "soft-hyphen" || seg.kind === "hard-break") continue;
548
555
  if (i === lineStartSeg && lineStartGrapheme > 0 && seg.graphemeWidths) {
549
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
556
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
550
557
  granularity: "grapheme"
551
558
  });
552
- const graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);
559
+ const graphemes = [...graphemeSegmenter2.segment(seg.text)].map((g) => g.segment);
553
560
  text += graphemes.slice(lineStartGrapheme).join("");
554
561
  } else {
555
562
  text += seg.text;
@@ -557,10 +564,10 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
557
564
  }
558
565
  if (endGrapheme > 0 && endSeg < segments.length) {
559
566
  const seg = segments[endSeg];
560
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
567
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
561
568
  granularity: "grapheme"
562
569
  });
563
- const graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);
570
+ const graphemes = [...graphemeSegmenter2.segment(seg.text)].map((g) => g.segment);
564
571
  const startG = lineStartSeg === endSeg ? lineStartGrapheme : 0;
565
572
  text += graphemes.slice(startG, endGrapheme).join("");
566
573
  }
@@ -580,7 +587,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
580
587
  pendingBreakSeg = -1;
581
588
  pendingBreakWidth = 0;
582
589
  }
583
- function canBreakAfter(kind) {
590
+ function canBreakAfter2(kind) {
584
591
  return kind === "space" || kind === "zero-width-break" || kind === "soft-hyphen";
585
592
  }
586
593
  function startLine(segIdx, graphemeIdx, width) {
@@ -625,7 +632,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
625
632
  } else {
626
633
  startLine(i, 0, w);
627
634
  }
628
- if (canBreakAfter(seg.kind)) {
635
+ if (canBreakAfter2(seg.kind)) {
629
636
  pendingBreakSeg = i + 1;
630
637
  pendingBreakWidth = seg.kind === "space" ? lineW - w : lineW;
631
638
  }
@@ -633,7 +640,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
633
640
  }
634
641
  const newW = lineW + w;
635
642
  if (newW > maxWidth + 5e-3) {
636
- if (canBreakAfter(seg.kind)) {
643
+ if (canBreakAfter2(seg.kind)) {
637
644
  lineW += w;
638
645
  lineEndSeg = i + 1;
639
646
  lineEndGrapheme = 0;
@@ -657,7 +664,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
657
664
  lineW = newW;
658
665
  lineEndSeg = i + 1;
659
666
  lineEndGrapheme = 0;
660
- if (canBreakAfter(seg.kind)) {
667
+ if (canBreakAfter2(seg.kind)) {
661
668
  pendingBreakSeg = i + 1;
662
669
  pendingBreakWidth = seg.kind === "space" ? lineW - w : lineW;
663
670
  }
@@ -688,9 +695,289 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
688
695
  }
689
696
  }
690
697
  }
698
+ function canBreakAfter(kind) {
699
+ return kind === "space" || kind === "zero-width-break" || kind === "soft-hyphen";
700
+ }
701
+ var _graphemeSegmenter = null;
702
+ function graphemeSegmenter() {
703
+ if (_graphemeSegmenter === null) {
704
+ _graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
705
+ }
706
+ return _graphemeSegmenter;
707
+ }
708
+ function sliceSegmentText(seg, startG, endG) {
709
+ if (startG === 0 && endG < 0) return seg.text;
710
+ const graphemes = [...graphemeSegmenter().segment(seg.text)].map((g) => g.segment);
711
+ const stop = endG < 0 ? graphemes.length : endG;
712
+ return graphemes.slice(startG, stop).join("");
713
+ }
714
+ function buildLineText(segments, startSeg, startG, endSeg, endG, appendHyphen) {
715
+ let text = "";
716
+ for (let i = startSeg; i < endSeg; i++) {
717
+ const seg = segments[i];
718
+ if (seg.kind === "soft-hyphen" || seg.kind === "hard-break") continue;
719
+ if (i === startSeg && startG > 0) {
720
+ text += sliceSegmentText(seg, startG, -1);
721
+ } else {
722
+ text += seg.text;
723
+ }
724
+ }
725
+ if (endG > 0 && endSeg < segments.length) {
726
+ const seg = segments[endSeg];
727
+ const from = startSeg === endSeg ? startG : 0;
728
+ text += sliceSegmentText(seg, from, endG);
729
+ }
730
+ if (appendHyphen) text += "-";
731
+ return text;
732
+ }
733
+ function resolveHyphenWidth(ctx) {
734
+ if (!ctx || !ctx.adapter || !ctx.font) return 0;
735
+ const cache = ctx.cache;
736
+ if (cache) {
737
+ let fc = cache.get(ctx.font);
738
+ if (!fc) {
739
+ fc = /* @__PURE__ */ new Map();
740
+ cache.set(ctx.font, fc);
741
+ }
742
+ let hw = fc.get("-");
743
+ if (hw === void 0) {
744
+ hw = ctx.adapter.measureSegment("-", ctx.font).width;
745
+ fc.set("-", hw);
746
+ }
747
+ return hw;
748
+ }
749
+ return ctx.adapter.measureSegment("-", ctx.font).width;
750
+ }
751
+ function layoutNextLine(segments, cursor, slotWidth, ctx) {
752
+ let i = cursor.segmentIndex;
753
+ const initialG = cursor.graphemeIndex;
754
+ if (i >= segments.length) return null;
755
+ if (initialG === 0) {
756
+ while (i < segments.length) {
757
+ const seg = segments[i];
758
+ if (seg.kind === "hard-break") {
759
+ return {
760
+ text: "",
761
+ width: 0,
762
+ start: { segmentIndex: cursor.segmentIndex, graphemeIndex: 0 },
763
+ end: { segmentIndex: i + 1, graphemeIndex: 0 }
764
+ };
765
+ }
766
+ if (seg.kind === "space" || seg.kind === "zero-width-break" || seg.kind === "soft-hyphen") {
767
+ i += 1;
768
+ continue;
769
+ }
770
+ break;
771
+ }
772
+ if (i >= segments.length) return null;
773
+ }
774
+ const hyphenWidth = resolveHyphenWidth(ctx);
775
+ const startSeg = i;
776
+ const startG = i === cursor.segmentIndex ? initialG : 0;
777
+ let lineW = 0;
778
+ let lineEndSeg = startSeg;
779
+ let lineEndG = 0;
780
+ let hasContent = false;
781
+ let pendingBreakSeg = -1;
782
+ let pendingBreakG = 0;
783
+ let pendingBreakWidth = 0;
784
+ let pendingBreakSoftHyphen = false;
785
+ const recordPending = (sIdx, gIdx, widthAtBreak, kind) => {
786
+ pendingBreakSeg = sIdx;
787
+ pendingBreakG = gIdx;
788
+ pendingBreakWidth = widthAtBreak;
789
+ pendingBreakSoftHyphen = kind === "soft-hyphen";
790
+ };
791
+ const consumeBreakable = (segIdx, gStart, gWidths) => {
792
+ for (let g = gStart; g < gWidths.length; g++) {
793
+ const gw = gWidths[g];
794
+ if (!hasContent) {
795
+ lineW = gw;
796
+ lineEndSeg = segIdx;
797
+ lineEndG = g + 1;
798
+ hasContent = true;
799
+ continue;
800
+ }
801
+ if (lineW + gw > slotWidth + 5e-3) {
802
+ return true;
803
+ }
804
+ lineW += gw;
805
+ lineEndSeg = segIdx;
806
+ lineEndG = g + 1;
807
+ }
808
+ if (lineEndSeg === segIdx && lineEndG === gWidths.length) {
809
+ lineEndSeg = segIdx + 1;
810
+ lineEndG = 0;
811
+ }
812
+ return false;
813
+ };
814
+ if (startG > 0 && startSeg < segments.length) {
815
+ const seg = segments[startSeg];
816
+ if (seg.graphemeWidths) {
817
+ const overflowed = consumeBreakable(startSeg, startG, seg.graphemeWidths);
818
+ if (overflowed) {
819
+ const text2 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
820
+ return {
821
+ text: text2,
822
+ width: lineW,
823
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
824
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
825
+ };
826
+ }
827
+ i = lineEndSeg;
828
+ } else {
829
+ }
830
+ }
831
+ for (; i < segments.length; ) {
832
+ const seg = segments[i];
833
+ if (seg.kind === "hard-break") {
834
+ if (hasContent) {
835
+ const endsAtSoftHyphen2 = lineEndSeg > 0 && segments[lineEndSeg - 1]?.kind === "soft-hyphen";
836
+ const text2 = buildLineText(
837
+ segments,
838
+ startSeg,
839
+ startG,
840
+ lineEndSeg,
841
+ lineEndG,
842
+ endsAtSoftHyphen2
843
+ );
844
+ return {
845
+ text: text2,
846
+ width: lineW + (endsAtSoftHyphen2 ? hyphenWidth : 0),
847
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
848
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
849
+ };
850
+ }
851
+ return {
852
+ text: "",
853
+ width: 0,
854
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
855
+ end: { segmentIndex: i + 1, graphemeIndex: 0 }
856
+ };
857
+ }
858
+ const w = seg.width;
859
+ if (!hasContent) {
860
+ if (w > slotWidth && seg.graphemeWidths) {
861
+ const overflowed = consumeBreakable(i, 0, seg.graphemeWidths);
862
+ if (overflowed) {
863
+ const text2 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
864
+ return {
865
+ text: text2,
866
+ width: lineW,
867
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
868
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
869
+ };
870
+ }
871
+ i = lineEndSeg;
872
+ continue;
873
+ }
874
+ lineW = w;
875
+ lineEndSeg = i + 1;
876
+ lineEndG = 0;
877
+ hasContent = true;
878
+ if (canBreakAfter(seg.kind)) {
879
+ recordPending(i + 1, 0, seg.kind === "space" ? lineW - w : lineW, seg.kind);
880
+ }
881
+ i += 1;
882
+ continue;
883
+ }
884
+ const newW = lineW + w;
885
+ if (newW > slotWidth + 5e-3) {
886
+ if (canBreakAfter(seg.kind)) {
887
+ lineEndSeg = i + 1;
888
+ lineEndG = 0;
889
+ const endsAtSoftHyphen2 = seg.kind === "soft-hyphen";
890
+ const finalWidth = seg.kind === "space" ? lineW : lineW + (endsAtSoftHyphen2 ? hyphenWidth : 0);
891
+ const text3 = buildLineText(
892
+ segments,
893
+ startSeg,
894
+ startG,
895
+ lineEndSeg,
896
+ lineEndG,
897
+ endsAtSoftHyphen2
898
+ );
899
+ return {
900
+ text: text3,
901
+ width: finalWidth,
902
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
903
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
904
+ };
905
+ }
906
+ if (pendingBreakSeg >= 0) {
907
+ const text3 = buildLineText(
908
+ segments,
909
+ startSeg,
910
+ startG,
911
+ pendingBreakSeg,
912
+ pendingBreakG,
913
+ pendingBreakSoftHyphen
914
+ );
915
+ return {
916
+ text: text3,
917
+ width: pendingBreakWidth + (pendingBreakSoftHyphen ? hyphenWidth : 0),
918
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
919
+ end: { segmentIndex: pendingBreakSeg, graphemeIndex: pendingBreakG }
920
+ };
921
+ }
922
+ if (w > slotWidth && seg.graphemeWidths) {
923
+ const text3 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
924
+ return {
925
+ text: text3,
926
+ width: lineW,
927
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
928
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
929
+ };
930
+ }
931
+ const text2 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
932
+ return {
933
+ text: text2,
934
+ width: lineW,
935
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
936
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
937
+ };
938
+ }
939
+ lineW = newW;
940
+ lineEndSeg = i + 1;
941
+ lineEndG = 0;
942
+ if (canBreakAfter(seg.kind)) {
943
+ recordPending(i + 1, 0, seg.kind === "space" ? lineW - w : lineW, seg.kind);
944
+ }
945
+ i += 1;
946
+ }
947
+ if (!hasContent) return null;
948
+ const endsAtSoftHyphen = lineEndSeg > 0 && segments[lineEndSeg - 1]?.kind === "soft-hyphen";
949
+ const text = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, endsAtSoftHyphen);
950
+ return {
951
+ text,
952
+ width: lineW + (endsAtSoftHyphen ? hyphenWidth : 0),
953
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
954
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
955
+ };
956
+ }
957
+ function carveTextLineSlots(base, blocked, minSlotWidth = 0) {
958
+ let slots = [base];
959
+ for (let bi = 0; bi < blocked.length; bi++) {
960
+ const block = blocked[bi];
961
+ const next = [];
962
+ for (let si = 0; si < slots.length; si++) {
963
+ const slot = slots[si];
964
+ if (block.right <= slot.left || block.left >= slot.right) {
965
+ next.push(slot);
966
+ continue;
967
+ }
968
+ if (block.left > slot.left) next.push({ left: slot.left, right: block.left });
969
+ if (block.right < slot.right) next.push({ left: block.right, right: slot.right });
970
+ }
971
+ slots = next;
972
+ }
973
+ if (minSlotWidth > 0) {
974
+ return slots.filter((s) => s.right - s.left >= minSlotWidth);
975
+ }
976
+ return slots;
977
+ }
691
978
  function computeCharPositions(lineBreaks, segments, lineHeight) {
692
979
  const positions = [];
693
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
980
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
694
981
  granularity: "grapheme"
695
982
  });
696
983
  for (let lineIdx = 0; lineIdx < lineBreaks.lines.length; lineIdx++) {
@@ -703,7 +990,7 @@ function computeCharPositions(lineBreaks, segments, lineHeight) {
703
990
  if (si >= line.endSegment && line.endGrapheme === 0) break;
704
991
  continue;
705
992
  }
706
- const graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);
993
+ const graphemes = [...graphemeSegmenter2.segment(seg.text)].map((g) => g.segment);
707
994
  if (graphemes.length === 0) continue;
708
995
  const startG = si === line.startSegment ? line.startGrapheme : 0;
709
996
  let endG;
@@ -1060,9 +1347,190 @@ function reactiveBlockLayout(opts) {
1060
1347
  };
1061
1348
  }
1062
1349
 
1350
+ // src/patterns/reactive-layout/reactive-flow-layout.ts
1351
+ function circleIntervalForBand(o, bandTop, bandBottom) {
1352
+ const hPad = o.hPad ?? 0;
1353
+ const vPad = o.vPad ?? 0;
1354
+ const top = bandTop - vPad;
1355
+ const bottom = bandBottom + vPad;
1356
+ if (top >= o.cy + o.r || bottom <= o.cy - o.r) return null;
1357
+ const minDy = o.cy >= top && o.cy <= bottom ? 0 : o.cy < top ? top - o.cy : o.cy - bottom;
1358
+ if (minDy >= o.r) return null;
1359
+ const maxDx = Math.sqrt(o.r * o.r - minDy * minDy);
1360
+ return { left: o.cx - maxDx - hPad, right: o.cx + maxDx + hPad };
1361
+ }
1362
+ function rectIntervalForBand(o, bandTop, bandBottom) {
1363
+ const hPad = o.hPad ?? 0;
1364
+ const vPad = o.vPad ?? 0;
1365
+ if (bandBottom <= o.y - vPad) return null;
1366
+ if (bandTop >= o.y + o.h + vPad) return null;
1367
+ return { left: o.x - hPad, right: o.x + o.w + hPad };
1368
+ }
1369
+ function obstacleIntervalForBand(o, bandTop, bandBottom) {
1370
+ return o.kind === "circle" ? circleIntervalForBand(o, bandTop, bandBottom) : rectIntervalForBand(o, bandTop, bandBottom);
1371
+ }
1372
+ function computeFlowLines(segments, container, columns, obstacles, lineHeight, minSlotWidth) {
1373
+ const lines = [];
1374
+ let cursor = { segmentIndex: 0, graphemeIndex: 0 };
1375
+ if (segments.length === 0 || columns.count <= 0 || lineHeight <= 0) {
1376
+ return { lines, cursor };
1377
+ }
1378
+ const padX = container.paddingX ?? 0;
1379
+ const padY = container.paddingY ?? 0;
1380
+ const availWidth = Math.max(0, container.width - padX * 2);
1381
+ const availHeight = Math.max(0, container.height - padY * 2);
1382
+ const gapTotal = columns.gap * Math.max(0, columns.count - 1);
1383
+ const colWidth = Math.max(0, (availWidth - gapTotal) / columns.count);
1384
+ if (colWidth <= 0) return { lines, cursor };
1385
+ outerCol: for (let col = 0; col < columns.count; col++) {
1386
+ const colLeft = padX + col * (colWidth + columns.gap);
1387
+ const colRight = colLeft + colWidth;
1388
+ let bandTop = padY;
1389
+ while (bandTop + lineHeight <= padY + availHeight) {
1390
+ const bandBottom = bandTop + lineHeight;
1391
+ const blocked = [];
1392
+ for (let oi = 0; oi < obstacles.length; oi++) {
1393
+ const iv = obstacleIntervalForBand(obstacles[oi], bandTop, bandBottom);
1394
+ if (iv !== null) blocked.push(iv);
1395
+ }
1396
+ const slots = carveTextLineSlots({ left: colLeft, right: colRight }, blocked, minSlotWidth);
1397
+ if (slots.length === 0) {
1398
+ bandTop += lineHeight;
1399
+ continue;
1400
+ }
1401
+ let hardBreakThisBand = false;
1402
+ for (let si = 0; si < slots.length; si++) {
1403
+ const slot = slots[si];
1404
+ const slotW = slot.right - slot.left;
1405
+ const line = layoutNextLine(segments, cursor, slotW);
1406
+ if (line === null) {
1407
+ return { lines, cursor };
1408
+ }
1409
+ if (line.text.length === 0 && line.width === 0) {
1410
+ cursor = line.end;
1411
+ hardBreakThisBand = true;
1412
+ break;
1413
+ }
1414
+ lines.push({
1415
+ x: slot.left,
1416
+ y: bandTop,
1417
+ width: line.width,
1418
+ slotWidth: slotW,
1419
+ text: line.text,
1420
+ columnIndex: col,
1421
+ flushToRight: slot.right < colRight - 0.5
1422
+ });
1423
+ cursor = line.end;
1424
+ }
1425
+ bandTop += lineHeight;
1426
+ if (hardBreakThisBand) continue;
1427
+ if (cursor.segmentIndex >= segments.length) break outerCol;
1428
+ }
1429
+ if (cursor.segmentIndex >= segments.length) break;
1430
+ }
1431
+ return { lines, cursor };
1432
+ }
1433
+ function reactiveFlowLayout(opts) {
1434
+ const { adapter, name = "reactive-flow-layout", minSlotWidth = 20 } = opts;
1435
+ const g = new Graph(name);
1436
+ const measureCache = /* @__PURE__ */ new Map();
1437
+ const textNode = state(opts.text ?? "", { name: "text" });
1438
+ const fontNode = state(opts.font ?? "16px sans-serif", { name: "font" });
1439
+ const lineHeightNode = state(opts.lineHeight ?? 20, { name: "line-height" });
1440
+ const containerNode = state(
1441
+ opts.container ?? { width: 800, height: 600, paddingX: 0, paddingY: 0 },
1442
+ { name: "container" }
1443
+ );
1444
+ const columnsNode = state(opts.columns ?? { count: 1, gap: 0 }, {
1445
+ name: "columns"
1446
+ });
1447
+ const obstaclesNode = state(opts.obstacles ?? [], { name: "obstacles" });
1448
+ const segmentsNode = node(
1449
+ [textNode, fontNode],
1450
+ (data, actions, ctx) => {
1451
+ const b0 = data[0];
1452
+ const textVal = b0 != null && b0.length > 0 ? b0.at(-1) : ctx.prevData[0];
1453
+ const b1 = data[1];
1454
+ const fontVal = b1 != null && b1.length > 0 ? b1.at(-1) : ctx.prevData[1];
1455
+ const result = analyzeAndMeasure(textVal, fontVal, adapter, measureCache);
1456
+ actions.emit(result);
1457
+ return () => {
1458
+ measureCache.clear();
1459
+ adapter.clearCache?.();
1460
+ };
1461
+ },
1462
+ { name: "segments", describeKind: "derived" }
1463
+ );
1464
+ const flowLinesNode = derived(
1465
+ [segmentsNode, containerNode, columnsNode, obstaclesNode, lineHeightNode],
1466
+ ([segs, cont, cols, obs, lh]) => {
1467
+ const segments = segs;
1468
+ const t0 = monotonicNs();
1469
+ const { lines: result, cursor } = computeFlowLines(
1470
+ segments,
1471
+ cont,
1472
+ cols,
1473
+ obs,
1474
+ lh,
1475
+ minSlotWidth
1476
+ );
1477
+ const elapsed = monotonicNs() - t0;
1478
+ const overflow = Math.max(0, segments.length - cursor.segmentIndex);
1479
+ const meta = flowLinesNode.meta;
1480
+ if (meta) {
1481
+ emitToMeta(meta["line-count"], result.length);
1482
+ emitToMeta(meta["layout-time-ns"], elapsed);
1483
+ emitToMeta(meta["overflow-segments"], overflow);
1484
+ }
1485
+ return result;
1486
+ },
1487
+ {
1488
+ name: "flow-lines",
1489
+ meta: {
1490
+ "line-count": 0,
1491
+ "layout-time-ns": 0,
1492
+ "overflow-segments": 0
1493
+ },
1494
+ equals: (a, b) => {
1495
+ const la = a;
1496
+ const lb = b;
1497
+ if (la.length !== lb.length) return false;
1498
+ for (let i = 0; i < la.length; i++) {
1499
+ const pa = la[i];
1500
+ const pb = lb[i];
1501
+ if (pa.x !== pb.x || pa.y !== pb.y || pa.width !== pb.width || pa.slotWidth !== pb.slotWidth || pa.text !== pb.text || pa.columnIndex !== pb.columnIndex || pa.flushToRight !== pb.flushToRight)
1502
+ return false;
1503
+ }
1504
+ return true;
1505
+ }
1506
+ }
1507
+ );
1508
+ g.add("text", textNode);
1509
+ g.add("font", fontNode);
1510
+ g.add("line-height", lineHeightNode);
1511
+ g.add("container", containerNode);
1512
+ g.add("columns", columnsNode);
1513
+ g.add("obstacles", obstaclesNode);
1514
+ g.add("segments", segmentsNode);
1515
+ g.add("flow-lines", flowLinesNode);
1516
+ return {
1517
+ graph: g,
1518
+ setText: (t) => g.set("text", t),
1519
+ setFont: (f) => g.set("font", f),
1520
+ setLineHeight: (lh) => g.set("line-height", lh),
1521
+ setContainer: (c) => g.set("container", c),
1522
+ setColumns: (c) => g.set("columns", c),
1523
+ setObstacles: (o) => g.set("obstacles", o),
1524
+ segments: segmentsNode,
1525
+ flowLines: flowLinesNode
1526
+ };
1527
+ }
1528
+
1063
1529
  export {
1064
1530
  analyzeAndMeasure,
1065
1531
  computeLineBreaks,
1532
+ layoutNextLine,
1533
+ carveTextLineSlots,
1066
1534
  computeCharPositions,
1067
1535
  reactiveLayout,
1068
1536
  CliMeasureAdapter,
@@ -1076,6 +1544,10 @@ export {
1076
1544
  computeBlockFlow,
1077
1545
  computeTotalHeight,
1078
1546
  reactiveBlockLayout,
1547
+ circleIntervalForBand,
1548
+ rectIntervalForBand,
1549
+ computeFlowLines,
1550
+ reactiveFlowLayout,
1079
1551
  reactive_layout_exports
1080
1552
  };
1081
- //# sourceMappingURL=chunk-TDEXAMGO.js.map
1553
+ //# sourceMappingURL=chunk-TKE3JGOH.js.map