@graphrefly/graphrefly 0.23.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 (78) hide show
  1. package/dist/{chunk-NZMBRXQV.js → chunk-5DJTTKX3.js} +11 -13
  2. package/dist/chunk-5DJTTKX3.js.map +1 -0
  3. package/dist/{chunk-PNUZM7PC.js → chunk-EVR6UFUV.js} +6 -6
  4. package/dist/{chunk-HVBX5KIW.js → chunk-H4RVA4VE.js} +2 -2
  5. package/dist/{chunk-32N5A454.js → chunk-HWPIFSW2.js} +2 -2
  6. package/dist/chunk-HWPIFSW2.js.map +1 -0
  7. package/dist/{chunk-XTLYW4FR.js → chunk-IAHGTNOZ.js} +6 -4
  8. package/dist/{chunk-XTLYW4FR.js.map → chunk-IAHGTNOZ.js.map} +1 -1
  9. package/dist/{chunk-CWYPA63G.js → chunk-L2GLW2U7.js} +69 -2
  10. package/dist/chunk-L2GLW2U7.js.map +1 -0
  11. package/dist/{chunk-2GQLMQVJ.js → chunk-MW4VAKAO.js} +3 -3
  12. package/dist/{chunk-JFONSPNF.js → chunk-PY4XCDLR.js} +2 -2
  13. package/dist/{chunk-263BEJJO.js → chunk-QOWVNWOC.js} +3 -3
  14. package/dist/{chunk-PX6PDUJ5.js → chunk-TKE3JGOH.js} +491 -19
  15. package/dist/chunk-TKE3JGOH.js.map +1 -0
  16. package/dist/{chunk-XRFJJ2IU.js → chunk-XOFWRC73.js} +3 -3
  17. package/dist/compat/nestjs/index.cjs +10 -12
  18. package/dist/compat/nestjs/index.cjs.map +1 -1
  19. package/dist/compat/nestjs/index.d.cts +6 -6
  20. package/dist/compat/nestjs/index.d.ts +6 -6
  21. package/dist/compat/nestjs/index.js +9 -9
  22. package/dist/core/index.cjs +10 -12
  23. package/dist/core/index.cjs.map +1 -1
  24. package/dist/core/index.d.cts +3 -3
  25. package/dist/core/index.d.ts +3 -3
  26. package/dist/core/index.js +3 -3
  27. package/dist/extra/index.cjs +78 -12
  28. package/dist/extra/index.cjs.map +1 -1
  29. package/dist/extra/index.d.cts +4 -4
  30. package/dist/extra/index.d.ts +4 -4
  31. package/dist/extra/index.js +6 -4
  32. package/dist/graph/index.cjs +10 -12
  33. package/dist/graph/index.cjs.map +1 -1
  34. package/dist/graph/index.d.cts +5 -5
  35. package/dist/graph/index.d.ts +5 -5
  36. package/dist/graph/index.js +4 -4
  37. package/dist/{graph-CEO2FkLY.d.ts → graph-B6NFqv3z.d.ts} +3 -3
  38. package/dist/{graph-BtdSRHUc.d.cts → graph-D-3JIQme.d.cts} +3 -3
  39. package/dist/{index-BFGjXbiP.d.cts → index-AMWewNDe.d.cts} +2 -2
  40. package/dist/{index-BUj3ASVe.d.cts → index-BJB7t9gg.d.cts} +10 -24
  41. package/dist/{index-DSPc5rkv.d.ts → index-C-TXEa7C.d.ts} +10 -24
  42. package/dist/{index-CkElcUY6.d.ts → index-CYkjxu3s.d.ts} +2 -2
  43. package/dist/{index-B0tfuXwV.d.cts → index-Ch0IpIO0.d.cts} +32 -5
  44. package/dist/{index-C59uSJAH.d.cts → index-DKE1EATr.d.cts} +224 -4
  45. package/dist/{index-DgscL7v0.d.ts → index-DiobMNwE.d.ts} +3 -3
  46. package/dist/{index-RXN94sHK.d.ts → index-Ds23Wvou.d.ts} +32 -5
  47. package/dist/{index-BPlWVAKY.d.cts → index-J7Kc0oIQ.d.cts} +3 -3
  48. package/dist/{index-jEtF4N7L.d.ts → index-OXImXMq6.d.ts} +224 -4
  49. package/dist/index.cjs +570 -47
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +15 -15
  52. package/dist/index.d.ts +15 -15
  53. package/dist/index.js +23 -31
  54. package/dist/index.js.map +1 -1
  55. package/dist/{meta-3QjzotRv.d.ts → meta-CnkLA_43.d.ts} +1 -1
  56. package/dist/{meta-B-Lbs4-O.d.cts → meta-DWbkoq1s.d.cts} +1 -1
  57. package/dist/{node-C7PD3sn9.d.cts → node-B-f-Lu-k.d.cts} +15 -13
  58. package/dist/{node-C7PD3sn9.d.ts → node-B-f-Lu-k.d.ts} +15 -13
  59. package/dist/{observable-axpzv1K2.d.cts → observable-DBnrwcar.d.cts} +1 -1
  60. package/dist/{observable-EyO-moQY.d.ts → observable-uP-wy_uK.d.ts} +1 -1
  61. package/dist/patterns/reactive-layout/index.cjs +498 -28
  62. package/dist/patterns/reactive-layout/index.cjs.map +1 -1
  63. package/dist/patterns/reactive-layout/index.d.cts +5 -5
  64. package/dist/patterns/reactive-layout/index.d.ts +5 -5
  65. package/dist/patterns/reactive-layout/index.js +20 -8
  66. package/dist/{storage-DIgAr7M_.d.cts → storage-BuTdpCI1.d.cts} +1 -1
  67. package/dist/{storage-CHT5WE9m.d.ts → storage-F2X1U1x0.d.ts} +1 -1
  68. package/package.json +2 -2
  69. package/dist/chunk-32N5A454.js.map +0 -1
  70. package/dist/chunk-CWYPA63G.js.map +0 -1
  71. package/dist/chunk-NZMBRXQV.js.map +0 -1
  72. package/dist/chunk-PX6PDUJ5.js.map +0 -1
  73. /package/dist/{chunk-PNUZM7PC.js.map → chunk-EVR6UFUV.js.map} +0 -0
  74. /package/dist/{chunk-HVBX5KIW.js.map → chunk-H4RVA4VE.js.map} +0 -0
  75. /package/dist/{chunk-2GQLMQVJ.js.map → chunk-MW4VAKAO.js.map} +0 -0
  76. /package/dist/{chunk-JFONSPNF.js.map → chunk-PY4XCDLR.js.map} +0 -0
  77. /package/dist/{chunk-263BEJJO.js.map → chunk-QOWVNWOC.js.map} +0 -0
  78. /package/dist/{chunk-XRFJJ2IU.js.map → chunk-XOFWRC73.js.map} +0 -0
