@rowsncolumns/utils 9.3.0 → 9.5.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 (51) hide show
  1. package/dist/cjs/index.d.ts +5 -0
  2. package/dist/cjs/index.d.ts.map +1 -1
  3. package/dist/cjs/index.js +5 -0
  4. package/dist/cjs/rich-text-html.d.ts +82 -0
  5. package/dist/cjs/rich-text-html.d.ts.map +1 -0
  6. package/dist/cjs/rich-text-html.js +601 -0
  7. package/dist/cjs/rich-text-links.d.ts +23 -0
  8. package/dist/cjs/rich-text-links.d.ts.map +1 -0
  9. package/dist/cjs/rich-text-links.js +48 -0
  10. package/dist/cjs/sparkline-groups.d.ts +102 -0
  11. package/dist/cjs/sparkline-groups.d.ts.map +1 -0
  12. package/dist/cjs/sparkline-groups.js +452 -0
  13. package/dist/cjs/string.d.ts +15 -0
  14. package/dist/cjs/string.d.ts.map +1 -1
  15. package/dist/cjs/string.js +46 -1
  16. package/dist/cjs/table-names.d.ts +55 -0
  17. package/dist/cjs/table-names.d.ts.map +1 -0
  18. package/dist/cjs/table-names.js +189 -0
  19. package/dist/cjs/table-styles.d.ts +9 -0
  20. package/dist/cjs/table-styles.d.ts.map +1 -1
  21. package/dist/cjs/table-styles.js +53 -1
  22. package/dist/cjs/table-totals.d.ts +31 -0
  23. package/dist/cjs/table-totals.d.ts.map +1 -0
  24. package/dist/cjs/table-totals.js +84 -0
  25. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  26. package/dist/esm/index.d.ts +5 -0
  27. package/dist/esm/index.d.ts.map +1 -1
  28. package/dist/esm/index.js +5 -0
  29. package/dist/esm/rich-text-html.d.ts +82 -0
  30. package/dist/esm/rich-text-html.d.ts.map +1 -0
  31. package/dist/esm/rich-text-html.js +520 -0
  32. package/dist/esm/rich-text-links.d.ts +23 -0
  33. package/dist/esm/rich-text-links.d.ts.map +1 -0
  34. package/dist/esm/rich-text-links.js +31 -0
  35. package/dist/esm/sparkline-groups.d.ts +102 -0
  36. package/dist/esm/sparkline-groups.d.ts.map +1 -0
  37. package/dist/esm/sparkline-groups.js +387 -0
  38. package/dist/esm/string.d.ts +15 -0
  39. package/dist/esm/string.d.ts.map +1 -1
  40. package/dist/esm/string.js +43 -0
  41. package/dist/esm/table-names.d.ts +55 -0
  42. package/dist/esm/table-names.d.ts.map +1 -0
  43. package/dist/esm/table-names.js +129 -0
  44. package/dist/esm/table-styles.d.ts +9 -0
  45. package/dist/esm/table-styles.d.ts.map +1 -1
  46. package/dist/esm/table-styles.js +34 -1
  47. package/dist/esm/table-totals.d.ts +31 -0
  48. package/dist/esm/table-totals.d.ts.map +1 -0
  49. package/dist/esm/table-totals.js +68 -0
  50. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  51. package/package.json +5 -3
