@nkardaz/typography-rules 1.0.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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +911 -0
  3. package/dist/api/blacklist.d.ts +72 -0
  4. package/dist/api/htmlNodes.d.ts +30 -0
  5. package/dist/api/index.d.ts +6 -0
  6. package/dist/api/newRule.d.ts +51 -0
  7. package/dist/api/registerRule.d.ts +27 -0
  8. package/dist/api/rulesInit.d.ts +49 -0
  9. package/dist/functions/chemNotation.d.ts +10 -0
  10. package/dist/functions/clearSpaces.d.ts +16 -0
  11. package/dist/functions/index.cjs +514 -0
  12. package/dist/functions/index.d.ts +8 -0
  13. package/dist/functions/index.mjs +491 -0
  14. package/dist/functions/rubyText.d.ts +11 -0
  15. package/dist/functions/runt.d.ts +3 -0
  16. package/dist/functions/smartNumberGrouping.d.ts +25 -0
  17. package/dist/functions/smartQuotes.d.ts +29 -0
  18. package/dist/functions/wrapWithTag.d.ts +42 -0
  19. package/dist/glyphs/index.cjs +737 -0
  20. package/dist/glyphs/index.d.ts +53 -0
  21. package/dist/glyphs/index.mjs +714 -0
  22. package/dist/glyphs/proto.d.ts +11 -0
  23. package/dist/glyphs/registry.d.ts +728 -0
  24. package/dist/glyphs/types.d.ts +151 -0
  25. package/dist/helpers/index.cjs +268 -0
  26. package/dist/helpers/index.d.ts +133 -0
  27. package/dist/helpers/index.mjs +245 -0
  28. package/dist/helpers/types.d.ts +71 -0
  29. package/dist/index.cjs +985 -0
  30. package/dist/index.d.ts +5 -0
  31. package/dist/index.mjs +977 -0
  32. package/dist/style/index.d.ts +2 -0
  33. package/dist/style/main.css +16 -0
  34. package/dist/types.d.ts +223 -0
  35. package/dist/typography/aliases.d.ts +129 -0
  36. package/dist/typography/expressions/common.d.ts +29 -0
  37. package/dist/typography/expressions/en.d.ts +25 -0
  38. package/dist/typography/expressions/ru.d.ts +29 -0
  39. package/dist/typography/markup/common.d.ts +17 -0
  40. package/dist/typography/markup/en.d.ts +3 -0
  41. package/dist/typography/markup/index.d.ts +4 -0
  42. package/dist/typography/markup/ru.d.ts +3 -0
  43. package/dist/typography/sets/ang.d.ts +3 -0
  44. package/dist/typography/sets/common.d.ts +17 -0
  45. package/dist/typography/sets/en.d.ts +14 -0
  46. package/dist/typography/sets/index.d.ts +5 -0
  47. package/dist/typography/sets/ru.d.ts +16 -0
  48. package/dist/typography/store.d.ts +63 -0
  49. package/package.json +92 -0