@@ -27,14 +27,20 @@ __export(reactive_layout_exports, {
27
27
  PrecomputedAdapter: () => PrecomputedAdapter,
28
28
  SvgBoundsAdapter: () => SvgBoundsAdapter,
29
29
  analyzeAndMeasure: () => analyzeAndMeasure,
30
+ carveTextLineSlots: () => carveTextLineSlots,
31
+ circleIntervalForBand: () => circleIntervalForBand,
30
32
  computeBlockFlow: () => computeBlockFlow,
31
33
  computeCharPositions: () => computeCharPositions,
34
+ computeFlowLines: () => computeFlowLines,
32
35
  computeLineBreaks: () => computeLineBreaks,
33
36
  computeTotalHeight: () => computeTotalHeight,
37
+ layoutNextLine: () => layoutNextLine,
34
38
  measureBlock: () => measureBlock,
35
39
  measureBlocks: () => measureBlocks,
36
40
  reactiveBlockLayout: () => reactiveBlockLayout,
37
- reactiveLayout: () => reactiveLayout
41
+ reactiveFlowLayout: () => reactiveFlowLayout,
42
+ reactiveLayout: () => reactiveLayout,
43
+ rectIntervalForBand: () => rectIntervalForBand
38
44
  });
39
45
  module.exports = __toCommonJS(reactive_layout_exports);
40
46
 