@@ -0,0 +1,601 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clipboardHtmlToRichText = exports.findSingleClipboardCell = exports.htmlElementToRichText = exports.cssFontSizeToPt = exports.cssColorToHex = exports.mountClipboardHtml = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var color_1 = require("./color");
6
+ var dom_1 = require("./dom");
7
+ var BLOCK_TAGS = new Set([
8
+ "p",
9
+ "div",
10
+ "li",
11
+ "ul",
12
+ "ol",
13
+ "h1",
14
+ "h2",
15
+ "h3",
16
+ "h4",
17
+ "h5",
18
+ "h6",
19
+ "pre",
20
+ "tr",
21
+ "blockquote",
22
+ ]);
23
+ var SKIPPED_TAGS = new Set([
24
+ "style",
25
+ "script",
26
+ "meta",
27
+ "head",
28
+ "title",
29
+ "link",
30
+ "col",
31
+ "colgroup",
32
+ "template",
33
+ "noscript",
34
+ ]);
35
+ // `<font size="N">` → points (HTML 4 size keywords).
36
+ var FONT_SIZE_KEYWORDS = {
37
+ 1: 8,
38
+ 2: 10,
39
+ 3: 12,
40
+ 4: 14,
41
+ 5: 18,
42
+ 6: 24,
43
+ 7: 36,
44
+ };
45
+ // CSS named colors Excel / Word actually emit (`color:red`, `<font color=red>`).
46
+ // `getComputedStyle` in a browser already resolves names to `rgb()`, this
47
+ // table covers inline keywords and jsdom (which reports the declared value).
48
+ var NAMED_COLORS = {
49
+ black: "#000000",
50
+ white: "#FFFFFF",
51
+ red: "#FF0000",
52
+ green: "#008000",
53
+ lime: "#00FF00",
54
+ blue: "#0000FF",
55
+ yellow: "#FFFF00",
56
+ orange: "#FFA500",
57
+ purple: "#800080",
58
+ gray: "#808080",
59
+ grey: "#808080",
60
+ silver: "#C0C0C0",
61
+ maroon: "#800000",
62
+ navy: "#000080",
63
+ teal: "#008080",
64
+ olive: "#808000",
65
+ aqua: "#00FFFF",
66
+ cyan: "#00FFFF",
67
+ fuchsia: "#FF00FF",
68
+ magenta: "#FF00FF",
69
+ brown: "#A52A2A",
70
+ pink: "#FFC0CB",
71
+ gold: "#FFD700",
72
+ indigo: "#4B0082",
73
+ violet: "#EE82EE",
74
+ darkred: "#8B0000",
75
+ darkgreen: "#006400",
76
+ darkblue: "#00008B",
77
+ };
78
+ // Values that mean "no explicit color" — Excel's `windowtext`, CSS keywords.
79
+ var NON_COLORS = new Set([
80
+ "windowtext",
81
+ "inherit",
82
+ "initial",
83
+ "unset",
84
+ "currentcolor",
85
+ "transparent",
86
+ "auto",
87
+ "",
88
+ ]);
89
+ /**
90
+ * Parse an HTML clipboard payload into a hidden, mounted sandbox so
91
+ * `getComputedStyle` resolves the payload's own `<style>` rules (Excel's
92
+ * `.fontN` / `.xlNN` classes). Callers must `cleanup()` when done.
93
+ */
94
+ var mountClipboardHtml = function (clipboardValue) {
95
+ var _a, _b, _c, _d;
96
+ var parser = new DOMParser();
97
+ var doc = parser.parseFromString(clipboardValue, "text/html");
98
+ var sandbox = document.createElement("div");
99
+ sandbox.style.cssText =
100
+ "position:fixed;left:-10000px;top:-10000px;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none;font-size:10pt;";
101
+ sandbox.innerHTML = (_b = (_a = doc.body) === null || _a === void 0 ? void 0 : _a.innerHTML) !== null && _b !== void 0 ? _b : "";
102
+ var styles = (_d = (_c = doc.head) === null || _c === void 0 ? void 0 : _c.querySelectorAll("style")) !== null && _d !== void 0 ? _d : [];
103
+ styles.forEach(function (style) { return sandbox.appendChild(style.cloneNode(true)); });
104
+ document.body.appendChild(sandbox);
105
+ return {
106
+ root: sandbox,
107
+ cleanup: function () { return sandbox.remove(); },
108
+ };
109
+ };
110
+ exports.mountClipboardHtml = mountClipboardHtml;
111
+ var cssColorToHex = function (value) {
112
+ if (!value) {
113
+ return undefined;
114
+ }
115
+ var trimmed = value.trim();
116
+ var lower = trimmed.toLowerCase();
117
+ if (NON_COLORS.has(lower)) {
118
+ return undefined;
119
+ }
120
+ // `rgba(0, 0, 0, 0)` is the transparent default many engines report.
121
+ if (/^rgba\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)$/.test(lower)) {
122
+ return undefined;
123
+ }
124
+ var hex = (0, color_1.colorStringToHex)(trimmed);
125
+ if (hex) {
126
+ return hex.toUpperCase();
127
+ }
128
+ return NAMED_COLORS[lower];
129
+ };
130
+ exports.cssColorToHex = cssColorToHex;
131
+ /** CSS font-size → points. `pt` verbatim, `px` at 96dpi, `em`/`rem` on a 16px base. */
132
+ var cssFontSizeToPt = function (value) {
133
+ var _a;
134
+ if (!value) {
135
+ return undefined;
136
+ }
137
+ var match = value.trim().match(/^(\d+(?:\.\d+)?)\s*(pt|px|rem|em)?$/i);
138
+ if (!match) {
139
+ return undefined;
140
+ }
141
+ var numeric = parseFloat(match[1]);
142
+ if (!Number.isFinite(numeric) || numeric <= 0) {
143
+ return undefined;
144
+ }
145
+ var unit = ((_a = match[2]) !== null && _a !== void 0 ? _a : "px").toLowerCase();
146
+ var pt = unit === "pt"
147
+ ? numeric
148
+ : unit === "px"
149
+ ? numeric * 0.75
150
+ : numeric * 16 * 0.75;
151
+ return Number(pt.toFixed(1));
152
+ };
153
+ exports.cssFontSizeToPt = cssFontSizeToPt;
154
+ var firstFontFamily = function (value) {
155
+ if (!value) {
156
+ return undefined;
157
+ }
158
+ var family = value
159
+ .split(",")[0]
160
+ .trim()
161
+ .replace(/^["']|["']$/g, "")
162
+ .trim();
163
+ if (!family || /^(inherit|initial|unset)$/i.test(family)) {
164
+ return undefined;
165
+ }
166
+ return family;
167
+ };
168
+ var safeComputedStyle = function (element) {
169
+ if (!dom_1.canUseDOM || typeof getComputedStyle !== "function") {
170
+ return null;
171
+ }
172
+ try {
173
+ return getComputedStyle(element);
174
+ }
175
+ catch (_a) {
176
+ return null;
177
+ }
178
+ };
179
+ var readWeight = function (value) {
180
+ if (!value) {
181
+ return undefined;
182
+ }
183
+ var lower = value.trim().toLowerCase();
184
+ if (lower === "bold" || lower === "bolder") {
185
+ return true;
186
+ }
187
+ if (lower === "normal" || lower === "lighter") {
188
+ return false;
189
+ }
190
+ var numeric = Number(lower);
191
+ if (Number.isFinite(numeric)) {
192
+ return numeric >= 600;
193
+ }
194
+ return undefined;
195
+ };
196
+ var normalizeWhitespace = function (text) {
197
+ return text.replace(/[\n\r]+/g, " ").replace(/\s{2,}/g, " ");
198
+ };
199
+ /**
200
+ * The formatting an element applies on top of `inherited`: tag semantics
201
+ * (`<b>`, `<font color>`, `<a href>`, …) + inline `style` + (except for
202
+ * anchors) the cascaded computed style.
203
+ */
204
+ var readElementFormat = function (element, inherited) {
205
+ var _a, _b, _c, _d, _e, _f, _g, _h;
206
+ var format = tslib_1.__assign({}, inherited);
207
+ var tag = element.tagName.toLowerCase();
208
+ var attrColor;
209
+ var attrFace;
210
+ var attrSize;
211
+ switch (tag) {
212
+ case "b":
213
+ case "strong":
214
+ format.bold = true;
215
+ break;
216
+ case "i":
217
+ case "em":
218
+ format.italic = true;
219
+ break;
220
+ case "u":
221
+ format.underline = true;
222
+ break;
223
+ case "s":
224
+ case "strike":
225
+ case "del":
226
+ format.strikethrough = true;
227
+ break;
228
+ case "sup":
229
+ format.baselineOffset = "superscript";
230
+ break;
231
+ case "sub":
232
+ format.baselineOffset = "subscript";
233
+ break;
234
+ case "font": {
235
+ // Presentational attributes. They rank between the inline style and
236
+ // the cascaded computed style below: a browser folds them into the
237
+ // computed value, jsdom reports the inherited one instead.
238
+ attrColor = (0, exports.cssColorToHex)(element.getAttribute("color"));
239
+ attrFace = firstFontFamily(element.getAttribute("face"));
240
+ var size = parseInt((_a = element.getAttribute("size")) !== null && _a !== void 0 ? _a : "", 10);
241
+ if (!Number.isNaN(size) && FONT_SIZE_KEYWORDS[size]) {
242
+ attrSize = FONT_SIZE_KEYWORDS[size];
243
+ }
244
+ break;
245
+ }
246
+ case "a": {
247
+ var href = element.getAttribute("href");
248
+ if (href && href.trim()) {
249
+ format.link = { uri: href.trim() };
250
+ }
251
+ break;
252
+ }
253
+ }
254
+ var inline = element.style;
255
+ var computed = tag === "a" ? null : safeComputedStyle(element);
256
+ var pick = function (inlineValue, computedValue) { return (inlineValue && inlineValue.trim() ? inlineValue : computedValue); };
257
+ var weight = readWeight(pick(inline.fontWeight, computed === null || computed === void 0 ? void 0 : computed.fontWeight));
258
+ if (weight !== undefined) {
259
+ format.bold = weight;
260
+ }
261
+ var fontStyle = pick(inline.fontStyle, computed === null || computed === void 0 ? void 0 : computed.fontStyle);
262
+ if (fontStyle) {
263
+ var lower = fontStyle.trim().toLowerCase();
264
+ if (lower === "italic" || lower === "oblique") {
265
+ format.italic = true;
266
+ }
267
+ else if (lower === "normal") {
268
+ format.italic = false;
269
+ }
270
+ }
271
+ // `text-decoration` is not inherited in CSS but propagates visually, so a
272
+ // parent's underline stays on (inherited state) unless a child says `none`.
273
+ var decoration = pick(inline.textDecoration || inline.textDecorationLine, (computed === null || computed === void 0 ? void 0 : computed.textDecorationLine) || (computed === null || computed === void 0 ? void 0 : computed.textDecoration));
274
+ if (decoration) {
275
+ var lower = decoration.toLowerCase();
276
+ if (lower.includes("underline")) {
277
+ format.underline = true;
278
+ }
279
+ if (lower.includes("line-through")) {
280
+ format.strikethrough = true;
281
+ }
282
+ if (/^\s*none\s*$/.test(lower)) {
283
+ format.underline = false;
284
+ format.strikethrough = false;
285
+ }
286
+ }
287
+ var verticalAlign = (_b = pick(inline.verticalAlign, computed === null || computed === void 0 ? void 0 : computed.verticalAlign)) === null || _b === void 0 ? void 0 : _b.toLowerCase();
288
+ if (verticalAlign === "super") {
289
+ format.baselineOffset = "superscript";
290
+ }
291
+ else if (verticalAlign === "sub") {
292
+ format.baselineOffset = "subscript";
293
+ }
294
+ else if (verticalAlign === "baseline" && tag !== "sup" && tag !== "sub") {
295
+ format.baselineOffset = null;
296
+ }
297
+ var color = (_d = (_c = (0, exports.cssColorToHex)(inline.color)) !== null && _c !== void 0 ? _c : attrColor) !== null && _d !== void 0 ? _d : (0, exports.cssColorToHex)(computed === null || computed === void 0 ? void 0 : computed.color);
298
+ if (color) {
299
+ format.color = color;
300
+ }
301
+ var fontSize = (_f = (_e = (0, exports.cssFontSizeToPt)(inline.fontSize)) !== null && _e !== void 0 ? _e : attrSize) !== null && _f !== void 0 ? _f : (0, exports.cssFontSizeToPt)(computed === null || computed === void 0 ? void 0 : computed.fontSize);
302
+ if (fontSize !== undefined) {
303
+ format.fontSize = fontSize;
304
+ }
305
+ var fontFamily = (_h = (_g = firstFontFamily(inline.fontFamily)) !== null && _g !== void 0 ? _g : attrFace) !== null && _h !== void 0 ? _h : firstFontFamily(computed === null || computed === void 0 ? void 0 : computed.fontFamily);
306
+ if (fontFamily) {
307
+ format.fontFamily = fontFamily;
308
+ }
309
+ return format;
310
+ };
311
+ var isBlockElement = function (element) {
312
+ var _a;
313
+ var tag = element.tagName.toLowerCase();
314
+ if (BLOCK_TAGS.has(tag)) {
315
+ return true;
316
+ }
317
+ var display = (_a = element.style.display) === null || _a === void 0 ? void 0 : _a.toLowerCase();
318
+ return display === "block" || display === "list-item";
319
+ };
320
+ var collectSegments = function (node, inherited, segments) {
321
+ var e_1, _a;
322
+ var _b;
323
+ if (node.nodeType === Node.TEXT_NODE) {
324
+ var text = normalizeWhitespace((_b = node.textContent) !== null && _b !== void 0 ? _b : "");
325
+ if (text) {
326
+ segments.push({ text: text, format: inherited });
327
+ }
328
+ return;
329
+ }
330
+ if (node.nodeType !== Node.ELEMENT_NODE) {
331
+ return;
332
+ }
333
+ var element = node;
334
+ var tag = element.tagName.toLowerCase();
335
+ if (SKIPPED_TAGS.has(tag)) {
336
+ return;
337
+ }
338
+ if (tag === "br") {
339
+ segments.push({ text: "\n", format: inherited });
340
+ return;
341
+ }
342
+ var block = isBlockElement(element);
343
+ if (block && segments.length && !endsWithNewline(segments)) {
344
+ segments.push({ text: "\n", format: inherited });
345
+ }
346
+ var format = readElementFormat(element, inherited);
347
+ try {
348
+ for (var _c = tslib_1.__values(Array.from(element.childNodes)), _d = _c.next(); !_d.done; _d = _c.next()) {
349
+ var child = _d.value;
350
+ collectSegments(child, format, segments);
351
+ }
352
+ }
353
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
354
+ finally {
355
+ try {
356
+ if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
357
+ }
358
+ finally { if (e_1) throw e_1.error; }
359
+ }
360
+ if (block && segments.length && !endsWithNewline(segments)) {
361
+ segments.push({ text: "\n", format: inherited });
362
+ }
363
+ };
364
+ var endsWithNewline = function (segments) {
365
+ var last = segments[segments.length - 1];
366
+ return !!last && last.text.endsWith("\n");
367
+ };
368
+ var TEXT_KEYS = [
369
+ "bold",
370
+ "italic",
371
+ "underline",
372
+ "strikethrough",
373
+ "baselineOffset",
374
+ "color",
375
+ "fontSize",
376
+ "fontFamily",
377
+ ];
378
+ /** The properties of `format` that differ from `base` (the run's own contribution). */
379
+ var diffFormat = function (format, base) {
380
+ var e_2, _a;
381
+ var diff = {};
382
+ try {
383
+ for (var TEXT_KEYS_1 = tslib_1.__values(TEXT_KEYS), TEXT_KEYS_1_1 = TEXT_KEYS_1.next(); !TEXT_KEYS_1_1.done; TEXT_KEYS_1_1 = TEXT_KEYS_1.next()) {
384
+ var key = TEXT_KEYS_1_1.value;
385
+ var value = format[key];
386
+ var baseValue = base[key];
387
+ var normalizedValue = value !== null && value !== void 0 ? value : undefined;
388
+ var normalizedBase = baseValue !== null && baseValue !== void 0 ? baseValue : undefined;
389
+ if (normalizedValue === normalizedBase) {
390
+ continue;
391
+ }
392
+ if (normalizedValue === undefined || normalizedValue === false) {
393
+ // A run switching OFF a base property (bold cell, normal run).
394
+ if (normalizedBase) {
395
+ diff[key] =
396
+ key === "baselineOffset" ? null : false;
397
+ }
398
+ continue;
399
+ }
400
+ // Black on an unstyled base is the default text color, not a run color.
401
+ if (key === "color" &&
402
+ normalizedBase === undefined &&
403
+ typeof normalizedValue === "string" &&
404
+ normalizedValue.toUpperCase() === "#000000") {
405
+ continue;
406
+ }
407
+ diff[key] = normalizedValue;
408
+ }
409
+ }
410
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
411
+ finally {
412
+ try {
413
+ if (TEXT_KEYS_1_1 && !TEXT_KEYS_1_1.done && (_a = TEXT_KEYS_1.return)) _a.call(TEXT_KEYS_1);
414
+ }
415
+ finally { if (e_2) throw e_2.error; }
416
+ }
417
+ return diff;
418
+ };
419
+ var sameFormat = function (a, b) {
420
+ var e_3, _a;
421
+ var keys = new Set(tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(Object.keys(a)), false), tslib_1.__read(Object.keys(b)), false));
422
+ try {
423
+ for (var keys_1 = tslib_1.__values(keys), keys_1_1 = keys_1.next(); !keys_1_1.done; keys_1_1 = keys_1.next()) {
424
+ var key = keys_1_1.value;
425
+ var av = a[key];
426
+ var bv = b[key];
427
+ if (key === "link") {
428
+ if ((av === null || av === void 0 ? void 0 : av.uri) !== (bv === null || bv === void 0 ? void 0 : bv.uri)) {
429
+ return false;
430
+ }
431
+ continue;
432
+ }
433
+ if (av !== bv) {
434
+ return false;
435
+ }
436
+ }
437
+ }
438
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
439
+ finally {
440
+ try {
441
+ if (keys_1_1 && !keys_1_1.done && (_a = keys_1.return)) _a.call(keys_1);
442
+ }
443
+ finally { if (e_3) throw e_3.error; }
444
+ }
445
+ return true;
446
+ };
447
+ var isEmptyFormat = function (format) {
448
+ return Object.keys(format).length === 0;
449
+ };
450
+ /**
451
+ * Convert a mounted element (a clipboard `<td>`, or the sandbox wrapper for
452
+ * table-less HTML) into the engine's rich-text shape. See the module doc.
453
+ */
454
+ var htmlElementToRichText = function (root) {
455
+ var e_4, _a, e_5, _b;
456
+ var _c, _d, _e;
457
+ var baseFormat = normalizeBase(readElementFormat(root, {}));
458
+ var segments = [];
459
+ try {
460
+ for (var _f = tslib_1.__values(Array.from(root.childNodes)), _g = _f.next(); !_g.done; _g = _f.next()) {
461
+ var child = _g.value;
462
+ collectSegments(child, baseFormat, segments);
463
+ }
464
+ }
465
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
466
+ finally {
467
+ try {
468
+ if (_g && !_g.done && (_a = _f.return)) _a.call(_f);
469
+ }
470
+ finally { if (e_4) throw e_4.error; }
471
+ }
472
+ // Trim outer whitespace (leading/trailing spaces and line breaks) so the
473
+ // text matches `extractTextContent`, shifting the segments with it.
474
+ var joined = segments.map(function (s) { return s.text; }).join("");
475
+ var leading = joined.length - joined.trimStart().length;
476
+ var text = joined.trim();
477
+ var runs = [];
478
+ var links = [];
479
+ var cursor = -leading;
480
+ try {
481
+ for (var segments_1 = tslib_1.__values(segments), segments_1_1 = segments_1.next(); !segments_1_1.done; segments_1_1 = segments_1.next()) {
482
+ var segment = segments_1_1.value;
483
+ var start = Math.max(0, cursor);
484
+ var end = Math.min(text.length, cursor + segment.text.length);
485
+ cursor += segment.text.length;
486
+ if (end <= start) {
487
+ continue;
488
+ }
489
+ var format = diffFormat(segment.format, baseFormat);
490
+ var uri = (_c = segment.format.link) === null || _c === void 0 ? void 0 : _c.uri;
491
+ if (uri) {
492
+ format.link = { uri: uri };
493
+ var previous_1 = links[links.length - 1];
494
+ if (previous_1 && previous_1.uri === uri && previous_1.endIndex === start) {
495
+ previous_1.endIndex = end;
496
+ }
497
+ else {
498
+ links.push({ startIndex: start, endIndex: end, uri: uri });
499
+ }
500
+ }
501
+ if (isEmptyFormat(format)) {
502
+ continue;
503
+ }
504
+ var previous = runs[runs.length - 1];
505
+ if (previous &&
506
+ previous.nodeType === "text" &&
507
+ previous.endIndex === start &&
508
+ sameFormat(previous.format, format)) {
509
+ previous.endIndex = end;
510
+ continue;
511
+ }
512
+ runs.push({ startIndex: start, endIndex: end, format: format, nodeType: "text" });
513
+ }
514
+ }
515
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
516
+ finally {
517
+ try {
518
+ if (segments_1_1 && !segments_1_1.done && (_b = segments_1.return)) _b.call(segments_1);
519
+ }
520
+ finally { if (e_5) throw e_5.error; }
521
+ }
522
+ var wholeTextLink = links.find(function (link) { return link.startIndex === 0 && link.endIndex === text.length; });
523
+ var hyperlink = (_d = wholeTextLink === null || wholeTextLink === void 0 ? void 0 : wholeTextLink.uri) !== null && _d !== void 0 ? _d : (_e = links[0]) === null || _e === void 0 ? void 0 : _e.uri;
524
+ return {
525
+ text: text,
526
+ runs: runs.length ? runs : undefined,
527
+ links: links,
528
+ hyperlink: hyperlink,
529
+ baseFormat: baseFormat,
530
+ isRich: runs.length > 0 || links.length > 0,
531
+ };
532
+ };
533
+ exports.htmlElementToRichText = htmlElementToRichText;
534
+ // The base drops the segment-only `link` key and `false`/`null` values — a
535
+ // container can't be "not bold" more than the default already is.
536
+ var normalizeBase = function (format) {
537
+ var e_6, _a;
538
+ var base = {};
539
+ try {
540
+ for (var TEXT_KEYS_2 = tslib_1.__values(TEXT_KEYS), TEXT_KEYS_2_1 = TEXT_KEYS_2.next(); !TEXT_KEYS_2_1.done; TEXT_KEYS_2_1 = TEXT_KEYS_2.next()) {
541
+ var key = TEXT_KEYS_2_1.value;
542
+ var value = format[key];
543
+ if (value === undefined || value === null || value === false) {
544
+ continue;
545
+ }
546
+ base[key] = value;
547
+ }
548
+ }
549
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
550
+ finally {
551
+ try {
552
+ if (TEXT_KEYS_2_1 && !TEXT_KEYS_2_1.done && (_a = TEXT_KEYS_2.return)) _a.call(TEXT_KEYS_2);
553
+ }
554
+ finally { if (e_6) throw e_6.error; }
555
+ }
556
+ return base;
557
+ };
558
+ /**
559
+ * The element a single-cell clipboard payload's text lives in: the lone
560
+ * `<td>`/`<th>` of a one-cell table (Excel / Google Sheets), or the mounted
561
+ * wrapper for table-less HTML. `undefined` for a multi-cell table — that is a
562
+ * grid paste, not a rich-text one.
563
+ */
564
+ var findSingleClipboardCell = function (root) {
565
+ var cells = root.querySelectorAll("td,th");
566
+ if (cells.length > 1) {
567
+ return undefined;
568
+ }
569
+ if (cells.length === 1) {
570
+ return cells[0];
571
+ }
572
+ return root;
573
+ };
574
+ exports.findSingleClipboardCell = findSingleClipboardCell;
575
+ /**
576
+ * One-shot helper for the editor targets: mount the clipboard HTML, convert
577
+ * the single cell it carries, clean up. `undefined` when the payload is a
578
+ * multi-cell table or carries no formatting/links worth keeping (the caller
579
+ * then falls back to its plain-text handling).
580
+ */
581
+ var clipboardHtmlToRichText = function (html) {
582
+ if (!dom_1.canUseDOM || !(html === null || html === void 0 ? void 0 : html.trim())) {
583
+ return undefined;
584
+ }
585
+ var _a = (0, exports.mountClipboardHtml)(html), root = _a.root, cleanup = _a.cleanup;
586
+ try {
587
+ var cell = (0, exports.findSingleClipboardCell)(root);
588
+ if (!cell) {
589
+ return undefined;
590
+ }
591
+ var parsed = (0, exports.htmlElementToRichText)(cell);
592
+ if (!parsed.text || !parsed.isRich) {
593
+ return undefined;
594
+ }
595
+ return parsed;
596
+ }
597
+ finally {
598
+ cleanup();
599
+ }
600
+ };
601
+ exports.clipboardHtmlToRichText = clipboardHtmlToRichText;
@@ -0,0 +1,23 @@
1
+ import type { TextFormatRun } from "@rowsncolumns/common-types";
2
+ /**
3
+ * Reconcile a committed string's run-level links (`TextFormatRun.format.link`)
4
+ * with the cell-level `hyperlink`. Excel keeps ONE link per cell, painted and
5
+ * followed at the cell level; Google Sheets keeps several inside the text.
6
+ * Both engines apply this on commit (`SheetCell.setStringValue`, rnc-store
7
+ * `apply_set_value_rich_text`) so a rich paste lands the same way whether it
8
+ * came through the grid, the formula bar or the in-cell editor:
9
+ *
10
+ * - a cell without a hyperlink takes the FIRST run link as its hyperlink;
11
+ * - a run link equal to the cell hyperlink that spans the WHOLE text is
12
+ * redundant (that is exactly what the cell hyperlink says) and is stripped
13
+ * — the run itself goes when nothing else is left on it;
14
+ * - every other run link stays as data (the multi-link cell keeps them all).
15
+ */
16
+ export type ResolvedRunLinks = {
17
+ runs: TextFormatRun[] | undefined | null;
18
+ hyperlink: string | undefined;
19
+ /** True when `runs` or `hyperlink` differ from the input. */
20
+ changed: boolean;
21
+ };
22
+ export declare const promoteRunLinkToHyperlink: (text: string, runs: TextFormatRun[] | undefined | null, hyperlink: string | undefined | null) => ResolvedRunLinks;
23
+ //# sourceMappingURL=rich-text-links.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rich-text-links.d.ts","sourceRoot":"","sources":["../../rich-text-links.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,aAAa,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC;IACzC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,MAAM,MAAM,EACZ,MAAM,aAAa,EAAE,GAAG,SAAS,GAAG,IAAI,EACxC,WAAW,MAAM,GAAG,SAAS,GAAG,IAAI,KACnC,gBAkCF,CAAC"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promoteRunLinkToHyperlink = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var promoteRunLinkToHyperlink = function (text, runs, hyperlink) {
6
+ var e_1, _a;
7
+ var _b, _c;
8
+ if (!runs || runs.length === 0) {
9
+ return { runs: runs, hyperlink: hyperlink !== null && hyperlink !== void 0 ? hyperlink : undefined, changed: false };
10
+ }
11
+ var firstLink = runs.find(function (run) { var _a, _b; return run.nodeType !== "mention" && ((_b = (_a = run.format) === null || _a === void 0 ? void 0 : _a.link) === null || _b === void 0 ? void 0 : _b.uri); });
12
+ if (!firstLink || firstLink.nodeType === "mention") {
13
+ return { runs: runs, hyperlink: hyperlink !== null && hyperlink !== void 0 ? hyperlink : undefined, changed: false };
14
+ }
15
+ var resolvedHyperlink = hyperlink || firstLink.format.link.uri;
16
+ var changed = resolvedHyperlink !== (hyperlink !== null && hyperlink !== void 0 ? hyperlink : undefined);
17
+ var stripped = [];
18
+ try {
19
+ for (var runs_1 = tslib_1.__values(runs), runs_1_1 = runs_1.next(); !runs_1_1.done; runs_1_1 = runs_1.next()) {
20
+ var run = runs_1_1.value;
21
+ if (run.nodeType === "mention" ||
22
+ ((_c = (_b = run.format) === null || _b === void 0 ? void 0 : _b.link) === null || _c === void 0 ? void 0 : _c.uri) !== resolvedHyperlink ||
23
+ run.startIndex !== 0 ||
24
+ run.endIndex < text.length) {
25
+ stripped.push(run);
26
+ continue;
27
+ }
28
+ changed = true;
29
+ var _d = run.format, _link = _d.link, rest = tslib_1.__rest(_d, ["link"]);
30
+ if (Object.keys(rest).length > 0) {
31
+ stripped.push(tslib_1.__assign(tslib_1.__assign({}, run), { format: rest }));
32
+ }
33
+ }
34
+ }
35
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
36
+ finally {
37
+ try {
38
+ if (runs_1_1 && !runs_1_1.done && (_a = runs_1.return)) _a.call(runs_1);
39
+ }
40
+ finally { if (e_1) throw e_1.error; }
41
+ }
42
+ return {
43
+ runs: changed ? (stripped.length ? stripped : undefined) : runs,
44
+ hyperlink: resolvedHyperlink,
45
+ changed: changed,
46
+ };
47
+ };
48
+ exports.promoteRunLinkToHyperlink = promoteRunLinkToHyperlink;