@@ -0,0 +1,491 @@
1
+ // src/functions/smartNumberGrouping.ts
2
+ function smartNumberGrouping(text, { locale = "en-US", minLength = 5 }) {
3
+ return text.replace(
4
+ /(?<![\p{L}\d])([+\-\u2212]?)(\d(?:[\u00A0]?\d)*)([.,]\d+)?(?!\d)/gu,
5
+ (match, sign, rawInt, floatPart) => {
6
+ const intPart = rawInt.replace(/\u00A0/g, "");
7
+ if (intPart.length < minLength) return match;
8
+ const decimalDigits = floatPart ? floatPart.slice(1) : "";
9
+ const normalised = decimalDigits ? `${intPart}.${decimalDigits}` : intPart;
10
+ const num = Number(normalised);
11
+ if (!Number.isFinite(num)) return match;
12
+ const formatter = new Intl.NumberFormat(locale, {
13
+ maximumFractionDigits: decimalDigits.length,
14
+ minimumFractionDigits: decimalDigits.length
15
+ });
16
+ return (sign === "\u2212" ? "\u2212" : sign) + formatter.format(num);
17
+ }
18
+ );
19
+ }
20
+
21
+ // src/functions/smartQuotes.ts
22
+ import { PUNCTUATION } from "../glyphs/index.mjs";
23
+ function smartQuotes(text, {
24
+ outer = [PUNCTUATION.en.leftSided.outerQuoteOpen, PUNCTUATION.en.rightSided.outerQuoteClose],
25
+ inner = [PUNCTUATION.en.leftSided.innerQuoteOpen, PUNCTUATION.en.rightSided.innerQuoteClose]
26
+ } = {}) {
27
+ let result = "";
28
+ const stack = [];
29
+ for (let i = 0; i < text.length; i++) {
30
+ const char = text[i];
31
+ const prev = text[i - 1] ?? "";
32
+ const next = text[i + 1] ?? "";
33
+ if (char === '"') {
34
+ const afterSpace = prev === "" || /\s/.test(prev);
35
+ const beforeSpace = next === "" || /\s/.test(next);
36
+ let isOpen;
37
+ if (stack.length === 0) {
38
+ isOpen = true;
39
+ } else if (afterSpace && !beforeSpace) {
40
+ isOpen = true;
41
+ } else if (!afterSpace && beforeSpace) {
42
+ isOpen = false;
43
+ } else if (!afterSpace && !beforeSpace) {
44
+ isOpen = false;
45
+ } else {
46
+ isOpen = false;
47
+ }
48
+ if (isOpen) {
49
+ const q = stack.length === 0 ? outer : inner;
50
+ result += q[0];
51
+ stack.push('"');
52
+ } else {
53
+ const matchIdx = [...stack].reverse().indexOf('"');
54
+ if (matchIdx !== -1) {
55
+ stack.splice(stack.length - 1 - matchIdx, 1);
56
+ }
57
+ const q = stack.length === 0 ? outer : inner;
58
+ result += q[1];
59
+ }
60
+ continue;
61
+ }
62
+ if (char === "'") {
63
+ const insideDoubleQuotes = stack.includes('"');
64
+ const isApostrophe = /[a-zA-Z\u0430-\u044F\u0410-\u042F\u0451\u0401]/.test(prev) && /[a-zA-Z\u0430-\u044F\u0410-\u042F\u0451\u04010-9]/.test(next);
65
+ if (isApostrophe || !insideDoubleQuotes) {
66
+ result += char;
67
+ continue;
68
+ }
69
+ const lastDoubleIdx = stack.lastIndexOf('"');
70
+ const hasOpenSingle = stack.slice(lastDoubleIdx + 1).includes("'");
71
+ const isOpen = !hasOpenSingle;
72
+ if (isOpen) {
73
+ result += inner[0];
74
+ stack.push("'");
75
+ } else {
76
+ const matchIdx = [...stack].reverse().indexOf("'");
77
+ if (matchIdx !== -1) {
78
+ stack.splice(stack.length - 1 - matchIdx, 1);
79
+ }
80
+ result += inner[1];
81
+ }
82
+ continue;
83
+ }
84
+ result += char;
85
+ }
86
+ return result;
87
+ }
88
+
89
+ // src/functions/clearSpaces.ts
90
+ import { SPACES } from "../glyphs/index.mjs";
91
+ function clearSpaces(text, { spaces = SPACES.find("noBreak", "hair", "thin") ?? [SPACES._] } = {}) {
92
+ let result = text;
93
+ spaces.forEach((s) => {
94
+ const escaped = s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
95
+ const regex = new RegExp(`${escaped}{2,}`, "g");
96
+ result = result.replace(regex, s);
97
+ });
98
+ return result;
99
+ }
100
+
101
+ // src/functions/runt.ts
102
+ import { SPACES as SPACES2 } from "../glyphs/index.mjs";
103
+
104
+ // src/functions/wrapWithTag.ts
105
+ function wrapWithTag(text, settings = {}, tagSettings = {}) {
106
+ if ("expression" in settings) {
107
+ return wrapWithTagExpression(text, settings, tagSettings);
108
+ }
109
+ const { marker = "^", tag = "sup", wrapper = ["[", "]"] } = settings;
110
+ const result = [];
111
+ let i = 0;
112
+ while (i < text.length) {
113
+ const start = text.indexOf(wrapper[0] + marker, i);
114
+ if (start === -1) {
115
+ result.push({ type: "text", value: text.slice(i) });
116
+ break;
117
+ }
118
+ if (start > i) {
119
+ result.push({ type: "text", value: text.slice(i, start) });
120
+ }
121
+ let depth = 0;
122
+ let j = start;
123
+ let end = -1;
124
+ while (j < text.length) {
125
+ if (text[j] === wrapper[0]) depth++;
126
+ else if (text[j] === wrapper[1]) {
127
+ depth--;
128
+ if (depth === 0) {
129
+ end = j;
130
+ break;
131
+ }
132
+ }
133
+ j++;
134
+ }
135
+ if (end === -1) {
136
+ result.push({ type: "text", value: text.slice(start) });
137
+ break;
138
+ }
139
+ result.push({
140
+ type: tag,
141
+ data: { skipTypography: true },
142
+ ...tagSettings.className && { className: tagSettings.className },
143
+ ...tagSettings.attrs && { attrs: tagSettings.attrs },
144
+ children: wrapWithTag(text.slice(start + 2, end))
145
+ });
146
+ i = end + 1;
147
+ }
148
+ return result;
149
+ }
150
+ var TAG_OPEN = "<TAG>";
151
+ var TAG_CLOSE = "</TAG>";
152
+ function wrapWithTagExpression(text, { expression, tag = "sup", placement }, tagSettings = {}) {
153
+ const result = [];
154
+ const regex = new RegExp(
155
+ expression.source,
156
+ expression.flags.includes("g") ? expression.flags : expression.flags + "g"
157
+ );
158
+ let lastIndex = 0;
159
+ let match;
160
+ while ((match = regex.exec(text)) !== null) {
161
+ if (match[0].length === 0) {
162
+ regex.lastIndex++;
163
+ continue;
164
+ }
165
+ if (match.index > lastIndex) {
166
+ result.push({ type: "text", value: text.slice(lastIndex, match.index) });
167
+ }
168
+ if (placement) {
169
+ result.push(...resolvePlacement(match, placement, tag, tagSettings));
170
+ } else {
171
+ result.push({
172
+ type: tag,
173
+ data: { skipTypography: true },
174
+ ...tagSettings.className && { className: tagSettings.className },
175
+ ...tagSettings.attrs && { attrs: tagSettings.attrs },
176
+ children: [{ type: "text", value: match[0] }]
177
+ });
178
+ }
179
+ lastIndex = match.index + match[0].length;
180
+ }
181
+ if (lastIndex < text.length) {
182
+ result.push({ type: "text", value: text.slice(lastIndex) });
183
+ }
184
+ return result;
185
+ }
186
+ function resolvePlacement(match, placement, tag, tagSettings) {
187
+ const resolved = placement.replace(/\$(\d+)/g, (_, n) => match[Number(n)] ?? "");
188
+ const tagOpenIdx = resolved.indexOf(TAG_OPEN);
189
+ const tagCloseIdx = resolved.indexOf(TAG_CLOSE);
190
+ if (tagOpenIdx === -1 || tagCloseIdx === -1) {
191
+ return [{ type: "text", value: resolved }];
192
+ }
193
+ const nodes = [];
194
+ if (tagOpenIdx > 0) {
195
+ nodes.push({ type: "text", value: resolved.slice(0, tagOpenIdx) });
196
+ }
197
+ nodes.push({
198
+ type: tag,
199
+ data: { skipTypography: true },
200
+ ...tagSettings.className && { className: tagSettings.className },
201
+ ...tagSettings.attrs && { attrs: tagSettings.attrs },
202
+ children: [{ type: "text", value: resolved.slice(tagOpenIdx + TAG_OPEN.length, tagCloseIdx) }]
203
+ });
204
+ const after = resolved.slice(tagCloseIdx + TAG_CLOSE.length);
205
+ if (after) {
206
+ nodes.push({ type: "text", value: after });
207
+ }
208
+ return nodes;
209
+ }
210
+
211
+ // src/functions/runt.ts
212
+ var NOWRAP_MARKER = "~";
213
+ var NOWRAP_WRAPPER = ["[", "]"];
214
+ function runt(text, { threshold = 10, space = SPACES2.noBreak, minLineLength = 75 * 2 } = {}) {
215
+ if (text.length < minLineLength) return [{ type: "text", value: text }];
216
+ const segmenter = new Intl.Segmenter(void 0, {
217
+ granularity: "word"
218
+ });
219
+ const words = [...segmenter.segment(text)].filter((s) => s.isWordLike).map((s) => ({
220
+ index: s.index,
221
+ word: s.segment,
222
+ length: s.segment.length
223
+ }));
224
+ if (words.length < 2) return [{ type: "text", value: text }];
225
+ const noBreakPositions = /* @__PURE__ */ new Set();
226
+ let nowrapRange = null;
227
+ const markSpaceBefore = (pos) => {
228
+ for (let i = pos - 1; i >= 0; i--) {
229
+ const char = text[i];
230
+ if (char && /\s/u.test(char)) {
231
+ noBreakPositions.add(i);
232
+ break;
233
+ }
234
+ }
235
+ };
236
+ const last = words.at(-1);
237
+ if ([...last.word].length >= threshold) {
238
+ markSpaceBefore(last.index);
239
+ return [{ type: "text", value: applyNoBreaks(text, noBreakPositions, space) }];
240
+ }
241
+ const limit = threshold / 1.25;
242
+ for (let i = words.length - 1; i > 0; i--) {
243
+ const word = words[i];
244
+ const prev = words[i - 1];
245
+ markSpaceBefore(word.index);
246
+ if ([...prev.word].length >= limit) {
247
+ markSpaceBefore(prev.index);
248
+ nowrapRange = {
249
+ start: prev.index,
250
+ end: word.index + word.word.length
251
+ };
252
+ break;
253
+ }
254
+ }
255
+ const processed = applyNoBreaks(text, noBreakPositions, space);
256
+ if (!nowrapRange) return [{ type: "text", value: processed }];
257
+ const marked = processed.slice(0, nowrapRange.start) + NOWRAP_WRAPPER[0] + NOWRAP_MARKER + processed.slice(nowrapRange.start, nowrapRange.end) + NOWRAP_WRAPPER[1] + processed.slice(nowrapRange.end);
258
+ return wrapWithTag(
259
+ marked,
260
+ {
261
+ marker: NOWRAP_MARKER,
262
+ tag: "span",
263
+ wrapper: NOWRAP_WRAPPER
264
+ },
265
+ {
266
+ attrs: { style: "white-space: nowrap;" }
267
+ }
268
+ );
269
+ }
270
+ function applyNoBreaks(text, positions, space) {
271
+ if (positions.size === 0) return text;
272
+ let out = "";
273
+ for (let i = 0; i < text.length; i++) {
274
+ out += positions.has(i) ? space : text[i];
275
+ }
276
+ return out;
277
+ }
278
+
279
+ // src/functions/rubyText.ts
280
+ function rubyText(text, { marker = ":", wrapper = ["[", "]"] } = {}, { className, attrs } = {}) {
281
+ const result = [];
282
+ let i = 0;
283
+ const open = wrapper[0] + marker;
284
+ while (i < text.length) {
285
+ const baseStart = text.indexOf(open, i);
286
+ if (baseStart === -1) {
287
+ result.push({ type: "text", value: text.slice(i) });
288
+ break;
289
+ }
290
+ if (baseStart > i) {
291
+ result.push({ type: "text", value: text.slice(i, baseStart) });
292
+ }
293
+ let depth = 0;
294
+ let j = baseStart;
295
+ let baseEnd = -1;
296
+ while (j < text.length) {
297
+ if (text[j] === wrapper[0]) depth++;
298
+ else if (text[j] === wrapper[1]) {
299
+ depth--;
300
+ if (depth === 0) {
301
+ baseEnd = j;
302
+ break;
303
+ }
304
+ }
305
+ j++;
306
+ }
307
+ if (baseEnd === -1) {
308
+ result.push({ type: "text", value: text.slice(baseStart) });
309
+ break;
310
+ }
311
+ const furiganaStart = text.indexOf(open, baseEnd + 1);
312
+ if (furiganaStart !== baseEnd + 1) {
313
+ result.push({ type: "text", value: text.slice(baseStart, baseEnd + 1) });
314
+ i = baseEnd + 1;
315
+ continue;
316
+ }
317
+ depth = 0;
318
+ j = furiganaStart;
319
+ let furiganaEnd = -1;
320
+ while (j < text.length) {
321
+ if (text[j] === wrapper[0]) depth++;
322
+ else if (text[j] === wrapper[1]) {
323
+ depth--;
324
+ if (depth === 0) {
325
+ furiganaEnd = j;
326
+ break;
327
+ }
328
+ }
329
+ j++;
330
+ }
331
+ if (furiganaEnd === -1) {
332
+ result.push({ type: "text", value: text.slice(baseStart) });
333
+ break;
334
+ }
335
+ const baseInner = text.slice(baseStart + open.length, baseEnd);
336
+ const furiganaInner = text.slice(furiganaStart + open.length, furiganaEnd);
337
+ const baseParts = baseInner.split("|");
338
+ const furiganaParts = furiganaInner.split("|");
339
+ const children = [];
340
+ for (let k = 0; k < baseParts.length; k++) {
341
+ children.push({
342
+ type: "rb",
343
+ data: { skipTypography: true },
344
+ children: [{ type: "text", value: baseParts[k] ?? "" }]
345
+ });
346
+ children.push({
347
+ type: "rt",
348
+ data: { skipTypography: true },
349
+ children: [{ type: "text", value: furiganaParts[k] ?? "" }]
350
+ });
351
+ }
352
+ result.push({
353
+ type: "ruby",
354
+ data: { skipTypography: true },
355
+ ...className && { className },
356
+ ...attrs && { attrs },
357
+ children
358
+ });
359
+ i = furiganaEnd + 1;
360
+ }
361
+ return result;
362
+ }
363
+
364
+ // src/functions/chemNotation.ts
365
+ function parseScripts(content) {
366
+ const tokens = [];
367
+ let j = 0;
368
+ while (j < content.length) {
369
+ if (content[j] === "(") {
370
+ const close = content.indexOf(")", j);
371
+ if (close === -1) break;
372
+ const inner = content.slice(j + 1, close);
373
+ if (inner.startsWith("^")) {
374
+ tokens.push({ kind: "sup", value: inner.slice(1) });
375
+ } else if (inner.startsWith("_")) {
376
+ tokens.push({ kind: "sub", value: inner.slice(1) });
377
+ }
378
+ j = close + 1;
379
+ } else {
380
+ let k = j;
381
+ while (k < content.length && content[k] !== "(") k++;
382
+ const chunk = content.slice(j, k).trim();
383
+ if (chunk) tokens.push({ kind: "base", value: chunk });
384
+ j = k;
385
+ }
386
+ }
387
+ const baseIdx = tokens.findIndex((t) => t.kind === "base" && t.value);
388
+ if (baseIdx === -1) return null;
389
+ const base = tokens[baseIdx].value;
390
+ const left = tokens.slice(0, baseIdx);
391
+ const right = tokens.slice(baseIdx + 1);
392
+ return {
393
+ base,
394
+ supL: left.find((t) => t.kind === "sup")?.value ?? "",
395
+ subL: left.find((t) => t.kind === "sub")?.value ?? "",
396
+ supR: right.find((t) => t.kind === "sup")?.value ?? "",
397
+ subR: right.find((t) => t.kind === "sub")?.value ?? ""
398
+ };
399
+ }
400
+ function splitParts(content) {
401
+ const parts = [];
402
+ let depth = 0;
403
+ let start = 0;
404
+ for (let i = 0; i < content.length; i++) {
405
+ if (content[i] === "(") depth++;
406
+ else if (content[i] === ")") depth--;
407
+ else if (content[i] === "-" && depth === 0) {
408
+ parts.push(content.slice(start, i));
409
+ start = i + 1;
410
+ }
411
+ }
412
+ parts.push(content.slice(start));
413
+ return parts.map((p) => p.trim()).filter(Boolean);
414
+ }
415
+ function textNode(value) {
416
+ return { type: "text", value };
417
+ }
418
+ function mnNode(value) {
419
+ return { type: "mn", data: { skipTypography: true }, children: [textNode(value)] };
420
+ }
421
+ function buildMmultiscripts({ base, supL, subL, supR, subR }) {
422
+ const baseNode = { type: "mi", data: { skipTypography: true }, children: [textNode(base)] };
423
+ const children = [baseNode, mnNode(subR), mnNode(supR)];
424
+ if (subL || supL) {
425
+ children.push({ type: "mprescripts", data: { skipTypography: true }, children: [] });
426
+ children.push(mnNode(subL), mnNode(supL));
427
+ }
428
+ return { type: "mmultiscripts", data: { skipTypography: true }, children };
429
+ }
430
+ function chemNotation(text, { marker = "%", wrapper = ["[", "]"] } = {}, { className, attrs } = {}) {
431
+ const result = [];
432
+ const open = wrapper[0] + marker;
433
+ const openChar = wrapper[0];
434
+ const closeChar = wrapper[1];
435
+ let i = 0;
436
+ while (i < text.length) {
437
+ const start = text.indexOf(open, i);
438
+ if (start === -1) {
439
+ result.push(textNode(text.slice(i)));
440
+ break;
441
+ }
442
+ if (start > i) result.push(textNode(text.slice(i, start)));
443
+ let depth = 0;
444
+ let j = start;
445
+ let end = -1;
446
+ while (j < text.length) {
447
+ if (text[j] === openChar) depth++;
448
+ else if (text[j] === closeChar) {
449
+ depth--;
450
+ if (depth === 0) {
451
+ end = j;
452
+ break;
453
+ }
454
+ }
455
+ j++;
456
+ }
457
+ if (end === -1) {
458
+ result.push(textNode(text.slice(start)));
459
+ break;
460
+ }
461
+ const content = text.slice(start + open.length, end);
462
+ const parts = splitParts(content);
463
+ const parsed = parts.map(parseScripts).filter((p) => p !== null);
464
+ if (!parsed.length) {
465
+ result.push(textNode(text.slice(start, end + 1)));
466
+ i = end + 1;
467
+ continue;
468
+ }
469
+ result.push({
470
+ type: "math",
471
+ data: { skipTypography: true },
472
+ ...className && { className },
473
+ ...attrs && { attrs },
474
+ children: parsed.map(buildMmultiscripts)
475
+ });
476
+ i = end + 1;
477
+ }
478
+ return result;
479
+ }
480
+ export {
481
+ chemNotation,
482
+ clearSpaces,
483
+ resolvePlacement,
484
+ rubyText,
485
+ runt,
486
+ smartNumberGrouping,
487
+ smartQuotes,
488
+ wrapWithTag,
489
+ wrapWithTagExpression
490
+ };
491
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,11 @@
1
+ import type { Node, TagSettings, RubyTextSettings } from '@/types';
2
+ /**
3
+ * Parses ruby annotation syntax (base text and reading) and converts it to a <ruby> structure.
4
+ * Expected format: [marker]base|text[marker]reading
5
+ * * @param text - The input string containing ruby syntax
6
+ * @param settings - Configuration for the marker and wrapper delimiters
7
+ * @param tagSettings - Optional class name and attributes for the <ruby> element
8
+ * @returns An array of nodes containing text and <ruby> components (<rb>, <rt>)
9
+ */
10
+ export declare function rubyText(text: string, { marker, wrapper }?: RubyTextSettings, { className, attrs }?: TagSettings): Node[];
11
+ //# sourceMappingURL=rubyText.d.ts.map
@@ -0,0 +1,3 @@
1
+ import type { Node, RuntSettings } from '@/types';
2
+ export declare function runt(text: string, { threshold, space, minLineLength }?: RuntSettings): Node[];
3
+ //# sourceMappingURL=runt.d.ts.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Inserts locale-aware grouping into large numeric sequences inside text,
3
+ * delegating all formatting to `Intl.NumberFormat`.
4
+ *
5
+ * @param text - Input text that may contain numbers.
6
+ * @param locale - BCP 47 locale tag used by `Intl.NumberFormat` (e.g. `"ru"`, `"en-US"`).
7
+ *
8
+ * @param settings - Formatting options:
9
+ * - `minLength` — minimum integer digit count before grouping is applied (default: 5)
10
+ *
11
+ * @returns Text with formatted numbers using locale-appropriate grouping separators.
12
+ *
13
+ * @example
14
+ * smartNumberGrouping("Price: 1234567", "en-US");
15
+ * // "Price: 1,234,567"
16
+ *
17
+ * @example
18
+ * smartNumberGrouping("Цена: 1234567.891011", "ru");
19
+ * // "Цена: 1 234 567,891011"
20
+ */
21
+ export declare function smartNumberGrouping(text: string, { locale, minLength }: {
22
+ locale?: string | undefined;
23
+ minLength?: number | undefined;
24
+ }): string;
25
+ //# sourceMappingURL=smartNumberGrouping.d.ts.map
@@ -0,0 +1,29 @@
1
+ import type { QuoteSettings } from '@/types';
2
+ /**
3
+ * Smart typographic quotes replacement.
4
+ *
5
+ * Converts straight quotes (`"` and `'`) into typographically correct
6
+ * opening/closing quotes based on nesting context and surrounding characters.
7
+ *
8
+ * Supports:
9
+ * - Outer and inner quote levels
10
+ * - Nested quotation handling
11
+ * - Apostrophe detection for contractions (e.g. don't, it's)
12
+ *
13
+ * @param text - Input string containing raw quotes.
14
+ * @param quotes - Quote configuration for outer and inner levels.
15
+ * @param quotes.outer - Pair of outer quotes: [open, close]
16
+ * @param quotes.inner - Pair of inner quotes: [open, close]
17
+ *
18
+ * @returns String with typographically corrected quotes.
19
+ *
20
+ * @example
21
+ * smartQuotes('"Hello"');
22
+ * // «Hello» (depending on configuration)
23
+ *
24
+ * @example
25
+ * smartQuotes('"He said \'hi\'"');
26
+ * // «He said „hi“»
27
+ */
28
+ export declare function smartQuotes(text: string, { outer, inner, }?: QuoteSettings): string;
29
+ //# sourceMappingURL=smartQuotes.d.ts.map
@@ -0,0 +1,42 @@
1
+ import type { Node, TagSettings, WrapWithTagsSettings, WrapWithTagExpressionSettings } from '@/types';
2
+ /**
3
+ * Parses text and wraps content found within specific markers into HTML tags.
4
+ * Supports nested structures by recursively calling itself.
5
+ *
6
+ * @param text The input string to parse
7
+ * @param settings Configuration for the marker, tag type, and wrapper delimiters
8
+ * @param tagSettings Optional class name and attributes for the generated tag
9
+ * @returns An array of nodes representing the processed text and wrapped elements
10
+ */
11
+ export declare function wrapWithTag(text: string): Node[];
12
+ /**
13
+ * Parses text using a custom RegExp and wraps matched regions into HTML tags.
14
+ *
15
+ * When `placement` is provided, it acts as a structural template:
16
+ * - `$1`, `$2`, etc. reference capture groups from `expression`
17
+ * - `<TAG>...</TAG>` marks which part of the match gets wrapped
18
+ *
19
+ * @example
20
+ * // expression: /([\d\s]м)(2|3)/g, tag: 'sup', placement: '$1<TAG>$2</TAG>'
21
+ * // 'Площадь 25м2' → 'Площадь 25м<sup>2</sup>'
22
+ *
23
+ * @param text The input string to parse
24
+ * @param settings Configuration with expression, tag, and optional placement
25
+ * @param tagSettings Optional class name and attributes for the generated tag
26
+ * @returns An array of nodes representing the processed text and wrapped elements
27
+ */
28
+ export declare function wrapWithTag(text: string, settings: WrapWithTagsSettings, tagSettings?: TagSettings): Node[];
29
+ export declare function wrapWithTag(text: string, settings: WrapWithTagExpressionSettings, tagSettings?: TagSettings): Node[];
30
+ export declare function wrapWithTagExpression(text: string, { expression, tag, placement }: WrapWithTagExpressionSettings, tagSettings?: TagSettings): Node[];
31
+ /**
32
+ * Resolves a placement template into an array of nodes.
33
+ *
34
+ * Replaces `$N` references with capture groups and wraps the content
35
+ * inside `<TAG>...</TAG>` into an element node.
36
+ *
37
+ * @example
38
+ * // placement: '$1<TAG>$2</TAG>', match for /([\d\s]м)(2|3)/
39
+ * // → [TextNode('25м'), ElementNode('sup', [TextNode('2')])]
40
+ */
41
+ export declare function resolvePlacement(match: RegExpExecArray, placement: string, tag: string, tagSettings: TagSettings): Node[];
42
+ //# sourceMappingURL=wrapWithTag.d.ts.map