@@ -1954,37 +1960,35 @@ var NodeImpl = class _NodeImpl {
1954
1960
  // --- Emit pipeline ---
1955
1961
  /**
1956
1962
  * @internal The unified dispatch waist — one call = one wave.
1963
+ * See `GRAPHREFLY-SPEC.md` §1.3.1 for protocol context — the stages
1964
+ * below are the implementation order.
1957
1965
  *
1958
1966
  * Pipeline stages, in order:
1959
1967
  *
1960
- * 1. Early-return on empty batch.
1961
- * 2. Terminal filter — post-COMPLETE/ERROR only TEARDOWN/INVALIDATE
1968
+ * 1. Terminal filter — post-COMPLETE/ERROR only TEARDOWN/INVALIDATE
1962
1969
  * still propagate so graph teardown and cache-clear still work.
1963
- * 3. Tier sort (stable) — the batch can be in any order when it
1970
+ * 2. Tier sort (stable) — the batch can be in any order when it
1964
1971
  * arrives; the walker downstream (`downWithBatch`) assumes
1965
1972
  * ascending tier monotone, and so does `_updateState`'s tier-3
1966
1973
  * slice walk. This is the single source of truth for ordering.
1967
- * 4. Synthetic DIRTY prefix — if a tier-3 payload is present, no
1974
+ * 3. Synthetic DIRTY prefix — if a tier-3 payload is present, no
1968
1975
  * DIRTY is already in the batch, and the node isn't already in
1969
1976
  * `"dirty"` status, prepend `[DIRTY]` after any tier-0 START
1970
1977
  * entries. Guarantees spec §1.3.1 (DIRTY precedes DATA within
1971
1978
  * the same batch) uniformly across every entry point.
1972
- * 5. PAUSE/RESUME lock bookkeeping (C0) — update `_pauseLocks`,
1979
+ * 4. PAUSE/RESUME lock bookkeeping (C0) — update `_pauseLocks`,
1973
1980
  * derive `_paused`, filter unknown-lockId RESUME, replay
1974
1981
  * bufferAll buffer on final lock release.
1975
- * 6. Meta TEARDOWN fan-out — notify meta children before
1982
+ * 5. Meta TEARDOWN fan-out — notify meta children before
1976
1983
  * `_updateState`'s TEARDOWN branch calls `_deactivate`. Hoisted
1977
1984
  * out of the walk to keep `_updateState` re-entrance-free.
1978
- * 7. `_updateState` — walk the batch in tier order, advancing
1985
+ * 6. `_updateState` — walk the batch in tier order, advancing
1979
1986
  * `_cached` / `_status` / `_versioning` and running equals
1980
1987
  * substitution on tier-3 DATA (§3.5.1). Returns
1981
1988
  * `{finalMessages, equalsError?}`.
1982
- * 8. `downWithBatch` dispatch (or bufferAll capture if paused with
1989
+ * 7. `downWithBatch` dispatch (or bufferAll capture if paused with
1983
1990
  * `pausable: "resumeAll"`).
1984
- * 9. Recursive ERROR emission if equals threw mid-walk.
1985
- *
1986
- * `node.down` / `node.emit` / `actions.down` / `actions.emit` all
1987
- * converge here — the unified `_emit` waist (spec §1.3.1).
1991
+ * 8. Recursive ERROR emission if equals threw mid-walk.
1988
1992
  */
1989
1993
  _emit(messages) {
1990
1994
  if (messages.length === 0) return;
@@ -4960,7 +4964,7 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
4960
4964
  const normalized = normalizeWhitespace(text);
4961
4965
  if (normalized.length === 0) return [];
4962
4966
  const pieces = segmentText(normalized);
4963
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
4967
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
4964
4968
  granularity: "grapheme"
4965
4969
  });
4966
4970
  const rawTexts = [];
@@ -5004,7 +5008,8 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
5004
5008
  let w = fontCache.get(seg);
5005
5009
  if (w === void 0) {
5006
5010
  if (stats) stats.misses += 1;
5007
- w = adapter.measureSegment(seg, font).width;
5011
+ const raw = adapter.measureSegment(seg, font).width;
5012
+ w = Number.isFinite(raw) && raw >= 0 ? raw : 0;
5008
5013
  fontCache.set(seg, w);
5009
5014
  } else if (stats) {
5010
5015
  stats.hits += 1;
@@ -5026,7 +5031,7 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
5026
5031
  }
5027
5032
  if (isCJK(t)) {
5028
5033
  let unitText = "";
5029
- for (const gs of graphemeSegmenter.segment(t)) {
5034
+ for (const gs of graphemeSegmenter2.segment(t)) {
5030
5035
  const grapheme = gs.segment;
5031
5036
  if (unitText.length > 0 && kinsokuStart.has(grapheme)) {
5032
5037
  unitText += grapheme;
@@ -5058,7 +5063,7 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
5058
5063
  let graphemeWidths = null;
5059
5064
  if (mergedWordLike[i] && t.length > 1) {
5060
5065
  const gWidths = [];
5061
- for (const gs of graphemeSegmenter.segment(t)) {
5066
+ for (const gs of graphemeSegmenter2.segment(t)) {
5062
5067
  gWidths.push(measureCached(gs.segment));
5063
5068
  }
5064
5069
  if (gWidths.length > 1) {
@@ -5098,10 +5103,10 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5098
5103
  const seg = segments[i];
5099
5104
  if (seg.kind === "soft-hyphen" || seg.kind === "hard-break") continue;
5100
5105
  if (i === lineStartSeg && lineStartGrapheme > 0 && seg.graphemeWidths) {
5101
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
5106
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
5102
5107
  granularity: "grapheme"
5103
5108
  });
5104
- const graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);
5109
+ const graphemes = [...graphemeSegmenter2.segment(seg.text)].map((g) => g.segment);
5105
5110
  text += graphemes.slice(lineStartGrapheme).join("");
5106
5111
  } else {
5107
5112
  text += seg.text;
@@ -5109,10 +5114,10 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5109
5114
  }
5110
5115
  if (endGrapheme > 0 && endSeg < segments.length) {
5111
5116
  const seg = segments[endSeg];
5112
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
5117
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
5113
5118
  granularity: "grapheme"
5114
5119
  });
5115
- const graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);
5120
+ const graphemes = [...graphemeSegmenter2.segment(seg.text)].map((g) => g.segment);
5116
5121
  const startG = lineStartSeg === endSeg ? lineStartGrapheme : 0;
5117
5122
  text += graphemes.slice(startG, endGrapheme).join("");
5118
5123
  }
@@ -5132,7 +5137,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5132
5137
  pendingBreakSeg = -1;
5133
5138
  pendingBreakWidth = 0;
5134
5139
  }
