@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,157 @@
1
+ import { P as PrepareOptions, u as usePreparedText } from './usePreparedText-DmUr2kss.js';
2
+ export { U as UsePreparedTextInput, a as UsePreparedTextResult } from './usePreparedText-DmUr2kss.js';
3
+ import * as react from 'react';
4
+ import { CSSProperties } from 'react';
5
+ import { PreparedTextWithSegments, PreparedText } from '@chenglou/pretext';
6
+
7
+ type ElementWidthResult<T extends HTMLElement> = {
8
+ ref: (node: T | null) => void;
9
+ width: number;
10
+ node: T | null;
11
+ };
12
+ declare function useElementWidth<T extends HTMLElement = HTMLDivElement>(): ElementWidthResult<T>;
13
+
14
+ type PretextTypographyShorthandInput = {
15
+ font: string;
16
+ lineHeight: number;
17
+ width?: number;
18
+ family?: never;
19
+ size?: never;
20
+ weight?: never;
21
+ };
22
+ type PretextTypographyObjectInput = {
23
+ family: string;
24
+ size: number;
25
+ weight?: number | string;
26
+ lineHeight: number;
27
+ width?: number;
28
+ font?: never;
29
+ };
30
+ type PretextTypographyInput = PretextTypographyShorthandInput | PretextTypographyObjectInput;
31
+ type PretextTypography = {
32
+ font: string;
33
+ lineHeight: number;
34
+ width?: number;
35
+ style: CSSProperties;
36
+ };
37
+ declare function createPretextTypography(input: PretextTypographyInput): PretextTypography;
38
+
39
+ type UseMeasuredTextInput = {
40
+ text: string;
41
+ width?: number;
42
+ font?: string;
43
+ lineHeight?: number;
44
+ typography?: PretextTypography;
45
+ prepareOptions?: PrepareOptions;
46
+ enableProfiling?: boolean;
47
+ enabled?: boolean;
48
+ };
49
+ type UseMeasuredTextResult = {
50
+ prepared: ReturnType<typeof usePreparedText>['prepared'];
51
+ height: number;
52
+ lineCount: number;
53
+ prepareMs?: number;
54
+ isReady: boolean;
55
+ };
56
+ declare function useMeasuredText({ text, width, font, lineHeight, typography, prepareOptions, enableProfiling, enabled, }: UseMeasuredTextInput): UseMeasuredTextResult;
57
+
58
+ type UsePreparedSegmentsInput = {
59
+ text: string;
60
+ font: string;
61
+ options?: PrepareOptions;
62
+ enabled?: boolean;
63
+ };
64
+ type UsePreparedSegmentsResult = {
65
+ prepared: PreparedTextWithSegments | null;
66
+ isReady: boolean;
67
+ };
68
+ declare function usePreparedSegments({ text, font, options, enabled }: UsePreparedSegmentsInput): UsePreparedSegmentsResult;
69
+
70
+ type UsePretextLayoutInput = {
71
+ prepared: PreparedText | null;
72
+ width: number;
73
+ lineHeight: number;
74
+ enabled?: boolean;
75
+ };
76
+ type UsePretextLayoutResult = {
77
+ height: number;
78
+ lineCount: number;
79
+ isReady: boolean;
80
+ };
81
+ declare function usePretextLayout({ prepared, width, lineHeight, enabled }: UsePretextLayoutInput): UsePretextLayoutResult;
82
+
83
+ type UsePretextLinesInput = {
84
+ prepared: PreparedTextWithSegments | null;
85
+ width: number;
86
+ lineHeight: number;
87
+ enabled?: boolean;
88
+ };
89
+ type UsePretextLinesResult = {
90
+ height: number;
91
+ lineCount: number;
92
+ lines: Array<{
93
+ text: string;
94
+ width: number;
95
+ start: {
96
+ segmentIndex: number;
97
+ graphemeIndex: number;
98
+ };
99
+ end: {
100
+ segmentIndex: number;
101
+ graphemeIndex: number;
102
+ };
103
+ }>;
104
+ isReady: boolean;
105
+ };
106
+ declare function usePretextLines({ prepared, width, lineHeight, enabled }: UsePretextLinesInput): UsePretextLinesResult;
107
+
108
+ type UseTruncatedTextInput = {
109
+ text: string;
110
+ width?: number;
111
+ font?: string;
112
+ lineHeight?: number;
113
+ typography?: PretextTypography;
114
+ prepareOptions?: PrepareOptions;
115
+ maxLines: number;
116
+ ellipsis?: string;
117
+ enabled?: boolean;
118
+ };
119
+ type UseTruncatedTextResult = {
120
+ text: string;
121
+ didTruncate: boolean;
122
+ visibleLineCount: number;
123
+ fullLineCount: number;
124
+ height: number;
125
+ isReady: boolean;
126
+ };
127
+ declare function useTruncatedText({ text, width, font, lineHeight, typography, prepareOptions, maxLines, ellipsis, enabled, }: UseTruncatedTextInput): UseTruncatedTextResult;
128
+
129
+ type PTextTag = 'p' | 'div' | 'span' | 'h1' | 'h2' | 'h3';
130
+ type PTextMeasure = {
131
+ width: number;
132
+ height: number;
133
+ lineCount: number;
134
+ };
135
+ type PTextOwnProps = {
136
+ as?: PTextTag;
137
+ children: string;
138
+ prepareOptions?: PrepareOptions;
139
+ onMeasure?: (result: PTextMeasure) => void;
140
+ };
141
+ type PTextExplicitMeasureProps = {
142
+ font: string;
143
+ lineHeight: number;
144
+ width?: number;
145
+ typography?: PretextTypography;
146
+ };
147
+ type PTextTypographyProps = {
148
+ typography: PretextTypography;
149
+ font?: string;
150
+ lineHeight?: number;
151
+ width?: number;
152
+ };
153
+ type PTextProps = PTextOwnProps & (PTextExplicitMeasureProps | PTextTypographyProps) & React.HTMLAttributes<HTMLElement>;
154
+ type PTextElement = HTMLParagraphElement | HTMLDivElement | HTMLSpanElement | HTMLHeadingElement;
155
+ declare const PText: react.ForwardRefExoticComponent<PTextProps & react.RefAttributes<PTextElement>>;
156
+
157
+ export { type ElementWidthResult, PText, type PTextMeasure, type PTextProps, type PTextTag, PrepareOptions, type PretextTypography, type PretextTypographyInput, type PretextTypographyObjectInput, type PretextTypographyShorthandInput, type UseMeasuredTextInput, type UseMeasuredTextResult, type UsePreparedSegmentsInput, type UsePreparedSegmentsResult, type UsePretextLayoutInput, type UsePretextLayoutResult, type UsePretextLinesInput, type UsePretextLinesResult, type UseTruncatedTextInput, type UseTruncatedTextResult, createPretextTypography, useElementWidth, useMeasuredText, usePreparedSegments, usePreparedText, usePretextLayout, usePretextLines, useTruncatedText };
package/dist/index.js ADDED
@@ -0,0 +1,393 @@
1
+ import {
2
+ useElementWidth,
3
+ usePreparedSegments
4
+ } from "./chunk-6P7OEVAC.js";
5
+
6
+ // src/core/hooks/usePreparedText.ts
7
+ import { prepare, profilePrepare } from "@chenglou/pretext";
8
+ import { useMemo } from "react";
9
+ function usePreparedText({ text, font, options, enableProfiling = false, enabled = true }) {
10
+ const whiteSpace = options?.whiteSpace;
11
+ return useMemo(() => {
12
+ if (!enabled || text.length === 0 || font.length === 0) {
13
+ return {
14
+ prepared: null,
15
+ isReady: false
16
+ };
17
+ }
18
+ const normalizedOptions = whiteSpace === void 0 ? void 0 : { whiteSpace };
19
+ return {
20
+ prepared: prepare(text, font, normalizedOptions),
21
+ ...enableProfiling ? { prepareMs: profilePrepare(text, font, normalizedOptions).totalMs } : {},
22
+ isReady: true
23
+ };
24
+ }, [enableProfiling, enabled, font, text, whiteSpace]);
25
+ }
26
+
27
+ // src/core/hooks/usePretextLayout.ts
28
+ import { layout } from "@chenglou/pretext";
29
+ import { useMemo as useMemo2 } from "react";
30
+ function usePretextLayout({ prepared, width, lineHeight, enabled = true }) {
31
+ return useMemo2(() => {
32
+ if (!enabled || prepared === null || width <= 0) {
33
+ return {
34
+ height: 0,
35
+ lineCount: 0,
36
+ isReady: false
37
+ };
38
+ }
39
+ const result = layout(prepared, width, lineHeight);
40
+ return {
41
+ height: result.height,
42
+ lineCount: result.lineCount,
43
+ isReady: true
44
+ };
45
+ }, [enabled, lineHeight, prepared, width]);
46
+ }
47
+
48
+ // src/core/hooks/useMeasuredText.ts
49
+ function useMeasuredText({
50
+ text,
51
+ width,
52
+ font,
53
+ lineHeight,
54
+ typography,
55
+ prepareOptions,
56
+ enableProfiling = false,
57
+ enabled = true
58
+ }) {
59
+ const resolvedFont = font ?? typography?.font;
60
+ const resolvedLineHeight = lineHeight ?? typography?.lineHeight;
61
+ const resolvedWidth = width ?? typography?.width;
62
+ if (resolvedFont === void 0 || resolvedLineHeight === void 0) {
63
+ throw new Error("useMeasuredText requires `font` and `lineHeight`, either directly or via `typography`.");
64
+ }
65
+ if (resolvedWidth === void 0) {
66
+ throw new Error("useMeasuredText requires `width`, either directly or via `typography`.");
67
+ }
68
+ const prepared = usePreparedText({
69
+ text,
70
+ font: resolvedFont,
71
+ options: prepareOptions,
72
+ enableProfiling,
73
+ enabled
74
+ });
75
+ const layout3 = usePretextLayout({
76
+ prepared: prepared.prepared,
77
+ width: resolvedWidth,
78
+ lineHeight: resolvedLineHeight,
79
+ enabled
80
+ });
81
+ return {
82
+ prepared: prepared.prepared,
83
+ height: layout3.height,
84
+ lineCount: layout3.lineCount,
85
+ prepareMs: prepared.prepareMs,
86
+ isReady: layout3.isReady
87
+ };
88
+ }
89
+
90
+ // src/core/hooks/usePretextLines.ts
91
+ import { layoutWithLines } from "@chenglou/pretext";
92
+ import { useMemo as useMemo3 } from "react";
93
+ function usePretextLines({ prepared, width, lineHeight, enabled = true }) {
94
+ return useMemo3(() => {
95
+ if (!enabled || prepared === null || width <= 0) {
96
+ return {
97
+ height: 0,
98
+ lineCount: 0,
99
+ lines: [],
100
+ isReady: false
101
+ };
102
+ }
103
+ const result = layoutWithLines(prepared, width, lineHeight);
104
+ return {
105
+ height: result.height,
106
+ lineCount: result.lineCount,
107
+ lines: result.lines,
108
+ isReady: true
109
+ };
110
+ }, [enabled, lineHeight, prepared, width]);
111
+ }
112
+
113
+ // src/core/hooks/useTruncatedText.ts
114
+ import { layout as layout2, prepare as prepare2 } from "@chenglou/pretext";
115
+ import { useMemo as useMemo4 } from "react";
116
+ var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
117
+ var wordLikePattern = /[\p{L}\p{N}\p{M}]/u;
118
+ var whitespacePattern = /\s/u;
119
+ function getSegmentGraphemes(segment, cache) {
120
+ const cached = cache.get(segment);
121
+ if (cached !== void 0) {
122
+ return cached;
123
+ }
124
+ const graphemes = Array.from(graphemeSegmenter.segment(segment), ({ segment: grapheme }) => grapheme);
125
+ cache.set(segment, graphemes);
126
+ return graphemes;
127
+ }
128
+ function getTextUpToCursor(prepared, end) {
129
+ const graphemeCache = /* @__PURE__ */ new Map();
130
+ let result = "";
131
+ for (let segmentIndex = 0; segmentIndex < end.segmentIndex; segmentIndex += 1) {
132
+ result += prepared.segments[segmentIndex] ?? "";
133
+ }
134
+ if (end.graphemeIndex > 0) {
135
+ const segment = prepared.segments[end.segmentIndex];
136
+ if (segment !== void 0) {
137
+ result += getSegmentGraphemes(segment, graphemeCache).slice(0, end.graphemeIndex).join("");
138
+ }
139
+ }
140
+ return result;
141
+ }
142
+ function isWordLike(grapheme) {
143
+ return grapheme !== void 0 && wordLikePattern.test(grapheme);
144
+ }
145
+ function preferWordBoundary(graphemes, count) {
146
+ if (count <= 0 || count >= graphemes.length) {
147
+ return count;
148
+ }
149
+ if (!isWordLike(graphemes[count - 1]) || !isWordLike(graphemes[count])) {
150
+ return count;
151
+ }
152
+ let boundary = count;
153
+ while (boundary > 0 && isWordLike(graphemes[boundary - 1])) {
154
+ boundary -= 1;
155
+ }
156
+ while (boundary > 0 && whitespacePattern.test(graphemes[boundary - 1] ?? "")) {
157
+ boundary -= 1;
158
+ }
159
+ return boundary === 0 ? count : boundary;
160
+ }
161
+ function buildCandidateText(graphemes, count, ellipsis) {
162
+ const base = graphemes.slice(0, count).join("").trimEnd();
163
+ if (ellipsis.length === 0) {
164
+ return base;
165
+ }
166
+ return base.length === 0 ? ellipsis : `${base}${ellipsis}`;
167
+ }
168
+ function useTruncatedText({
169
+ text,
170
+ width,
171
+ font,
172
+ lineHeight,
173
+ typography,
174
+ prepareOptions,
175
+ maxLines,
176
+ ellipsis = "\u2026",
177
+ enabled = true
178
+ }) {
179
+ const resolvedFont = font ?? typography?.font;
180
+ const resolvedLineHeight = lineHeight ?? typography?.lineHeight;
181
+ const resolvedWidth = width ?? typography?.width;
182
+ if (resolvedFont === void 0 || resolvedLineHeight === void 0) {
183
+ throw new Error("useTruncatedText requires `font` and `lineHeight`, either directly or via `typography`.");
184
+ }
185
+ if (resolvedWidth === void 0) {
186
+ throw new Error("useTruncatedText requires `width`, either directly or via `typography`.");
187
+ }
188
+ if (!Number.isInteger(maxLines) || maxLines <= 0) {
189
+ throw new Error("useTruncatedText requires `maxLines` to be a positive integer.");
190
+ }
191
+ const prepared = usePreparedSegments({
192
+ text,
193
+ font: resolvedFont,
194
+ options: prepareOptions,
195
+ enabled
196
+ });
197
+ const fullLayout = usePretextLines({
198
+ prepared: prepared.prepared,
199
+ width: resolvedWidth,
200
+ lineHeight: resolvedLineHeight,
201
+ enabled
202
+ });
203
+ const normalizedWhiteSpace = prepareOptions?.whiteSpace;
204
+ return useMemo4(() => {
205
+ if (!enabled || !prepared.isReady || !fullLayout.isReady || prepared.prepared === null) {
206
+ return {
207
+ text: "",
208
+ didTruncate: false,
209
+ visibleLineCount: 0,
210
+ fullLineCount: 0,
211
+ height: 0,
212
+ isReady: false
213
+ };
214
+ }
215
+ if (fullLayout.lineCount <= maxLines) {
216
+ return {
217
+ text,
218
+ didTruncate: false,
219
+ visibleLineCount: fullLayout.lineCount,
220
+ fullLineCount: fullLayout.lineCount,
221
+ height: fullLayout.height,
222
+ isReady: true
223
+ };
224
+ }
225
+ const lastVisibleLine = fullLayout.lines[maxLines - 1];
226
+ if (lastVisibleLine === void 0) {
227
+ return {
228
+ text: "",
229
+ didTruncate: true,
230
+ visibleLineCount: 0,
231
+ fullLineCount: fullLayout.lineCount,
232
+ height: 0,
233
+ isReady: true
234
+ };
235
+ }
236
+ const visibleText = getTextUpToCursor(prepared.prepared, lastVisibleLine.end);
237
+ const graphemes = Array.from(graphemeSegmenter.segment(visibleText), ({ segment }) => segment);
238
+ const measurementCache = /* @__PURE__ */ new Map();
239
+ const normalizedOptions = normalizedWhiteSpace === void 0 ? void 0 : { whiteSpace: normalizedWhiteSpace };
240
+ const measureCandidate = (candidateText) => {
241
+ const cached = measurementCache.get(candidateText);
242
+ if (cached !== void 0) {
243
+ return cached;
244
+ }
245
+ const nextResult = candidateText.length === 0 ? { lineCount: 0, height: 0 } : layout2(prepare2(candidateText, resolvedFont, normalizedOptions), resolvedWidth, resolvedLineHeight);
246
+ measurementCache.set(candidateText, nextResult);
247
+ return nextResult;
248
+ };
249
+ let low = 0;
250
+ let high = graphemes.length;
251
+ let bestCount = 0;
252
+ while (low <= high) {
253
+ const mid = Math.floor((low + high) / 2);
254
+ const candidate = buildCandidateText(graphemes, mid, ellipsis);
255
+ const candidateResult = measureCandidate(candidate);
256
+ if (candidateResult.lineCount <= maxLines) {
257
+ bestCount = mid;
258
+ low = mid + 1;
259
+ } else {
260
+ high = mid - 1;
261
+ }
262
+ }
263
+ const preferredCount = preferWordBoundary(graphemes, bestCount);
264
+ const truncatedText = buildCandidateText(graphemes, preferredCount, ellipsis);
265
+ const truncatedLayout = measureCandidate(truncatedText);
266
+ return {
267
+ text: truncatedText,
268
+ didTruncate: true,
269
+ visibleLineCount: truncatedLayout.lineCount,
270
+ fullLineCount: fullLayout.lineCount,
271
+ height: truncatedLayout.height,
272
+ isReady: true
273
+ };
274
+ }, [
275
+ ellipsis,
276
+ enabled,
277
+ fullLayout.height,
278
+ fullLayout.isReady,
279
+ fullLayout.lineCount,
280
+ fullLayout.lines,
281
+ maxLines,
282
+ normalizedWhiteSpace,
283
+ prepared.isReady,
284
+ prepared.prepared,
285
+ resolvedFont,
286
+ resolvedLineHeight,
287
+ resolvedWidth,
288
+ text
289
+ ]);
290
+ }
291
+
292
+ // src/core/lib/typography.ts
293
+ function isShorthandInput(input) {
294
+ return "font" in input;
295
+ }
296
+ function resolveFont(input) {
297
+ if (isShorthandInput(input)) {
298
+ if (input.font.trim().length === 0) {
299
+ throw new Error("createPretextTypography requires a non-empty `font` string.");
300
+ }
301
+ return input.font;
302
+ }
303
+ if (input.family.trim().length === 0) {
304
+ throw new Error("createPretextTypography requires a non-empty `family` value.");
305
+ }
306
+ if (!Number.isFinite(input.size) || input.size <= 0) {
307
+ throw new Error("createPretextTypography requires `size` to be a positive number.");
308
+ }
309
+ return [input.weight === void 0 ? void 0 : `${input.weight}`, `${input.size}px`, input.family].filter(Boolean).join(" ");
310
+ }
311
+ function createPretextTypography(input) {
312
+ const font = resolveFont(input);
313
+ const { lineHeight, width } = input;
314
+ if (!Number.isFinite(lineHeight) || lineHeight <= 0) {
315
+ throw new Error("createPretextTypography requires `lineHeight` to be a positive number.");
316
+ }
317
+ const style = {
318
+ font,
319
+ lineHeight: `${lineHeight}px`
320
+ };
321
+ if (width !== void 0) {
322
+ style.width = `${width}px`;
323
+ }
324
+ return {
325
+ font,
326
+ lineHeight,
327
+ width,
328
+ style
329
+ };
330
+ }
331
+
332
+ // src/core/components/PText.tsx
333
+ import { forwardRef, useCallback, useEffect } from "react";
334
+ import { jsx } from "react/jsx-runtime";
335
+ function assignRef(ref, value) {
336
+ if (typeof ref === "function") {
337
+ ref(value);
338
+ return;
339
+ }
340
+ if (ref) {
341
+ ref.current = value;
342
+ }
343
+ }
344
+ function PTextInner({ as, children, font, lineHeight, prepareOptions, typography, width, onMeasure, style, ...rest }, forwardedRef) {
345
+ const { ref: observedRef, width: observedWidth } = useElementWidth();
346
+ const explicitWidth = width ?? typography?.width;
347
+ const resolvedWidth = explicitWidth ?? observedWidth;
348
+ const resolvedFont = font ?? typography?.font;
349
+ const resolvedLineHeight = lineHeight ?? typography?.lineHeight;
350
+ if (resolvedFont === void 0 || resolvedLineHeight === void 0) {
351
+ throw new Error("PText requires `font` and `lineHeight`, either directly or via `typography`.");
352
+ }
353
+ const { prepared } = usePreparedText({ text: children, font: resolvedFont, options: prepareOptions });
354
+ const result = usePretextLayout({ prepared, width: resolvedWidth, lineHeight: resolvedLineHeight });
355
+ const typographyStyle = createPretextTypography({
356
+ font: resolvedFont,
357
+ lineHeight: resolvedLineHeight,
358
+ width: explicitWidth
359
+ }).style;
360
+ const composedRef = useCallback(
361
+ (node) => {
362
+ if (explicitWidth === void 0) {
363
+ observedRef(node);
364
+ }
365
+ assignRef(forwardedRef, node);
366
+ },
367
+ [explicitWidth, forwardedRef, observedRef]
368
+ );
369
+ useEffect(() => {
370
+ if (onMeasure === void 0) {
371
+ return;
372
+ }
373
+ onMeasure({
374
+ width: resolvedWidth,
375
+ height: result.height,
376
+ lineCount: result.lineCount
377
+ });
378
+ }, [onMeasure, resolvedWidth, result.height, result.lineCount]);
379
+ const Tag = as ?? "p";
380
+ return /* @__PURE__ */ jsx(Tag, { ref: composedRef, style: { ...typographyStyle, ...style }, ...rest, children });
381
+ }
382
+ var PText = forwardRef(PTextInner);
383
+ export {
384
+ PText,
385
+ createPretextTypography,
386
+ useElementWidth,
387
+ useMeasuredText,
388
+ usePreparedSegments,
389
+ usePreparedText,
390
+ usePretextLayout,
391
+ usePretextLines,
392
+ useTruncatedText
393
+ };
@@ -0,0 +1 @@
1
+ export * from '@chenglou/pretext';
@@ -0,0 +1,2 @@
1
+ // src/pretext.ts
2
+ export * from "@chenglou/pretext";
@@ -0,0 +1,20 @@
1
+ import { PreparedText } from '@chenglou/pretext';
2
+
3
+ type PrepareOptions = {
4
+ whiteSpace?: 'normal' | 'pre-wrap';
5
+ };
6
+ type UsePreparedTextInput = {
7
+ text: string;
8
+ font: string;
9
+ options?: PrepareOptions;
10
+ enableProfiling?: boolean;
11
+ enabled?: boolean;
12
+ };
13
+ type UsePreparedTextResult = {
14
+ prepared: PreparedText | null;
15
+ prepareMs?: number;
16
+ isReady: boolean;
17
+ };
18
+ declare function usePreparedText({ text, font, options, enableProfiling, enabled }: UsePreparedTextInput): UsePreparedTextResult;
19
+
20
+ export { type PrepareOptions as P, type UsePreparedTextInput as U, type UsePreparedTextResult as a, usePreparedText as u };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@santjc/react-pretext",
3
+ "version": "0.1.0",
4
+ "description": "Simple React wrapper over @chenglou/pretext for deterministic text measurement before paint, with an advanced editorial subpath.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "sideEffects": false,
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ },
16
+ "./pretext": {
17
+ "types": "./dist/pretext.d.ts",
18
+ "import": "./dist/pretext.js"
19
+ },
20
+ "./editorial": {
21
+ "types": "./dist/editorial.d.ts",
22
+ "import": "./dist/editorial.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "keywords": [
32
+ "react",
33
+ "pretext",
34
+ "text-measurement",
35
+ "typography",
36
+ "layout",
37
+ "line-count",
38
+ "virtualized-list",
39
+ "accordion-height",
40
+ "masonry-layout",
41
+ "responsive-text",
42
+ "editorial-layout",
43
+ "newspaper-layout",
44
+ "text-flow"
45
+ ],
46
+ "scripts": {
47
+ "build": "tsup src/index.ts src/pretext.ts src/editorial.ts --format esm --dts",
48
+ "lint": "eslint src",
49
+ "test": "vitest run --config vitest.config.ts"
50
+ },
51
+ "peerDependencies": {
52
+ "react": "^18.0.0 || ^19.0.0",
53
+ "react-dom": "^18.0.0 || ^19.0.0"
54
+ },
55
+ "dependencies": {
56
+ "@chenglou/pretext": "^0.0.3"
57
+ }
58
+ }