@santjc/react-pretext 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.
@@ -0,0 +1,684 @@
1
+ import {
2
+ useElementWidth,
3
+ usePreparedSegments
4
+ } from "./chunk-6P7OEVAC.js";
5
+
6
+ // src/editorial/components/EditorialColumns.tsx
7
+ import { useMemo } from "react";
8
+
9
+ // src/editorial/lib/flowText.ts
10
+ import { layoutNextLine } from "@chenglou/pretext";
11
+ var initialCursor = {
12
+ segmentIndex: 0,
13
+ graphemeIndex: 0
14
+ };
15
+ function flowText({
16
+ prepared,
17
+ lineHeight,
18
+ getLineSlotAtY,
19
+ startY = 0,
20
+ startCursor = initialCursor,
21
+ maxLines,
22
+ maxY,
23
+ maxSteps = 2e3
24
+ }) {
25
+ const lines = [];
26
+ let y = startY;
27
+ let cursor = startCursor;
28
+ let exhausted = false;
29
+ let truncated = false;
30
+ let step = 0;
31
+ for (; step < maxSteps; step += 1) {
32
+ if (maxLines !== void 0 && lines.length >= maxLines) {
33
+ break;
34
+ }
35
+ if (maxY !== void 0 && y + lineHeight > maxY) {
36
+ break;
37
+ }
38
+ const lineSlot = getLineSlotAtY(y);
39
+ if (lineSlot === null || lineSlot.right <= lineSlot.left) {
40
+ y += lineHeight;
41
+ continue;
42
+ }
43
+ const line = layoutNextLine(prepared, cursor, Math.max(1, Math.floor(lineSlot.right - lineSlot.left)));
44
+ if (line === null) {
45
+ exhausted = true;
46
+ break;
47
+ }
48
+ lines.push({
49
+ text: line.text,
50
+ x: lineSlot.left,
51
+ y,
52
+ width: line.width,
53
+ slotLeft: lineSlot.left,
54
+ slotRight: lineSlot.right,
55
+ slotWidth: lineSlot.right - lineSlot.left,
56
+ start: line.start,
57
+ end: line.end
58
+ });
59
+ cursor = line.end;
60
+ y += lineHeight;
61
+ }
62
+ if (!exhausted && step >= maxSteps) {
63
+ truncated = true;
64
+ }
65
+ if (!exhausted && !truncated && layoutNextLine(prepared, cursor, 1) === null) {
66
+ exhausted = true;
67
+ }
68
+ return {
69
+ lines,
70
+ height: Math.max(0, y - startY),
71
+ exhausted,
72
+ truncated,
73
+ endCursor: cursor
74
+ };
75
+ }
76
+
77
+ // src/editorial/lib/editorialJustify.ts
78
+ var supportedGapKinds = /* @__PURE__ */ new Set(["space", "preserved-space"]);
79
+ var unsupportedKinds = /* @__PURE__ */ new Set(["tab", "soft-hyphen", "hard-break", "zero-width-break"]);
80
+ function getEditorialJustification({
81
+ prepared,
82
+ line,
83
+ maxWordSpacing = 24
84
+ }) {
85
+ if (line.text.trimStart() !== line.text) {
86
+ return null;
87
+ }
88
+ let lastSegmentIndex = line.end.graphemeIndex > 0 ? line.end.segmentIndex : line.end.segmentIndex - 1;
89
+ while (lastSegmentIndex >= line.start.segmentIndex) {
90
+ const trailingKind = prepared.kinds[lastSegmentIndex];
91
+ const trailingSegment = prepared.segments[lastSegmentIndex];
92
+ if ((trailingKind === "space" || trailingKind === "preserved-space") && trailingSegment === " ") {
93
+ lastSegmentIndex -= 1;
94
+ continue;
95
+ }
96
+ break;
97
+ }
98
+ if (lastSegmentIndex < line.start.segmentIndex) {
99
+ return null;
100
+ }
101
+ let gapCount = 0;
102
+ for (let segmentIndex = line.start.segmentIndex; segmentIndex <= lastSegmentIndex; segmentIndex += 1) {
103
+ const kind = prepared.kinds[segmentIndex];
104
+ const segment = prepared.segments[segmentIndex];
105
+ if (kind === void 0 || segment === void 0) {
106
+ return null;
107
+ }
108
+ const isPartialStart = segmentIndex === line.start.segmentIndex && line.start.graphemeIndex > 0;
109
+ const isPartialEnd = segmentIndex === line.end.segmentIndex && line.end.graphemeIndex > 0;
110
+ if ((isPartialStart || isPartialEnd) && kind !== "text") {
111
+ return null;
112
+ }
113
+ if (unsupportedKinds.has(kind)) {
114
+ return null;
115
+ }
116
+ if (!supportedGapKinds.has(kind)) {
117
+ continue;
118
+ }
119
+ if (segment !== " ") {
120
+ return null;
121
+ }
122
+ const previousKind = prepared.kinds[segmentIndex - 1];
123
+ const nextKind = prepared.kinds[segmentIndex + 1];
124
+ if (supportedGapKinds.has(previousKind ?? "") || supportedGapKinds.has(nextKind ?? "")) {
125
+ return null;
126
+ }
127
+ gapCount += 1;
128
+ }
129
+ if (gapCount === 0) {
130
+ return null;
131
+ }
132
+ const extraWidth = line.slotWidth - line.width;
133
+ if (extraWidth <= 0) {
134
+ return null;
135
+ }
136
+ const wordSpacing = extraWidth / gapCount;
137
+ if (wordSpacing > maxWordSpacing) {
138
+ return null;
139
+ }
140
+ return wordSpacing;
141
+ }
142
+
143
+ // src/editorial/lib/editorialLineAnnotation.ts
144
+ function annotateEditorialLines(prepared, lines, preserveParagraphBreaks) {
145
+ return lines.map((line, index) => {
146
+ const isTerminal = index === lines.length - 1;
147
+ const isParagraphTerminal = preserveParagraphBreaks && /\n\s*$/.test(line.text);
148
+ const justifyWordSpacing = !isTerminal && !isParagraphTerminal ? getEditorialJustification({ prepared, line }) : null;
149
+ return {
150
+ ...line,
151
+ justifyWordSpacing,
152
+ isTerminal,
153
+ isParagraphTerminal
154
+ };
155
+ });
156
+ }
157
+
158
+ // src/editorial/lib/lineSlots.ts
159
+ function carveLineSlots(baseLineSlot, blockedLineRanges, minWidth = 24) {
160
+ let lineSlots = [baseLineSlot];
161
+ for (let blockedIndex = 0; blockedIndex < blockedLineRanges.length; blockedIndex += 1) {
162
+ const blockedLineRange = blockedLineRanges[blockedIndex];
163
+ const nextLineSlots = [];
164
+ for (let slotIndex = 0; slotIndex < lineSlots.length; slotIndex += 1) {
165
+ const lineSlot = lineSlots[slotIndex];
166
+ if (blockedLineRange.right <= lineSlot.left || blockedLineRange.left >= lineSlot.right) {
167
+ nextLineSlots.push(lineSlot);
168
+ continue;
169
+ }
170
+ if (blockedLineRange.left > lineSlot.left) {
171
+ nextLineSlots.push({ left: lineSlot.left, right: blockedLineRange.left });
172
+ }
173
+ if (blockedLineRange.right < lineSlot.right) {
174
+ nextLineSlots.push({ left: blockedLineRange.right, right: lineSlot.right });
175
+ }
176
+ }
177
+ lineSlots = nextLineSlots;
178
+ }
179
+ return lineSlots.filter((lineSlot) => lineSlot.right - lineSlot.left >= minWidth);
180
+ }
181
+ function getCircleBlockedLineRangeForRow({
182
+ cx,
183
+ cy,
184
+ radius,
185
+ lineTop,
186
+ lineBottom,
187
+ horizontalPadding = 0,
188
+ verticalPadding = 0
189
+ }) {
190
+ const top = lineTop - verticalPadding;
191
+ const bottom = lineBottom + verticalPadding;
192
+ if (bottom <= cy - radius || top >= cy + radius) {
193
+ return null;
194
+ }
195
+ const minDy = cy >= top && cy <= bottom ? 0 : cy < top ? top - cy : cy - bottom;
196
+ if (minDy >= radius) {
197
+ return null;
198
+ }
199
+ const maxDx = Math.sqrt(radius * radius - minDy * minDy);
200
+ return {
201
+ left: cx - maxDx - horizontalPadding,
202
+ right: cx + maxDx + horizontalPadding
203
+ };
204
+ }
205
+ function pickWidestLineSlot(lineSlots) {
206
+ if (lineSlots.length === 0) {
207
+ return null;
208
+ }
209
+ let widestLineSlot = lineSlots[0];
210
+ for (let index = 1; index < lineSlots.length; index += 1) {
211
+ const lineSlot = lineSlots[index];
212
+ if (lineSlot.right - lineSlot.left > widestLineSlot.right - widestLineSlot.left) {
213
+ widestLineSlot = lineSlot;
214
+ }
215
+ }
216
+ return widestLineSlot;
217
+ }
218
+ function createLineSlotResolver({
219
+ baseLineSlot,
220
+ lineHeight,
221
+ minWidth = 24,
222
+ getBlockedLineRanges = () => []
223
+ }) {
224
+ return (y) => {
225
+ const lineTop = y;
226
+ const lineBottom = y + lineHeight;
227
+ const blockedLineRanges = getBlockedLineRanges(lineTop, lineBottom);
228
+ const lineSlots = carveLineSlots(baseLineSlot, blockedLineRanges, minWidth);
229
+ return pickWidestLineSlot(lineSlots);
230
+ };
231
+ }
232
+
233
+ // src/editorial/lib/editorialFigures.ts
234
+ function resolveEditorialPlacement(figure, bounds) {
235
+ const placement = figure.placement ?? "top-right";
236
+ const freeWidth = Math.max(0, bounds.width - figure.width);
237
+ const freeHeight = Math.max(0, bounds.height - figure.height);
238
+ const horizontal = (() => {
239
+ switch (placement) {
240
+ case "top-left":
241
+ case "center-left":
242
+ case "bottom-left":
243
+ return 0;
244
+ case "top-center":
245
+ case "center":
246
+ case "bottom-center":
247
+ return freeWidth / 2;
248
+ case "top-right":
249
+ case "center-right":
250
+ case "bottom-right":
251
+ return freeWidth;
252
+ }
253
+ })();
254
+ const vertical = (() => {
255
+ switch (placement) {
256
+ case "top-left":
257
+ case "top-center":
258
+ case "top-right":
259
+ return 0;
260
+ case "center-left":
261
+ case "center":
262
+ case "center-right":
263
+ return freeHeight / 2;
264
+ case "bottom-left":
265
+ case "bottom-center":
266
+ case "bottom-right":
267
+ return freeHeight;
268
+ }
269
+ })();
270
+ return {
271
+ x: Math.min(Math.max(figure.x ?? horizontal, 0), freeWidth),
272
+ y: Math.min(Math.max(figure.y ?? vertical, 0), freeHeight)
273
+ };
274
+ }
275
+ function getRectBlockedLineRangeForRow({
276
+ x,
277
+ y,
278
+ width,
279
+ height,
280
+ lineTop,
281
+ lineBottom,
282
+ horizontalPadding = 0,
283
+ verticalPadding = 0
284
+ }) {
285
+ const top = y - verticalPadding;
286
+ const bottom = y + height + verticalPadding;
287
+ if (lineBottom <= top || lineTop >= bottom) {
288
+ return null;
289
+ }
290
+ return {
291
+ left: x - horizontalPadding,
292
+ right: x + width + horizontalPadding
293
+ };
294
+ }
295
+ function getBlockedLineRangesForEditorialFigures(figures, lineTop, lineBottom) {
296
+ const blocked = [];
297
+ for (let index = 0; index < figures.length; index += 1) {
298
+ const figure = figures[index];
299
+ if (figure.shape === "circle") {
300
+ const range2 = getCircleBlockedLineRangeForRow({
301
+ cx: figure.x + figure.width / 2,
302
+ cy: figure.y + figure.height / 2,
303
+ radius: Math.min(figure.width, figure.height) / 2,
304
+ lineTop,
305
+ lineBottom,
306
+ horizontalPadding: figure.linePadding ?? 0
307
+ });
308
+ if (range2 !== null) {
309
+ blocked.push(range2);
310
+ }
311
+ continue;
312
+ }
313
+ const range = getRectBlockedLineRangeForRow({
314
+ x: figure.x,
315
+ y: figure.y,
316
+ width: figure.width,
317
+ height: figure.height,
318
+ lineTop,
319
+ lineBottom,
320
+ horizontalPadding: figure.linePadding ?? 0
321
+ });
322
+ if (range !== null) {
323
+ blocked.push(range);
324
+ }
325
+ }
326
+ return blocked;
327
+ }
328
+
329
+ // src/editorial/lib/layoutEditorialTrack.ts
330
+ function layoutEditorialTrack({
331
+ prepared,
332
+ figures: figureDefs = [],
333
+ width,
334
+ height,
335
+ lineHeight,
336
+ paddingInline = 0,
337
+ paddingBlock = 0,
338
+ startCursor = initialCursor,
339
+ startY = paddingBlock,
340
+ maxY,
341
+ preserveParagraphBreaks = false
342
+ }) {
343
+ const innerWidth = Math.max(24, width - paddingInline * 2);
344
+ const innerHeight = Math.max(0, height - paddingBlock * 2);
345
+ const figures = figureDefs.map((figure) => {
346
+ const resolved = resolveEditorialPlacement(figure, {
347
+ width: innerWidth,
348
+ height: innerHeight
349
+ });
350
+ return {
351
+ ...figure,
352
+ x: paddingInline + resolved.x,
353
+ y: paddingBlock + resolved.y
354
+ };
355
+ });
356
+ const getLineSlotAtY = createLineSlotResolver({
357
+ baseLineSlot: { left: paddingInline, right: width - paddingInline },
358
+ lineHeight,
359
+ minWidth: 24,
360
+ getBlockedLineRanges: (lineTop, lineBottom) => getBlockedLineRangesForEditorialFigures(figures, lineTop, lineBottom)
361
+ });
362
+ const rawBody = flowText({
363
+ prepared,
364
+ lineHeight,
365
+ startCursor,
366
+ startY,
367
+ getLineSlotAtY,
368
+ maxY
369
+ });
370
+ return {
371
+ figures,
372
+ body: {
373
+ ...rawBody,
374
+ lines: annotateEditorialLines(prepared, rawBody.lines, preserveParagraphBreaks)
375
+ }
376
+ };
377
+ }
378
+
379
+ // src/editorial/lib/editorialTracks.ts
380
+ function resolveEditorialTracks(tracks, gap, availableWidth) {
381
+ const fixedWidth = tracks.reduce((total, track) => total + (track.width ?? 0), 0);
382
+ const totalGap = Math.max(0, tracks.length - 1) * gap;
383
+ const flexibleTracks = tracks.filter((track) => track.width === void 0);
384
+ const totalFr = flexibleTracks.reduce((total, track) => total + (track.fr ?? 1), 0);
385
+ const remainingWidth = Math.max(0, availableWidth - fixedWidth - totalGap);
386
+ let x = 0;
387
+ return tracks.map((track) => {
388
+ const resolvedWidth = track.width ?? (totalFr === 0 ? 0 : remainingWidth * (track.fr ?? 1) / totalFr);
389
+ const resolved = {
390
+ ...track,
391
+ width: resolvedWidth,
392
+ x
393
+ };
394
+ x += resolvedWidth + gap;
395
+ return resolved;
396
+ });
397
+ }
398
+ function getEditorialTracksWidth(tracks, gap) {
399
+ if (tracks.length === 0) {
400
+ return 0;
401
+ }
402
+ return tracks.reduce((total, track) => total + track.width, 0) + gap * (tracks.length - 1);
403
+ }
404
+
405
+ // src/editorial/components/FlowLines.tsx
406
+ import { Fragment, jsx } from "react/jsx-runtime";
407
+ function getFlowLineText(line, lineRenderMode) {
408
+ if (lineRenderMode === "justify" && line.justifyWordSpacing !== null && line.justifyWordSpacing !== void 0) {
409
+ return line.text.trimEnd();
410
+ }
411
+ return line.text;
412
+ }
413
+ function getFlowLineStyle(line, font, lineHeight, lineRenderMode) {
414
+ return {
415
+ position: "absolute",
416
+ left: `${line.slotLeft}px`,
417
+ top: `${line.y}px`,
418
+ width: `${Math.ceil(line.slotWidth)}px`,
419
+ font,
420
+ lineHeight: `${lineHeight}px`,
421
+ whiteSpace: "pre",
422
+ textAlign: "left",
423
+ wordSpacing: lineRenderMode === "justify" && line.justifyWordSpacing !== null && line.justifyWordSpacing !== void 0 ? `${line.justifyWordSpacing}px` : void 0
424
+ };
425
+ }
426
+ function getFlowLineKey(line, index) {
427
+ return `${line.start.segmentIndex}-${line.start.graphemeIndex}-${index}`;
428
+ }
429
+ function FlowLines({
430
+ lines,
431
+ font,
432
+ lineHeight,
433
+ lineClassName,
434
+ lineRenderMode = "natural",
435
+ renderLine
436
+ }) {
437
+ return /* @__PURE__ */ jsx(Fragment, { children: lines.map((line, index) => {
438
+ const key = getFlowLineKey(line, index);
439
+ const text = getFlowLineText(line, lineRenderMode);
440
+ const style = getFlowLineStyle(line, font, lineHeight, lineRenderMode);
441
+ if (renderLine !== void 0) {
442
+ return renderLine({ key, line, text, style });
443
+ }
444
+ return /* @__PURE__ */ jsx("div", { className: lineClassName, style, children: text }, key);
445
+ }) });
446
+ }
447
+
448
+ // src/editorial/components/renderResolvedEditorialFigure.tsx
449
+ import { jsx as jsx2 } from "react/jsx-runtime";
450
+ function renderResolvedEditorialFigure(figure, index) {
451
+ return /* @__PURE__ */ jsx2(
452
+ "div",
453
+ {
454
+ className: figure.className,
455
+ style: {
456
+ position: "absolute",
457
+ left: `${figure.x}px`,
458
+ top: `${figure.y}px`,
459
+ width: `${figure.width}px`,
460
+ height: `${figure.height}px`,
461
+ ...figure.style
462
+ },
463
+ children: figure.content
464
+ },
465
+ index
466
+ );
467
+ }
468
+
469
+ // src/editorial/components/EditorialColumns.tsx
470
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
471
+ function createEmptyEditorialBody() {
472
+ return {
473
+ lines: [],
474
+ height: 0,
475
+ exhausted: false,
476
+ truncated: false,
477
+ endCursor: initialCursor
478
+ };
479
+ }
480
+ function EditorialColumns({
481
+ text,
482
+ font,
483
+ lineHeight,
484
+ gap = 24,
485
+ lineRenderMode = "natural",
486
+ prepareOptions,
487
+ className,
488
+ style,
489
+ tracks: trackDefs
490
+ }) {
491
+ const { ref, width: availableWidth } = useElementWidth();
492
+ const tracks = useMemo(() => resolveEditorialTracks(trackDefs, gap, availableWidth), [availableWidth, gap, trackDefs]);
493
+ const { prepared } = usePreparedSegments({ text, font, options: prepareOptions });
494
+ const renderedTracks = useMemo(() => {
495
+ if (prepared === null || availableWidth <= 0) {
496
+ return tracks.map((track) => ({
497
+ ...track,
498
+ figures: [],
499
+ body: createEmptyEditorialBody()
500
+ }));
501
+ }
502
+ let cursor = initialCursor;
503
+ const preserveParagraphBreaks = prepareOptions?.whiteSpace === "pre-wrap";
504
+ return tracks.map((track) => {
505
+ const paddingInline = track.paddingInline ?? 16;
506
+ const paddingBlock = track.paddingBlock ?? 0;
507
+ const { figures, body } = layoutEditorialTrack({
508
+ prepared,
509
+ figures: track.figures,
510
+ width: track.width,
511
+ height: track.minHeight ?? 320,
512
+ lineHeight,
513
+ startCursor: cursor,
514
+ startY: paddingBlock,
515
+ maxY: track.minHeight === void 0 ? void 0 : track.minHeight - paddingBlock,
516
+ paddingInline,
517
+ paddingBlock,
518
+ preserveParagraphBreaks
519
+ });
520
+ cursor = body.endCursor;
521
+ return {
522
+ ...track,
523
+ figures,
524
+ body
525
+ };
526
+ });
527
+ }, [availableWidth, lineHeight, prepared, prepareOptions?.whiteSpace, tracks]);
528
+ const width = getEditorialTracksWidth(tracks, gap);
529
+ const minHeight = renderedTracks.reduce((current, track) => Math.max(current, track.minHeight ?? track.body.height), 0);
530
+ return /* @__PURE__ */ jsx3(
531
+ "div",
532
+ {
533
+ ref,
534
+ className,
535
+ style: {
536
+ position: "relative",
537
+ width: "100%",
538
+ minHeight: `${minHeight}px`,
539
+ ...style
540
+ },
541
+ children: /* @__PURE__ */ jsx3(
542
+ "div",
543
+ {
544
+ style: {
545
+ position: "relative",
546
+ width: `${width}px`,
547
+ minHeight: `${minHeight}px`
548
+ },
549
+ children: renderedTracks.map((track, trackIndex) => /* @__PURE__ */ jsxs(
550
+ "div",
551
+ {
552
+ className: track.className,
553
+ style: {
554
+ position: "absolute",
555
+ left: `${track.x}px`,
556
+ top: 0,
557
+ width: `${track.width}px`,
558
+ minHeight: `${track.minHeight ?? track.body.height}px`,
559
+ ...track.style
560
+ },
561
+ children: [
562
+ track.figures.map(renderResolvedEditorialFigure),
563
+ /* @__PURE__ */ jsx3(FlowLines, { lines: track.body.lines, font, lineHeight, lineRenderMode })
564
+ ]
565
+ },
566
+ trackIndex
567
+ ))
568
+ }
569
+ )
570
+ }
571
+ );
572
+ }
573
+
574
+ // src/editorial/components/EditorialSurface.tsx
575
+ import { useMemo as useMemo2 } from "react";
576
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
577
+ function EditorialSurface({
578
+ text,
579
+ font,
580
+ lineHeight,
581
+ startY = 0,
582
+ maxY,
583
+ minHeight = 320,
584
+ lineRenderMode = "natural",
585
+ prepareOptions,
586
+ className,
587
+ style,
588
+ figures
589
+ }) {
590
+ const { ref, width } = useElementWidth();
591
+ const { prepared } = usePreparedSegments({ text, font, options: prepareOptions });
592
+ const baseHeight = Math.max(minHeight, maxY ?? startY + 320);
593
+ const preserveParagraphBreaks = prepareOptions?.whiteSpace === "pre-wrap";
594
+ const layout = useMemo2(() => {
595
+ if (prepared === null || width <= 0) {
596
+ return {
597
+ figures: [],
598
+ body: {
599
+ lines: [],
600
+ height: 0
601
+ }
602
+ };
603
+ }
604
+ return layoutEditorialTrack({
605
+ prepared,
606
+ figures,
607
+ width,
608
+ height: baseHeight,
609
+ lineHeight,
610
+ startY,
611
+ maxY,
612
+ preserveParagraphBreaks
613
+ });
614
+ }, [baseHeight, figures, lineHeight, maxY, preserveParagraphBreaks, prepared, startY, width]);
615
+ const height = Math.max(baseHeight, startY + layout.body.height);
616
+ return /* @__PURE__ */ jsxs2(
617
+ "div",
618
+ {
619
+ ref,
620
+ className,
621
+ style: {
622
+ position: "relative",
623
+ minHeight: `${height}px`,
624
+ ...style
625
+ },
626
+ children: [
627
+ layout.figures.map(renderResolvedEditorialFigure),
628
+ /* @__PURE__ */ jsx4(FlowLines, { lines: layout.body.lines, font, lineHeight, lineRenderMode })
629
+ ]
630
+ }
631
+ );
632
+ }
633
+
634
+ // src/editorial/hooks/useTextFlow.ts
635
+ import { useMemo as useMemo3 } from "react";
636
+ function useTextFlow({
637
+ prepared,
638
+ lineHeight,
639
+ getLineSlotAtY,
640
+ startY,
641
+ startCursor,
642
+ maxLines,
643
+ maxY,
644
+ maxSteps,
645
+ enabled = true
646
+ }) {
647
+ return useMemo3(() => {
648
+ if (!enabled || prepared === null) {
649
+ return {
650
+ lines: [],
651
+ height: 0,
652
+ exhausted: false,
653
+ truncated: false,
654
+ isReady: false,
655
+ endCursor: initialCursor
656
+ };
657
+ }
658
+ const result = flowText({
659
+ prepared,
660
+ lineHeight,
661
+ getLineSlotAtY,
662
+ startY,
663
+ startCursor,
664
+ maxLines,
665
+ maxY,
666
+ maxSteps
667
+ });
668
+ return {
669
+ ...result,
670
+ isReady: true
671
+ };
672
+ }, [enabled, getLineSlotAtY, lineHeight, maxLines, maxSteps, maxY, prepared, startCursor, startY]);
673
+ }
674
+ export {
675
+ EditorialColumns,
676
+ EditorialSurface,
677
+ FlowLines,
678
+ carveLineSlots,
679
+ createLineSlotResolver,
680
+ flowText,
681
+ getCircleBlockedLineRangeForRow,
682
+ pickWidestLineSlot,
683
+ useTextFlow
684
+ };