5135
- function canBreakAfter(kind) {
5140
+ function canBreakAfter2(kind) {
5136
5141
  return kind === "space" || kind === "zero-width-break" || kind === "soft-hyphen";
5137
5142
  }
5138
5143
  function startLine(segIdx, graphemeIdx, width) {
@@ -5177,7 +5182,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5177
5182
  } else {
5178
5183
  startLine(i, 0, w);
5179
5184
  }
5180
- if (canBreakAfter(seg.kind)) {
5185
+ if (canBreakAfter2(seg.kind)) {
5181
5186
  pendingBreakSeg = i + 1;
5182
5187
  pendingBreakWidth = seg.kind === "space" ? lineW - w : lineW;
5183
5188
  }
@@ -5185,7 +5190,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5185
5190
  }
5186
5191
  const newW = lineW + w;
5187
5192
  if (newW > maxWidth + 5e-3) {
5188
- if (canBreakAfter(seg.kind)) {
5193
+ if (canBreakAfter2(seg.kind)) {
5189
5194
  lineW += w;
5190
5195
  lineEndSeg = i + 1;
5191
5196
  lineEndGrapheme = 0;
@@ -5209,7 +5214,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5209
5214
  lineW = newW;
5210
5215
  lineEndSeg = i + 1;
5211
5216
  lineEndGrapheme = 0;
5212
- if (canBreakAfter(seg.kind)) {
5217
+ if (canBreakAfter2(seg.kind)) {
5213
5218
  pendingBreakSeg = i + 1;
5214
5219
  pendingBreakWidth = seg.kind === "space" ? lineW - w : lineW;
5215
5220
  }
@@ -5240,9 +5245,289 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5240
5245
  }
5241
5246
  }
5242
5247
  }
5248
+ function canBreakAfter(kind) {
5249
+ return kind === "space" || kind === "zero-width-break" || kind === "soft-hyphen";
5250
+ }
5251
+ var _graphemeSegmenter = null;
5252
+ function graphemeSegmenter() {
5253
+ if (_graphemeSegmenter === null) {
5254
+ _graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
5255
+ }
5256
+ return _graphemeSegmenter;
5257
+ }
5258
+ function sliceSegmentText(seg, startG, endG) {
5259
+ if (startG === 0 && endG < 0) return seg.text;
5260
+ const graphemes = [...graphemeSegmenter().segment(seg.text)].map((g) => g.segment);
5261
+ const stop = endG < 0 ? graphemes.length : endG;
5262
+ return graphemes.slice(startG, stop).join("");
5263
+ }
5264
+ function buildLineText(segments, startSeg, startG, endSeg, endG, appendHyphen) {
5265
+ let text = "";
5266
+ for (let i = startSeg; i < endSeg; i++) {
5267
+ const seg = segments[i];
5268
+ if (seg.kind === "soft-hyphen" || seg.kind === "hard-break") continue;
5269
+ if (i === startSeg && startG > 0) {
5270
+ text += sliceSegmentText(seg, startG, -1);
5271
+ } else {
5272
+ text += seg.text;
5273
+ }
5274
+ }
5275
+ if (endG > 0 && endSeg < segments.length) {
5276
+ const seg = segments[endSeg];
5277
+ const from = startSeg === endSeg ? startG : 0;
5278
+ text += sliceSegmentText(seg, from, endG);
5279
+ }
5280
+ if (appendHyphen) text += "-";
5281
+ return text;
5282
+ }
5283
+ function resolveHyphenWidth(ctx) {
5284
+ if (!ctx || !ctx.adapter || !ctx.font) return 0;
5285
+ const cache = ctx.cache;
5286
+ if (cache) {
5287
+ let fc = cache.get(ctx.font);
5288
+ if (!fc) {
5289
+ fc = /* @__PURE__ */ new Map();
5290
+ cache.set(ctx.font, fc);
5291
+ }
5292
+ let hw = fc.get("-");
5293
+ if (hw === void 0) {
5294
+ hw = ctx.adapter.measureSegment("-", ctx.font).width;
5295
+ fc.set("-", hw);
5296
+ }
5297
+ return hw;
5298
+ }
5299
+ return ctx.adapter.measureSegment("-", ctx.font).width;
5300
+ }
5301
+ function layoutNextLine(segments, cursor, slotWidth, ctx) {
5302
+ let i = cursor.segmentIndex;
5303
+ const initialG = cursor.graphemeIndex;
5304
+ if (i >= segments.length) return null;
5305
+ if (initialG === 0) {
5306
+ while (i < segments.length) {
5307
+ const seg = segments[i];
5308
+ if (seg.kind === "hard-break") {
5309
+ return {
5310
+ text: "",
5311
+ width: 0,
5312
+ start: { segmentIndex: cursor.segmentIndex, graphemeIndex: 0 },
5313
+ end: { segmentIndex: i + 1, graphemeIndex: 0 }
5314
+ };
5315
+ }
5316
+ if (seg.kind === "space" || seg.kind === "zero-width-break" || seg.kind === "soft-hyphen") {
5317
+ i += 1;
5318
+ continue;
5319
+ }
5320
+ break;
5321
+ }
5322
+ if (i >= segments.length) return null;
5323
+ }
5324
+ const hyphenWidth = resolveHyphenWidth(ctx);
5325
+ const startSeg = i;
5326
+ const startG = i === cursor.segmentIndex ? initialG : 0;
5327
+ let lineW = 0;
5328
+ let lineEndSeg = startSeg;
5329
+ let lineEndG = 0;
5330
+ let hasContent = false;
5331
+ let pendingBreakSeg = -1;
5332
+ let pendingBreakG = 0;
5333
+ let pendingBreakWidth = 0;
5334
+ let pendingBreakSoftHyphen = false;
5335
+ const recordPending = (sIdx, gIdx, widthAtBreak, kind) => {
5336
+ pendingBreakSeg = sIdx;
5337
+ pendingBreakG = gIdx;
5338
+ pendingBreakWidth = widthAtBreak;
5339
+ pendingBreakSoftHyphen = kind === "soft-hyphen";
5340
+ };
5341
+ const consumeBreakable = (segIdx, gStart, gWidths) => {
5342
+ for (let g = gStart; g < gWidths.length; g++) {
5343
+ const gw = gWidths[g];
5344
+ if (!hasContent) {
5345
+ lineW = gw;
5346
+ lineEndSeg = segIdx;
5347
+ lineEndG = g + 1;
5348
+ hasContent = true;
5349
+ continue;
5350
+ }
5351
+ if (lineW + gw > slotWidth + 5e-3) {
5352
+ return true;
5353
+ }
5354
+ lineW += gw;
5355
+ lineEndSeg = segIdx;
5356
+ lineEndG = g + 1;
5357
+ }
5358
+ if (lineEndSeg === segIdx && lineEndG === gWidths.length) {
5359
+ lineEndSeg = segIdx + 1;
5360
+ lineEndG = 0;
5361
+ }
5362
+ return false;
5363
+ };
5364
+ if (startG > 0 && startSeg < segments.length) {
5365
+ const seg = segments[startSeg];
5366
+ if (seg.graphemeWidths) {
5367
+ const overflowed = consumeBreakable(startSeg, startG, seg.graphemeWidths);
5368
+ if (overflowed) {
5369
+ const text2 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
5370
+ return {
5371
+ text: text2,
5372
+ width: lineW,
5373
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5374
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5375
+ };
5376
+ }
5377
+ i = lineEndSeg;
5378
+ } else {
5379
+ }
5380
+ }
5381
+ for (; i < segments.length; ) {
5382
+ const seg = segments[i];
5383
+ if (seg.kind === "hard-break") {
5384
+ if (hasContent) {
5385
+ const endsAtSoftHyphen2 = lineEndSeg > 0 && segments[lineEndSeg - 1]?.kind === "soft-hyphen";
5386
+ const text2 = buildLineText(
5387
+ segments,
5388
+ startSeg,
5389
+ startG,
5390
+ lineEndSeg,
5391
+ lineEndG,
5392
+ endsAtSoftHyphen2
5393
+ );
5394
+ return {
5395
+ text: text2,
5396
+ width: lineW + (endsAtSoftHyphen2 ? hyphenWidth : 0),
5397
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5398
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5399
+ };
5400
+ }
5401
+ return {
5402
+ text: "",
5403
+ width: 0,
5404
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5405
+ end: { segmentIndex: i + 1, graphemeIndex: 0 }
5406
+ };
5407
+ }
5408
+ const w = seg.width;
5409
+ if (!hasContent) {
5410
+ if (w > slotWidth && seg.graphemeWidths) {
5411
+ const overflowed = consumeBreakable(i, 0, seg.graphemeWidths);
5412
+ if (overflowed) {
5413
+ const text2 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
5414
+ return {
5415
+ text: text2,
5416
+ width: lineW,
5417
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5418
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5419
+ };
5420
+ }
5421
+ i = lineEndSeg;
5422
+ continue;
5423
+ }
5424
+ lineW = w;
5425
+ lineEndSeg = i + 1;
5426
+ lineEndG = 0;
5427
+ hasContent = true;
5428
+ if (canBreakAfter(seg.kind)) {
5429
+ recordPending(i + 1, 0, seg.kind === "space" ? lineW - w : lineW, seg.kind);
5430
+ }
5431
+ i += 1;
5432
+ continue;
5433
+ }
5434
+ const newW = lineW + w;
5435
+ if (newW > slotWidth + 5e-3) {
5436
+ if (canBreakAfter(seg.kind)) {
5437
+ lineEndSeg = i + 1;
5438
+ lineEndG = 0;
5439
+ const endsAtSoftHyphen2 = seg.kind === "soft-hyphen";
5440
+ const finalWidth = seg.kind === "space" ? lineW : lineW + (endsAtSoftHyphen2 ? hyphenWidth : 0);
5441
+ const text3 = buildLineText(
5442
+ segments,
5443
+ startSeg,
5444
+ startG,
5445
+ lineEndSeg,
5446
+ lineEndG,
5447
+ endsAtSoftHyphen2
5448
+ );
5449
+ return {
5450
+ text: text3,
5451
+ width: finalWidth,
5452
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5453
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5454
+ };
5455
+ }
5456
+ if (pendingBreakSeg >= 0) {
5457
+ const text3 = buildLineText(
5458
+ segments,
5459
+ startSeg,
5460
+ startG,
5461
+ pendingBreakSeg,
5462
+ pendingBreakG,
5463
+ pendingBreakSoftHyphen
5464
+ );
5465
+ return {
5466
+ text: text3,
5467
+ width: pendingBreakWidth + (pendingBreakSoftHyphen ? hyphenWidth : 0),
5468
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5469
+ end: { segmentIndex: pendingBreakSeg, graphemeIndex: pendingBreakG }
5470
+ };
5471
+ }
5472
+ if (w > slotWidth && seg.graphemeWidths) {
5473
+ const text3 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
5474
+ return {
5475
+ text: text3,
5476
+ width: lineW,
5477
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5478
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5479
+ };
5480
+ }
5481
+ const text2 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
5482
+ return {
5483
+ text: text2,
5484
+ width: lineW,
5485
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5486
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5487
+ };
5488
+ }
5489
+ lineW = newW;
5490
+ lineEndSeg = i + 1;
5491
+ lineEndG = 0;
5492
+ if (canBreakAfter(seg.kind)) {
5493
+ recordPending(i + 1, 0, seg.kind === "space" ? lineW - w : lineW, seg.kind);
5494
+ }
5495
+ i += 1;
5496
+ }
5497
+ if (!hasContent) return null;
5498
+ const endsAtSoftHyphen = lineEndSeg > 0 && segments[lineEndSeg - 1]?.kind === "soft-hyphen";
5499
+ const text = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, endsAtSoftHyphen);
5500
+ return {
5501
+ text,
5502
+ width: lineW + (endsAtSoftHyphen ? hyphenWidth : 0),
5503
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5504
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5505
+ };
5506
+ }
5507
+ function carveTextLineSlots(base, blocked, minSlotWidth = 0) {
5508
+ let slots = [base];
5509
+ for (let bi = 0; bi < blocked.length; bi++) {
5510
+ const block = blocked[bi];
5511
+ const next = [];
5512
+ for (let si = 0; si < slots.length; si++) {
5513
+ const slot = slots[si];
5514
+ if (block.right <= slot.left || block.left >= slot.right) {
5515
+ next.push(slot);
5516
+ continue;
5517
+ }
5518
+ if (block.left > slot.left) next.push({ left: slot.left, right: block.left });
5519
+ if (block.right < slot.right) next.push({ left: block.right, right: slot.right });
5520
+ }
5521
+ slots = next;
5522
+ }
5523
+ if (minSlotWidth > 0) {
5524
+ return slots.filter((s) => s.right - s.left >= minSlotWidth);
5525
+ }
5526
+ return slots;
5527
+ }
5243
5528
  function computeCharPositions(lineBreaks, segments, lineHeight) {
5244
5529
  const positions = [];
5245
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
5530
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
5246
5531
  granularity: "grapheme"
5247
5532
  });
5248
5533
  for (let lineIdx = 0; lineIdx < lineBreaks.lines.length; lineIdx++) {
@@ -5255,7 +5540,7 @@ function computeCharPositions(lineBreaks, segments, lineHeight) {
5255
5540
  if (si >= line.endSegment && line.endGrapheme === 0) break;
5256
5541
  continue;
5257
5542
  }
5258
- const graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);
5543
+ const graphemes = [...graphemeSegmenter2.segment(seg.text)].map((g) => g.segment);
5259
5544
  if (graphemes.length === 0) continue;
5260
5545
  const startG = si === line.startSegment ? line.startGrapheme : 0;
5261
5546
  let endG;
@@ -5611,6 +5896,185 @@ function reactiveBlockLayout(opts) {
5611
5896
  totalHeight: totalHeightNode
5612
5897
  };
5613
5898
  }
5899
+
5900
+ // src/patterns/reactive-layout/reactive-flow-layout.ts
5901
+ function circleIntervalForBand(o, bandTop, bandBottom) {
5902
+ const hPad = o.hPad ?? 0;
5903
+ const vPad = o.vPad ?? 0;
5904
+ const top = bandTop - vPad;
5905
+ const bottom = bandBottom + vPad;
5906
+ if (top >= o.cy + o.r || bottom <= o.cy - o.r) return null;
5907
+ const minDy = o.cy >= top && o.cy <= bottom ? 0 : o.cy < top ? top - o.cy : o.cy - bottom;
5908
+ if (minDy >= o.r) return null;
5909
+ const maxDx = Math.sqrt(o.r * o.r - minDy * minDy);
5910
+ return { left: o.cx - maxDx - hPad, right: o.cx + maxDx + hPad };
5911
+ }
5912
+ function rectIntervalForBand(o, bandTop, bandBottom) {
5913
+ const hPad = o.hPad ?? 0;
5914
+ const vPad = o.vPad ?? 0;
5915
+ if (bandBottom <= o.y - vPad) return null;
5916
+ if (bandTop >= o.y + o.h + vPad) return null;
5917
+ return { left: o.x - hPad, right: o.x + o.w + hPad };
5918
+ }
5919
+ function obstacleIntervalForBand(o, bandTop, bandBottom) {
5920
+ return o.kind === "circle" ? circleIntervalForBand(o, bandTop, bandBottom) : rectIntervalForBand(o, bandTop, bandBottom);
5921
+ }
5922
+ function computeFlowLines(segments, container, columns, obstacles, lineHeight, minSlotWidth) {
5923
+ const lines = [];
5924
+ let cursor = { segmentIndex: 0, graphemeIndex: 0 };
5925
+ if (segments.length === 0 || columns.count <= 0 || lineHeight <= 0) {
5926
+ return { lines, cursor };
5927
+ }
5928
+ const padX = container.paddingX ?? 0;
5929
+ const padY = container.paddingY ?? 0;
5930
+ const availWidth = Math.max(0, container.width - padX * 2);
5931
+ const availHeight = Math.max(0, container.height - padY * 2);
5932
+ const gapTotal = columns.gap * Math.max(0, columns.count - 1);
5933
+ const colWidth = Math.max(0, (availWidth - gapTotal) / columns.count);
5934
+ if (colWidth <= 0) return { lines, cursor };
5935
+ outerCol: for (let col = 0; col < columns.count; col++) {
5936
+ const colLeft = padX + col * (colWidth + columns.gap);
5937
+ const colRight = colLeft + colWidth;
5938
+ let bandTop = padY;
5939
+ while (bandTop + lineHeight <= padY + availHeight) {
5940
+ const bandBottom = bandTop + lineHeight;
5941
+ const blocked = [];
5942
+ for (let oi = 0; oi < obstacles.length; oi++) {
5943
+ const iv = obstacleIntervalForBand(obstacles[oi], bandTop, bandBottom);
5944
+ if (iv !== null) blocked.push(iv);
5945
+ }
5946
+ const slots = carveTextLineSlots({ left: colLeft, right: colRight }, blocked, minSlotWidth);
5947
+ if (slots.length === 0) {
5948
+ bandTop += lineHeight;
5949
+ continue;
5950
+ }
5951
+ let hardBreakThisBand = false;
5952
+ for (let si = 0; si < slots.length; si++) {
5953
+ const slot = slots[si];
5954
+ const slotW = slot.right - slot.left;
5955
+ const line = layoutNextLine(segments, cursor, slotW);
5956
+ if (line === null) {
5957
+ return { lines, cursor };
5958
+ }
5959
+ if (line.text.length === 0 && line.width === 0) {
5960
+ cursor = line.end;
5961
+ hardBreakThisBand = true;
5962
+ break;
5963
+ }
5964
+ lines.push({
5965
+ x: slot.left,
5966
+ y: bandTop,
5967
+ width: line.width,
5968
+ slotWidth: slotW,
5969
+ text: line.text,
5970
+ columnIndex: col,
5971
+ flushToRight: slot.right < colRight - 0.5
5972
+ });
5973
+ cursor = line.end;
5974
+ }
5975
+ bandTop += lineHeight;
5976
+ if (hardBreakThisBand) continue;
5977
+ if (cursor.segmentIndex >= segments.length) break outerCol;
5978
+ }
5979
+ if (cursor.segmentIndex >= segments.length) break;
5980
+ }
5981
+ return { lines, cursor };
5982
+ }
5983
+ function reactiveFlowLayout(opts) {
5984
+ const { adapter, name = "reactive-flow-layout", minSlotWidth = 20 } = opts;
5985
+ const g = new Graph(name);
5986
+ const measureCache = /* @__PURE__ */ new Map();
5987
+ const textNode = state(opts.text ?? "", { name: "text" });
5988
+ const fontNode = state(opts.font ?? "16px sans-serif", { name: "font" });
5989
+ const lineHeightNode = state(opts.lineHeight ?? 20, { name: "line-height" });
5990
+ const containerNode = state(
5991
+ opts.container ?? { width: 800, height: 600, paddingX: 0, paddingY: 0 },
5992
+ { name: "container" }
5993
+ );
5994
+ const columnsNode = state(opts.columns ?? { count: 1, gap: 0 }, {
5995
+ name: "columns"
5996
+ });
5997
+ const obstaclesNode = state(opts.obstacles ?? [], { name: "obstacles" });
5998
+ const segmentsNode = node(
5999
+ [textNode, fontNode],
6000
+ (data, actions, ctx) => {
6001
+ const b0 = data[0];
6002
+ const textVal = b0 != null && b0.length > 0 ? b0.at(-1) : ctx.prevData[0];
6003
+ const b1 = data[1];
6004
+ const fontVal = b1 != null && b1.length > 0 ? b1.at(-1) : ctx.prevData[1];
6005
+ const result = analyzeAndMeasure(textVal, fontVal, adapter, measureCache);
6006
+ actions.emit(result);
6007
+ return () => {
6008
+ measureCache.clear();
6009
+ adapter.clearCache?.();
6010
+ };
6011
+ },
6012
+ { name: "segments", describeKind: "derived" }
6013
+ );
6014
+ const flowLinesNode = derived(
6015
+ [segmentsNode, containerNode, columnsNode, obstaclesNode, lineHeightNode],
6016
+ ([segs, cont, cols, obs, lh]) => {
6017
+ const segments = segs;
6018
+ const t0 = monotonicNs();
6019
+ const { lines: result, cursor } = computeFlowLines(
6020
+ segments,
6021
+ cont,
6022
+ cols,
6023
+ obs,
6024
+ lh,
6025
+ minSlotWidth
6026
+ );
6027
+ const elapsed = monotonicNs() - t0;
6028
+ const overflow = Math.max(0, segments.length - cursor.segmentIndex);
6029
+ const meta = flowLinesNode.meta;
6030
+ if (meta) {
6031
+ emitToMeta(meta["line-count"], result.length);
6032
+ emitToMeta(meta["layout-time-ns"], elapsed);
6033
+ emitToMeta(meta["overflow-segments"], overflow);
6034
+ }
6035
+ return result;
6036
+ },
6037
+ {
6038
+ name: "flow-lines",
6039
+ meta: {
6040
+ "line-count": 0,
6041
+ "layout-time-ns": 0,
6042
+ "overflow-segments": 0
6043
+ },
6044
+ equals: (a, b) => {
6045
+ const la = a;
6046
+ const lb = b;
6047
+ if (la.length !== lb.length) return false;
6048
+ for (let i = 0; i < la.length; i++) {
6049
+ const pa = la[i];
6050
+ const pb = lb[i];
6051
+ 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)
6052
+ return false;
6053
+ }
6054
+ return true;
6055
+ }
6056
+ }
6057
+ );
6058
+ g.add("text", textNode);
6059
+ g.add("font", fontNode);
6060
+ g.add("line-height", lineHeightNode);
6061
+ g.add("container", containerNode);
6062
+ g.add("columns", columnsNode);
6063
+ g.add("obstacles", obstaclesNode);
6064
+ g.add("segments", segmentsNode);
6065
+ g.add("flow-lines", flowLinesNode);
6066
+ return {
6067
+ graph: g,
6068
+ setText: (t) => g.set("text", t),
6069
+ setFont: (f) => g.set("font", f),
6070
+ setLineHeight: (lh) => g.set("line-height", lh),
6071
+ setContainer: (c) => g.set("container", c),
6072
+ setColumns: (c) => g.set("columns", c),
6073
+ setObstacles: (o) => g.set("obstacles", o),
6074
+ segments: segmentsNode,
6075
+ flowLines: flowLinesNode
6076
+ };
6077
+ }
5614
6078
  // Annotate the CommonJS export names for ESM import in node:
5615
6079
  0 && (module.exports = {
5616
6080
  CanvasMeasureAdapter,
@@ -5620,13 +6084,19 @@ function reactiveBlockLayout(opts) {
5620
6084
  PrecomputedAdapter,
5621
6085
  SvgBoundsAdapter,
5622
6086
  analyzeAndMeasure,
6087
+ carveTextLineSlots,
6088
+ circleIntervalForBand,
5623
6089
  computeBlockFlow,
5624
6090
  computeCharPositions,
6091
+ computeFlowLines,
5625
6092
  computeLineBreaks,
5626
6093
  computeTotalHeight,
6094
+ layoutNextLine,
5627
6095
  measureBlock,
5628
6096
  measureBlocks,
5629
6097
  reactiveBlockLayout,
5630
- reactiveLayout
6098
+ reactiveFlowLayout,
6099
+ reactiveLayout,
6100
+ rectIntervalForBand
5631
6101
  });
5632
6102
  //# sourceMappingURL=index.cjs.map