@mono-lit/helper 0.0.8 → 0.0.10

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 (161) hide show
  1. package/dist/accordion-core-DaNyPWRr.js +277 -0
  2. package/dist/alert-core-DWtHbX_-.js +134 -0
  3. package/dist/breadcrumb-list-core-Dh68T8Cf.js +747 -0
  4. package/dist/{mono-button-CLkHg3WW.js → button-core-DerXg84Q.js} +3 -276
  5. package/dist/button-dropdown-core-DKYEOyjy.js +654 -0
  6. package/dist/button-utils-D5-66M02.js +63 -0
  7. package/dist/card-utils-l80qAmrN.js +312 -0
  8. package/dist/chart-core-DV7hD3ZY.js +801 -0
  9. package/dist/checkbox-utils-DKdrhAzF.js +337 -0
  10. package/dist/chip-utils-DpRrd7Zj.js +403 -0
  11. package/dist/{data-source-controller-C6C4ej_M.js → data-source-controller-DS_JLrmS.js} +1 -1
  12. package/dist/{data-source-options-fxT5QIX4.js → data-source-options-DJUNU26C.js} +6 -6
  13. package/dist/{data-source-read-BL266IW0.js → data-source-read-uKJpRAzx.js} +2 -2
  14. package/dist/date-core-CyuSyvvG.js +734 -0
  15. package/dist/drawer-core-DbFWvNln.js +759 -0
  16. package/dist/dropdown-core-BCH1pK14.js +511 -0
  17. package/dist/dropdown-table-core-SfHUJVFq.js +1342 -0
  18. package/dist/export.js +1 -1
  19. package/dist/{field-path-BabiMrrA.js → field-path-DK2VBWHl.js} +1 -1
  20. package/dist/field-tooltip.css_raw-DVblekCl.js +5 -0
  21. package/dist/{field-validation.css_raw-Cx6SGrfP.js → field-validation.css_raw-BU7C6173.js} +1 -170
  22. package/dist/file-upload-core-Cz_dBQMx.js +624 -0
  23. package/dist/filter-builder-core-DyWjbBP7.js +381 -0
  24. package/dist/{filter-builder-B6Y2mmbM.js → filter-builder-vbqIuPhX.js} +1 -1
  25. package/dist/{filter-eval-ChDSaAUH.js → filter-eval-CqeBnMYh.js} +1 -1
  26. package/dist/{filter-odata-Bi27XUpQ.js → filter-odata-GsczYKNC.js} +1 -1
  27. package/dist/{hydration-flush-BaE9gN3C.js → hydration-flush-Volt9X7Q.js} +2 -1071
  28. package/dist/index.js +11 -11
  29. package/dist/{index.node-CLqE8F8P.js → index.node-Csy9zrr1.js} +4 -4
  30. package/dist/index.node.js +11 -11
  31. package/dist/input-utils-BglY-k0H.js +1145 -0
  32. package/dist/menu-core-DkAYuCZ2.js +831 -0
  33. package/dist/modal-core-CSBSPN_m.js +844 -0
  34. package/dist/mono-button-DVvZ03P-.js +280 -0
  35. package/dist/{mono-button.shadow-DqZMpcqY.js → mono-button.shadow-BANxMGln.js} +6 -594
  36. package/dist/{mono-data-chart-NKPdAgVq.js → mono-data-chart-ChF4J9RT.js} +6 -6
  37. package/dist/{mono-data-dropdown-DUXo_jxd.js → mono-data-dropdown-Bgj2PDfm.js} +1 -1
  38. package/dist/{mono-data-grid-Bb00QM72.js → mono-data-grid-DJkp96N-.js} +346 -51
  39. package/dist/{mono-event-BPku9HpS.js → mono-event-CAZf0F_k.js} +14 -9
  40. package/dist/{mono-form-controller-BrA10erm.js → mono-form-controller-DxZLvyg6.js} +2 -1
  41. package/dist/mono-table-checkbox-core-BKOMx8R5.js +5288 -0
  42. package/dist/nav-core-B5qU2dzn.js +225 -0
  43. package/dist/{normalize-error-D93O4q8g.js → normalize-error-BEcd1tmS.js} +2 -2
  44. package/dist/otp-core-DXg3vFLh.js +758 -0
  45. package/dist/radio-utils-Dk9Tl1jz.js +319 -0
  46. package/dist/rich-text-editor-core-Ddd_QaLn.js +1280 -0
  47. package/dist/select-utils-BgArYHk8.js +2140 -0
  48. package/dist/sidebar-core-CD6txDo3.js +583 -0
  49. package/dist/slider-core-vcWr321-.js +1014 -0
  50. package/dist/src/components/dropdown-table/dropdown-table-core.d.ts +2 -2
  51. package/dist/src/components/dropdown-table/dropdown-table-types.d.ts +1 -1
  52. package/dist/src/components/dropdown-table/index.d.ts +1 -1
  53. package/dist/src/components/dropdown-table/index.shadow.d.ts +1 -1
  54. package/dist/src/components/dropdown-table/mono-data-dropdown.d.ts +1 -1
  55. package/dist/src/components/form/form-types.d.ts +9 -3
  56. package/dist/src/components/input/input-core.d.ts +1 -1
  57. package/dist/src/components/input/input-types.d.ts +1 -1
  58. package/dist/src/components/otp/otp-core.d.ts +1 -1
  59. package/dist/src/components/select/select-core.d.ts +1 -1
  60. package/dist/src/components/select/select-types.d.ts +1 -1
  61. package/dist/src/components/slider/slider-core.d.ts +1 -1
  62. package/dist/src/components/slider/slider-types.d.ts +1 -1
  63. package/dist/src/components/table/filter-suggestion.d.ts +171 -0
  64. package/dist/src/components/table/index.d.ts +5 -4
  65. package/dist/src/components/table/index.shadow.d.ts +5 -4
  66. package/dist/src/components/table/mono-data-grid.d.ts +58 -3
  67. package/dist/src/components/table/mono-table-empty-core.d.ts +37 -0
  68. package/dist/src/components/table/mono-table-search-core.d.ts +22 -3
  69. package/dist/src/components/table/table-types.d.ts +74 -4
  70. package/dist/src/components/tag-input/tag-input-core.d.ts +1 -1
  71. package/dist/src/components/tag-input/tag-input-types.d.ts +1 -1
  72. package/dist/src/components/textarea/textarea-core.d.ts +1 -1
  73. package/dist/src/composables/mono-event.d.ts +7 -1
  74. package/dist/src/entries/index.d.ts +8 -6
  75. package/dist/src/entries/index.node.d.ts +8 -6
  76. package/dist/src/search/index.d.ts +1 -1
  77. package/dist/src/utils/group-count.d.ts +36 -0
  78. package/dist/switch-utils-CTZ4id2V.js +338 -0
  79. package/dist/tabs-core-r5-B8k_N.js +317 -0
  80. package/dist/tag-input-utils-D2JA90DQ.js +3060 -0
  81. package/dist/textarea-utils-VU1KeXm8.js +821 -0
  82. package/dist/ui/accordion.js +2 -274
  83. package/dist/ui/alert.js +2 -131
  84. package/dist/ui/breadcrumb.js +4 -745
  85. package/dist/ui/button-dropdown.js +4 -652
  86. package/dist/ui/button.js +2 -63
  87. package/dist/ui/card.js +2 -310
  88. package/dist/ui/chart.js +4 -800
  89. package/dist/ui/checkbox.js +4 -335
  90. package/dist/ui/chip.js +4 -402
  91. package/dist/ui/date.js +3 -732
  92. package/dist/ui/drawer.js +3 -757
  93. package/dist/ui/dropdown-table.js +5 -1341
  94. package/dist/ui/dropdown.js +4 -509
  95. package/dist/ui/file-upload.js +3 -622
  96. package/dist/ui/filter.js +5 -380
  97. package/dist/ui/form.js +1 -1
  98. package/dist/ui/index.css +130 -5
  99. package/dist/ui/input.js +3 -1141
  100. package/dist/ui/menu.js +4 -828
  101. package/dist/ui/modal.js +4 -843
  102. package/dist/ui/nav.js +4 -223
  103. package/dist/ui/otp.js +4 -756
  104. package/dist/ui/radio.js +4 -317
  105. package/dist/ui/rich-text-editor.js +4 -1278
  106. package/dist/ui/select.js +5 -2138
  107. package/dist/ui/shadow/accordion.js +4 -273
  108. package/dist/ui/shadow/alert.js +4 -130
  109. package/dist/ui/shadow/breadcrumb.js +5 -744
  110. package/dist/ui/shadow/button-dropdown.js +6 -652
  111. package/dist/ui/shadow/button.js +3 -63
  112. package/dist/ui/shadow/card.js +4 -309
  113. package/dist/ui/shadow/chart.js +6 -1576
  114. package/dist/ui/shadow/checkbox.js +4 -334
  115. package/dist/ui/shadow/chip.js +4 -396
  116. package/dist/ui/shadow/date.js +7 -731
  117. package/dist/ui/shadow/drawer.js +4 -756
  118. package/dist/ui/shadow/dropdown-table.js +8 -1762
  119. package/dist/ui/shadow/dropdown.js +4 -508
  120. package/dist/ui/shadow/file-upload.js +4 -620
  121. package/dist/ui/shadow/filter.js +7 -379
  122. package/dist/ui/shadow/input.js +6 -1140
  123. package/dist/ui/shadow/menu.js +5 -828
  124. package/dist/ui/shadow/modal.js +6 -1063
  125. package/dist/ui/shadow/nav.js +5 -223
  126. package/dist/ui/shadow/otp.js +6 -754
  127. package/dist/ui/shadow/radio.js +4 -316
  128. package/dist/ui/shadow/rich-text-editor.js +4 -1276
  129. package/dist/ui/shadow/select.js +6 -2136
  130. package/dist/ui/shadow/sidebar.js +5 -581
  131. package/dist/ui/shadow/slider.js +5 -1010
  132. package/dist/ui/shadow/switch.js +4 -335
  133. package/dist/ui/shadow/table.js +10 -4501
  134. package/dist/ui/shadow/tabs.js +6 -315
  135. package/dist/ui/shadow/tag-input.js +7 -3057
  136. package/dist/ui/shadow/textarea.js +6 -817
  137. package/dist/ui/sidebar.js +3 -581
  138. package/dist/ui/slider.js +4 -1012
  139. package/dist/ui/switch.js +4 -336
  140. package/dist/ui/table.js +9 -4502
  141. package/dist/ui/tabs.js +4 -315
  142. package/dist/ui/tag-input.js +5 -3058
  143. package/dist/ui/textarea.js +4 -819
  144. package/dist/vue.d.ts +3 -2
  145. package/package.json +3 -3
  146. package/dist/chip-strip-bNfLn52Z.js +0 -215
  147. package/dist/data-source-controller-Dx6IQ2CN.js +0 -243
  148. package/dist/data-source-options-BM4r4PRw.js +0 -558
  149. package/dist/data-source-read-HyBCRg1t.js +0 -242
  150. package/dist/excel-BXzr3uI1.js +0 -784
  151. package/dist/export-ixRdCS2y.js +0 -2388
  152. package/dist/field-tooltip-addon-CWCp19mJ.js +0 -702
  153. package/dist/field-tooltip.css_raw-U5UeTa2d.js +0 -269
  154. package/dist/filter-builder-C0T3pMx2.js +0 -208
  155. package/dist/form-control-core-BbiACMtL.js +0 -226
  156. package/dist/icon-ClxnYhBG.js +0 -19
  157. package/dist/import-Dc4eXC02.js +0 -612
  158. package/dist/light-slots-_no0r2Z-.js +0 -246
  159. package/dist/markdown-uGlE_OfJ.js +0 -20191
  160. package/dist/mono-data-grid-Bkr-e1-i.js +0 -4043
  161. package/dist/normalize-error-BmLbgANw.js +0 -1048
@@ -1,784 +0,0 @@
1
- import { t as flattenInline } from "./markdown-uGlE_OfJ.js";
2
-
3
- //#region src/export/styles.ts
4
- /**
5
- * Built-in styles. Consumers override individual keys via `options.styles`
6
- * without having to redefine a whole style.
7
- */
8
- const DEFAULT_STYLES = {
9
- title: {
10
- bold: true,
11
- size: 16,
12
- align: "left",
13
- height: 24
14
- },
15
- h1: {
16
- bold: true,
17
- size: 16,
18
- height: 24
19
- },
20
- h2: {
21
- bold: true,
22
- size: 14,
23
- height: 21
24
- },
25
- h3: {
26
- bold: true,
27
- size: 12,
28
- height: 18
29
- },
30
- h4: {
31
- bold: true,
32
- size: 11
33
- },
34
- h5: {
35
- bold: true,
36
- size: 11
37
- },
38
- h6: {
39
- bold: true,
40
- size: 11,
41
- italic: true
42
- },
43
- /** Table header row. */
44
- header: {
45
- bold: true,
46
- color: "#FFFFFF",
47
- bg: "#374151",
48
- align: "center",
49
- valign: "middle",
50
- border: "all",
51
- wrap: true
52
- },
53
- /** A group's banner row above its rows. */
54
- groupHeader: {
55
- bold: true,
56
- bg: "#E5E7EB",
57
- border: "bottom"
58
- },
59
- /** A subtotal / grand-total row. */
60
- total: {
61
- bold: true,
62
- bg: "#F3F4F6",
63
- border: "top"
64
- },
65
- currency: {
66
- numFmt: "#,##0.00",
67
- align: "right"
68
- },
69
- number: {
70
- numFmt: "#,##0",
71
- align: "right"
72
- },
73
- date: {
74
- numFmt: "yyyy-mm-dd",
75
- align: "center"
76
- },
77
- note: {
78
- italic: true,
79
- color: "#6B7280"
80
- },
81
- danger: {
82
- color: "#991B1B",
83
- bg: "#FEE2E2"
84
- },
85
- success: {
86
- color: "#065F46",
87
- bg: "#D1FAE5"
88
- },
89
- muted: { color: "#9CA3AF" },
90
- link: {
91
- color: "#0563C1",
92
- underline: true
93
- }
94
- };
95
- /** Merge user styles over the defaults, per style name (shallow, key by key). */
96
- function mergeStyles(overrides = {}) {
97
- const merged = {};
98
- for (const [name, style] of Object.entries(DEFAULT_STYLES)) merged[name] = { ...style };
99
- for (const [name, style] of Object.entries(overrides)) merged[name] = {
100
- ...merged[name] ?? {},
101
- ...style
102
- };
103
- return merged;
104
- }
105
- /** Later styles win, key by key — used to layer row style → cell style. */
106
- function combineStyles(...styles) {
107
- return Object.assign({}, ...styles.filter(Boolean));
108
- }
109
- /**
110
- * ExcelJS wants `ARGB` without the `#`. Accepts `#rgb`, `#rrggbb` and
111
- * `#aarrggbb`; anything already 8 hex digits is passed through.
112
- */
113
- function toArgb(color) {
114
- if (!color) return void 0;
115
- let hex = color.replace("#", "").trim();
116
- if (hex.length === 3) hex = hex.split("").map((c) => c + c).join("");
117
- if (hex.length === 6) hex = `FF${hex}`;
118
- return /^[0-9a-fA-F]{8}$/.test(hex) ? hex.toUpperCase() : void 0;
119
- }
120
- const VALIGN = {
121
- top: "top",
122
- middle: "middle",
123
- bottom: "bottom"
124
- };
125
- /** Translate a semantic style into ExcelJS cell options. */
126
- function toExcelStyle(style) {
127
- const out = {};
128
- const font = {};
129
- if (style.bold) font.bold = true;
130
- if (style.italic) font.italic = true;
131
- if (style.underline) font.underline = true;
132
- if (style.size) font.size = style.size;
133
- if (style.font) font.name = style.font;
134
- const fontColor = toArgb(style.color);
135
- if (fontColor) font.color = { argb: fontColor };
136
- if (Object.keys(font).length) out.font = font;
137
- const bg = toArgb(style.bg);
138
- if (bg) out.fill = {
139
- type: "pattern",
140
- pattern: "solid",
141
- fgColor: { argb: bg }
142
- };
143
- const alignment = {};
144
- if (style.align) alignment.horizontal = style.align;
145
- if (style.valign) alignment.vertical = VALIGN[style.valign];
146
- if (style.wrap) alignment.wrapText = true;
147
- if (style.indent) alignment.indent = style.indent;
148
- if (Object.keys(alignment).length) out.alignment = alignment;
149
- if (style.border && style.border !== "none") {
150
- const edge = {
151
- style: "thin",
152
- color: { argb: toArgb(style.borderColor) ?? "FFD1D5DB" }
153
- };
154
- if (style.border === "all" || style.border === "outline") out.border = {
155
- top: edge,
156
- left: edge,
157
- bottom: edge,
158
- right: edge
159
- };
160
- else if (style.border === "bottom") out.border = { bottom: edge };
161
- else if (style.border === "top") out.border = { top: edge };
162
- }
163
- if (style.numFmt) out.numFmt = style.numFmt;
164
- return out;
165
- }
166
-
167
- //#endregion
168
- //#region src/export/sheet-name.ts
169
- /**
170
- * Worksheet names, which Excel is fussier about than anything else in a
171
- * workbook — and every rule below is one it enforces by refusing to open the
172
- * file, not by degrading.
173
- *
174
- * · at most 31 characters
175
- * · none of `* ? : \ / [ ]`
176
- * · cannot be empty
177
- * · cannot start or end with an apostrophe
178
- * · unique within the workbook, case-insensitively
179
- *
180
- * A name derived from user data — a document number like `TU/2026/001` — breaks
181
- * three of those at once, so nothing may reach `addWorksheet` unsanitised.
182
- */
183
- /**
184
- * The characters Excel refuses outright.
185
- *
186
- * Note what is NOT in here: the space and the hyphen are both perfectly legal,
187
- * and stripping them would quietly rewrite a document number like `TP-001` into
188
- * `TP 001`, so the sheet name would no longer match what the master sheet shows.
189
- */
190
- const FORBIDDEN = /[*?:\\/[\]]/g;
191
- /**
192
- * One name, made legal. Does NOT make it unique — that is
193
- * {@link SheetNames.take}'s job, because uniqueness needs the whole workbook.
194
- */
195
- function sanitizeSheetName(raw, fallback = "Sheet") {
196
- return String(raw ?? "").replace(FORBIDDEN, " ").replace(/\s+/g, " ").trim().replace(/^'+|'+$/g, "").trim().slice(0, 31).replace(/'+$/, "").trim() || fallback;
197
- }
198
- /**
199
- * Escape a name for use inside a reference like `#'Sheet'!A1`.
200
- *
201
- * The name is single-quoted there, so a literal apostrophe has to be doubled.
202
- * {@link sanitizeSheetName} only strips apostrophes at the ends — one in the
203
- * middle (`Bob's Data`) is legal and has to survive.
204
- */
205
- function escapeSheetRef(name) {
206
- return name.replace(/'/g, "''");
207
- }
208
- /** A reference to `A1` of `name`, ready to use as a hyperlink target. */
209
- function sheetRef(name, cell = "A1") {
210
- return `#'${escapeSheetRef(name)}'!${cell}`;
211
- }
212
- /**
213
- * The workbook's name registry.
214
- *
215
- * Deliberately the ONLY place a name is claimed. The implementation this feature
216
- * is modelled on reserves a name up front and then dedupes a second time inside
217
- * its writer against the same set — so the second call always collides with the
218
- * reservation it just made, and every sheet in that workbook ends up suffixed
219
- * ` (2)`. One registry, one `take()`, no second opinion.
220
- */
221
- var SheetNames = class {
222
- constructor(existing = []) {
223
- this.used = /* @__PURE__ */ new Set();
224
- for (const name of existing) this.used.add(name.toLowerCase());
225
- }
226
- /** Claim a legal, unique name derived from `raw`. */
227
- take(raw, fallback = "Sheet") {
228
- const base = sanitizeSheetName(raw, fallback);
229
- if (!this.used.has(base.toLowerCase())) {
230
- this.used.add(base.toLowerCase());
231
- return base;
232
- }
233
- for (let n = 2; n < 1e4; n += 1) {
234
- const suffix = ` (${n})`;
235
- const candidate = `${base.slice(0, 31 - suffix.length).trim()}${suffix}`;
236
- if (!this.used.has(candidate.toLowerCase())) {
237
- this.used.add(candidate.toLowerCase());
238
- return candidate;
239
- }
240
- }
241
- const last = `${base.slice(0, 20)} ${Date.now().toString(36)}`.slice(0, 31);
242
- this.used.add(last.toLowerCase());
243
- return last;
244
- }
245
- };
246
-
247
- //#endregion
248
- //#region src/export/excel/nodes.ts
249
- /** An ISO date string (`2026-07-21`, optionally with a time part). */
250
- const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
251
- /** A plain number, allowing a leading sign and a decimal part. */
252
- const NUMERIC_RE = /^[+-]?\d+(?:\.\d+)?$/;
253
- /**
254
- * Walks an mdast tree and plans a worksheet. Planning (rather than writing
255
- * ExcelJS objects directly) keeps this file free of any ExcelJS import, so the
256
- * mapping stays testable and a future renderer can reuse the same walk.
257
- */
258
- var WorksheetWriter = class {
259
- constructor(ctx) {
260
- this.ctx = ctx;
261
- this.plan = {
262
- rows: [],
263
- images: [],
264
- spans: [],
265
- merges: [],
266
- width: 0,
267
- widths: []
268
- };
269
- this.tableColumns = [];
270
- this.tableFirstRow = 0;
271
- this.tableLastRow = 0;
272
- }
273
- /** Resolve a named style, or an empty style when the name is unknown. */
274
- named(name) {
275
- return name && this.ctx.styles[name] || {};
276
- }
277
- write(root) {
278
- this.writeBlocks(root.children);
279
- this.plan.width = this.plan.rows.reduce((max, row) => Math.max(max, row.cells.length), 0);
280
- return this.plan;
281
- }
282
- writeBlocks(nodes, indent = 0) {
283
- for (const node of nodes) this.writeBlock(node, indent);
284
- }
285
- writeBlock(node, indent) {
286
- switch (node.type) {
287
- case "heading": {
288
- const inline = flattenInline(node.children);
289
- const style = combineStyles(this.named(`h${node.depth}`), this.styleFrom(inline.directives));
290
- this.appendSpanningRow(inline, style);
291
- break;
292
- }
293
- case "paragraph": {
294
- const inline = flattenInline(node.children);
295
- const style = combineStyles(this.styleFrom(inline.directives), { indent });
296
- this.appendSpanningRow(inline, style);
297
- break;
298
- }
299
- case "table":
300
- this.writeTable(node.children);
301
- break;
302
- case "list":
303
- for (const item of node.children) this.writeBlocks(item.children, indent + 1);
304
- break;
305
- case "blockquote":
306
- this.writeBlockquote(node.children, indent);
307
- break;
308
- case "code":
309
- for (const line of node.value.split("\n")) this.pushRow([{
310
- value: line,
311
- style: {},
312
- width: line.length
313
- }], void 0, true);
314
- break;
315
- case "thematicBreak":
316
- this.pushRow([{
317
- value: null,
318
- style: {},
319
- width: 0
320
- }], void 0, true);
321
- break;
322
- default: if ("children" in node) this.writeBlocks(node.children, indent);
323
- }
324
- }
325
- writeBlockquote(children, indent) {
326
- const before = this.plan.rows.length;
327
- this.writeBlocks(children, indent);
328
- const note = toExcelStyle(this.named("note"));
329
- for (let i = before; i < this.plan.rows.length; i += 1) for (const cell of this.plan.rows[i].cells) cell.style = mergeExcelStyle(note, cell.style);
330
- }
331
- /** A one-cell row that will later be merged across the sheet's full width. */
332
- appendSpanningRow(inline, style) {
333
- const cell = this.toCell(inline, style);
334
- if (cell.value === null && !cell.formula && !inline.directives.length && !inline.images.length) return;
335
- const rowIndex = this.pushRow([cell], style.height, true);
336
- this.plan.spans.push({
337
- row: rowIndex,
338
- from: 1
339
- });
340
- this.placeImages(inline, rowIndex, 1);
341
- this.applyMerge(inline.directives, rowIndex, 1);
342
- }
343
- writeTable(rows) {
344
- if (!rows.length) return;
345
- const [headerRow, ...bodyRows] = rows;
346
- const headerInlines = headerRow.children.map((cell) => flattenInline(cell.children));
347
- this.tableColumns = headerInlines.map((inline) => inline.text.trim());
348
- const headerStyle = this.named("header");
349
- const headerRowIndex = this.pushRow(headerInlines.map((inline) => this.toCell(inline, combineStyles(headerStyle, this.styleFrom(inline.directives)))), headerStyle.height);
350
- headerInlines.forEach((inline, index) => {
351
- this.placeImages(inline, headerRowIndex, index + 1);
352
- this.applyMerge(inline.directives, headerRowIndex, index + 1);
353
- });
354
- this.tableFirstRow = this.plan.rows.length + 1;
355
- this.tableLastRow = this.tableFirstRow + bodyRows.length - 1;
356
- for (const bodyRow of bodyRows) {
357
- const inlines = bodyRow.children.map((cell) => flattenInline(cell.children));
358
- const rowDirectives = inlines.flatMap((inline) => inline.directives);
359
- const rowStyle = this.rowStyleFrom(rowDirectives);
360
- const cells = inlines.map((inline, index) => this.toCell(inline, combineStyles(rowStyle, this.columnStyle(index), this.styleFrom(inline.directives))));
361
- const rowIndex = this.pushRow(cells, rowStyle.height);
362
- inlines.forEach((inline, index) => {
363
- this.placeImages(inline, rowIndex, index + 1);
364
- this.applyMerge(inline.directives, rowIndex, index + 1);
365
- });
366
- }
367
- this.tableColumns = [];
368
- }
369
- /** Per-column `numFmt` override from `options.columns`. */
370
- columnStyle(index) {
371
- const numFmt = this.ctx.columns?.[index]?.numFmt;
372
- return numFmt ? { numFmt } : {};
373
- }
374
- /** Collect `{{style}}` directives into one style object. */
375
- styleFrom(directives) {
376
- return combineStyles(...directives.filter((d) => d.k === "style").map((d) => this.named(d.v)));
377
- }
378
- /** Collect `{{rowStyle}}` directives into one style object. */
379
- rowStyleFrom(directives) {
380
- return combineStyles(...directives.filter((d) => d.k === "rowStyle").map((d) => this.named(d.v)));
381
- }
382
- /** Build one planned cell from flattened inline content plus its style. */
383
- toCell(inline, style) {
384
- const value = inline.directives.find((d) => d.k === "val");
385
- const formula = inline.directives.find((d) => d.k === "formula");
386
- const inlineStyle = {
387
- ...inline.bold ? { bold: true } : {},
388
- ...inline.italic ? { italic: true } : {}
389
- };
390
- const excelStyle = toExcelStyle(combineStyles(inlineStyle, style));
391
- const width = inline.text.trim().length;
392
- const link = inline.link ? { hyperlink: inline.link } : void 0;
393
- if (formula) return {
394
- value: null,
395
- formula: this.resolveFormula(formula.v),
396
- style: excelStyle,
397
- numFmt: value?.f ?? style.numFmt,
398
- width: Math.max(width, 10),
399
- ...link
400
- };
401
- const isLoneValue = !!value && (value.t == null || value.t.trim() === inline.text.trim());
402
- if (value && isLoneValue) {
403
- if (typeof value.n === "number") return {
404
- value: value.n,
405
- style: excelStyle,
406
- numFmt: value.f,
407
- width,
408
- ...link
409
- };
410
- if (value.d) {
411
- const date = new Date(value.d);
412
- if (!Number.isNaN(date.getTime())) return {
413
- value: date,
414
- style: excelStyle,
415
- numFmt: value.f,
416
- width,
417
- ...link
418
- };
419
- }
420
- }
421
- return {
422
- value: coerceValue(inline.text),
423
- style: excelStyle,
424
- width,
425
- ...link
426
- };
427
- }
428
- /**
429
- * Substitute the placeholders a template couldn't resolve on its own:
430
- * `{row}` (the row about to be written), `{prevRow}` (the one above it),
431
- * `{firstRow}` / `{lastRow}` (the current table's body range) and
432
- * `{col:Caption}` (that header's column letter).
433
- *
434
- * `{lastRow}` includes *every* body row — so a total row that lives inside
435
- * the same Markdown table must sum `{firstRow}:{prevRow}` to avoid a circular
436
- * reference to itself.
437
- */
438
- resolveFormula(expr) {
439
- const row = this.plan.rows.length + 1;
440
- return expr.replace(/\{row\}/g, String(row)).replace(/\{prevRow\}/g, String(Math.max(1, row - 1))).replace(/\{firstRow\}/g, String(this.tableFirstRow || row)).replace(/\{lastRow\}/g, String(this.tableLastRow || row)).replace(/\{col:([^}]+)\}/g, (_match, caption) => {
441
- const index = this.tableColumns.findIndex((c) => c.toLowerCase() === caption.trim().toLowerCase());
442
- return index >= 0 ? columnLetter(index + 1) : caption;
443
- });
444
- }
445
- placeImages(inline, row, col) {
446
- const directives = inline.directives.filter((d) => d.k === "image");
447
- for (const d of directives) this.plan.images.push({
448
- src: d.v,
449
- row,
450
- col,
451
- width: d.width ?? 120,
452
- height: d.height ?? 40
453
- });
454
- for (const img of inline.images) this.plan.images.push({
455
- src: img.url,
456
- row,
457
- col,
458
- width: 120,
459
- height: 40
460
- });
461
- }
462
- applyMerge(directives, row, col) {
463
- const merge = directives.find((d) => d.k === "merge");
464
- if (!merge) return;
465
- const colEnd = col + Math.max(1, merge.cols ?? 1) - 1;
466
- const rowEnd = row + Math.max(1, merge.rows ?? 1) - 1;
467
- if (colEnd === col && rowEnd === row) return;
468
- this.plan.merges.push([
469
- row,
470
- col,
471
- rowEnd,
472
- colEnd
473
- ]);
474
- }
475
- /**
476
- * Append a row and return its 1-based index. `spanning` rows (headings,
477
- * paragraphs) are excluded from auto-fit: they get merged across the whole
478
- * sheet, so letting a long title set column A's width would leave a 60-wide
479
- * first column next to four narrow ones.
480
- */
481
- pushRow(cells, height, spanning = false) {
482
- this.plan.rows.push({
483
- cells,
484
- ...height ? { height } : {}
485
- });
486
- if (!spanning) cells.forEach((cell, index) => {
487
- this.plan.widths[index] = Math.max(this.plan.widths[index] ?? 0, cell.width);
488
- });
489
- return this.plan.rows.length;
490
- }
491
- };
492
- /** Later style wins, but per sub-object so a font isn't wiped by a fill. */
493
- function mergeExcelStyle(base, over) {
494
- return {
495
- ...base,
496
- ...over,
497
- font: {
498
- ...base.font,
499
- ...over.font
500
- },
501
- alignment: {
502
- ...base.alignment,
503
- ...over.alignment
504
- },
505
- border: {
506
- ...base.border,
507
- ...over.border
508
- }
509
- };
510
- }
511
- /**
512
- * Give a plain cell string its most useful type. Text that merely *looks*
513
- * numeric (`"00123"`, `"1,234"`) is left alone — coercing it would silently
514
- * mangle invoice numbers and phone numbers.
515
- */
516
- function coerceValue(text) {
517
- const trimmed = text.trim();
518
- if (!trimmed) return null;
519
- if (NUMERIC_RE.test(trimmed) && !/^0\d/.test(trimmed)) {
520
- const n = Number(trimmed);
521
- if (Number.isFinite(n)) return n;
522
- }
523
- if (ISO_DATE_RE.test(trimmed)) {
524
- const date = new Date(trimmed);
525
- if (!Number.isNaN(date.getTime())) return date;
526
- }
527
- return text;
528
- }
529
- /** 1-based column index → spreadsheet letter (1 → A, 27 → AA). */
530
- function columnLetter(index) {
531
- let n = index;
532
- let out = "";
533
- while (n > 0) {
534
- const rem = (n - 1) % 26;
535
- out = String.fromCharCode(65 + rem) + out;
536
- n = Math.floor((n - 1) / 26);
537
- }
538
- return out;
539
- }
540
-
541
- //#endregion
542
- //#region src/export/excel/index.ts
543
- /** What a template writes to link at a sheet it cannot yet name. */
544
- const SHEET_LINK_PREFIX = "sheet:";
545
- /** Auto-fit bounds, in characters — narrow enough to read, wide enough to fit. */
546
- const MIN_WIDTH = 8;
547
- const MAX_WIDTH = 60;
548
- /** Extra characters of padding added to the widest cell in a column. */
549
- const WIDTH_PADDING = 2;
550
- /** Load the optional `exceljs` peer, with an actionable error when it's absent. */
551
- async function loadExcelJs() {
552
- try {
553
- const mod = await import("exceljs");
554
- return mod?.default ?? mod;
555
- } catch (err) {
556
- throw new Error("[mono-export] xlsx output needs the optional peer dependency \"exceljs\". Install it in your app: pnpm add exceljs" + (err instanceof Error ? `\n (resolution failed: ${err.message})` : ""));
557
- }
558
- }
559
- /**
560
- * `{{image}}` accepts a data URL, a bare base64 string, or an `http(s)` URL.
561
- * ExcelJS wants raw base64 plus an extension, so normalise here — and fetch a
562
- * remote URL, which is the only genuinely async part of image handling.
563
- */
564
- async function toImagePayload(src) {
565
- const dataUrl = /^data:image\/(png|jpe?g|gif);base64,(.+)$/i.exec(src);
566
- if (dataUrl) {
567
- const ext = dataUrl[1].toLowerCase();
568
- return {
569
- base64: dataUrl[2],
570
- extension: ext === "gif" ? "gif" : ext === "png" ? "png" : "jpeg"
571
- };
572
- }
573
- if (/^https?:\/\//i.test(src)) try {
574
- const res = await fetch(src);
575
- if (!res.ok) return null;
576
- const buffer = await res.arrayBuffer();
577
- const type = res.headers.get("content-type") ?? "";
578
- const extension = type.includes("gif") ? "gif" : type.includes("png") ? "png" : "jpeg";
579
- return {
580
- base64: arrayBufferToBase64(buffer),
581
- extension
582
- };
583
- } catch {
584
- return null;
585
- }
586
- return /^[A-Za-z0-9+/=\s]+$/.test(src) ? {
587
- base64: src.trim(),
588
- extension: "png"
589
- } : null;
590
- }
591
- function arrayBufferToBase64(buffer) {
592
- const bytes = new Uint8Array(buffer);
593
- let binary = "";
594
- for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
595
- return typeof btoa === "function" ? btoa(binary) : globalThis.Buffer.from(bytes).toString("base64");
596
- }
597
- /** Build the ExcelJS workbook for a parsed report. */
598
- async function renderExcel(ast, options = {}) {
599
- const styles = mergeStyles(options.styles);
600
- const plan = new WorksheetWriter({
601
- styles,
602
- columns: options.columns
603
- }).write(ast);
604
- const workbook = new (await (loadExcelJs())).Workbook();
605
- workbook.created = /* @__PURE__ */ new Date();
606
- const names = new SheetNames();
607
- const masterName = names.take(options.sheetName ?? "Sheet1", "Sheet1");
608
- const sheet = workbook.addWorksheet(masterName);
609
- const pending = [];
610
- writeRows(sheet, plan, pending);
611
- applyMerges(sheet, plan);
612
- applyWidths(sheet, plan, options.columns);
613
- if (options.freezeRows) sheet.views = [{
614
- state: "frozen",
615
- ySplit: options.freezeRows
616
- }];
617
- await placeImages(workbook, sheet, plan);
618
- const targets = /* @__PURE__ */ new Map();
619
- for (const spec of options.sheets ?? []) {
620
- const name = names.take(spec.name || spec.key, "Detail");
621
- targets.set(spec.key, name);
622
- const detailPlan = new WorksheetWriter({
623
- styles,
624
- columns: options.columns
625
- }).write(spec.ast);
626
- const detailSheet = workbook.addWorksheet(name);
627
- if (options.back !== false) {
628
- const back = detailSheet.addRow([]).getCell(1);
629
- back.value = {
630
- text: "← Back",
631
- hyperlink: sheetRef(masterName)
632
- };
633
- back.font = {
634
- ...toExcelStyle(styles.link ?? {}).font ?? {},
635
- bold: true
636
- };
637
- }
638
- writeRows(detailSheet, detailPlan, pending);
639
- applyMerges(detailSheet, detailPlan);
640
- applyWidths(detailSheet, detailPlan, options.columns);
641
- await placeImages(workbook, detailSheet, detailPlan);
642
- }
643
- applySheetLinks(pending, targets, styles);
644
- return workbook;
645
- }
646
- /**
647
- * Rewrite every `sheet:<key>` target into a real in-workbook reference.
648
- *
649
- * Done last, and only here, because a template cannot know the name it is
650
- * linking to: names are sanitised for Excel and de-duplicated against the
651
- * workbook, both of which need every sheet to exist first.
652
- *
653
- * A key with no sheet — a document whose details failed to load, or had none —
654
- * has its link REMOVED rather than left dangling. The prior art leaves those
655
- * cells underlined and unclickable, which reads as a broken link.
656
- */
657
- function applySheetLinks(pending, targets, styles) {
658
- const linkFont = styles.link ? toExcelStyle(styles.link).font : void 0;
659
- for (const link of pending) {
660
- const name = targets.get(link.key);
661
- if (!name) continue;
662
- applyHyperlink(link.cell, sheetRef(name), link.value);
663
- if (linkFont) link.cell.font = {
664
- ...link.cell.font ?? {},
665
- ...linkFont
666
- };
667
- }
668
- }
669
- /**
670
- * Attach a link to a cell without wrecking what is already in it.
671
- *
672
- * `cell.hyperlink` looks assignable — ExcelJS' own typings declare it on
673
- * `Cell` — but at runtime it is a GETTER ONLY (`lib/doc/cell.js:201`), so
674
- * assigning it throws. The only public route is the value form, and that form
675
- * replaces the cell outright: a number linked this way stops being a number and
676
- * no longer sums in Excel. `numFmt` does survive, since it lives on the style
677
- * rather than the value, but the type does not.
678
- *
679
- * So each kind of cell takes the route that costs it nothing:
680
- *
681
- * · number — Excel's own HYPERLINK(), with the number as the displayed
682
- * argument and cached as `result`. Stays numeric, still sums,
683
- * still formats.
684
- * · text — the plain value form, which for a string loses nothing at all.
685
- * A Date takes this route too and becomes text; dates as link
686
- * anchors are not a case this feature has, and the alternative
687
- * is emitting a serial number nobody can read.
688
- *
689
- * Formula cells never reach here: a formula owns `cell.value`, so there is no
690
- * way to add a link without destroying it, and silently swapping one for the
691
- * other would be worse than not linking. Callers skip them.
692
- */
693
- function applyHyperlink(cell, target, value) {
694
- if (typeof value === "number") {
695
- cell.value = {
696
- formula: `HYPERLINK(${quoteFormulaArg(target)},${value})`,
697
- result: value
698
- };
699
- return;
700
- }
701
- cell.value = {
702
- text: String(value ?? ""),
703
- hyperlink: target
704
- };
705
- }
706
- /** A string literal inside an Excel formula: wrapped, with `"` doubled. */
707
- function quoteFormulaArg(value) {
708
- return `"${value.replace(/"/g, "\"\"")}"`;
709
- }
710
- function writeRows(sheet, plan, pending) {
711
- for (const plannedRow of plan.rows) {
712
- const row = sheet.addRow([]);
713
- plannedRow.cells.forEach((planned, index) => {
714
- const cell = row.getCell(index + 1);
715
- if (planned.formula) cell.value = { formula: planned.formula };
716
- else if (planned.value !== null) cell.value = planned.value;
717
- if (planned.style.font) cell.font = planned.style.font;
718
- if (planned.style.fill) cell.fill = planned.style.fill;
719
- if (planned.style.alignment) cell.alignment = planned.style.alignment;
720
- if (planned.style.border) cell.border = planned.style.border;
721
- const numFmt = planned.numFmt ?? planned.style.numFmt;
722
- if (numFmt) cell.numFmt = numFmt;
723
- if (planned.hyperlink && !planned.formula) {
724
- if (pending && planned.hyperlink.startsWith("sheet:")) pending.push({
725
- cell,
726
- key: planned.hyperlink.slice(6),
727
- value: planned.value
728
- });
729
- else applyHyperlink(cell, planned.hyperlink, planned.value);
730
- }
731
- });
732
- if (plannedRow.height) row.height = plannedRow.height;
733
- row.commit?.();
734
- }
735
- }
736
- function applyMerges(sheet, plan) {
737
- for (const span of plan.spans) if (plan.width > span.from) safeMerge(sheet, span.row, span.from, span.row, plan.width);
738
- for (const [row, col, rowEnd, colEnd] of plan.merges) safeMerge(sheet, row, col, rowEnd, colEnd);
739
- }
740
- /**
741
- * ExcelJS throws when two merges overlap. A template that merges a cell inside
742
- * a heading row is a mistake, not a reason to lose the whole report — so log
743
- * and keep going.
744
- */
745
- function safeMerge(sheet, top, left, bottom, right) {
746
- try {
747
- sheet.mergeCells(top, left, bottom, right);
748
- } catch {
749
- console.warn(`[mono-export] skipped overlapping merge ${columnLetter(left)}${top}:${columnLetter(right)}${bottom}`);
750
- }
751
- }
752
- function applyWidths(sheet, plan, columns) {
753
- for (let i = 0; i < plan.width; i += 1) {
754
- const explicit = columns?.[i]?.width;
755
- const measured = (plan.widths[i] ?? 0) + WIDTH_PADDING;
756
- sheet.getColumn(i + 1).width = explicit ?? Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, measured));
757
- }
758
- }
759
- async function placeImages(workbook, sheet, plan) {
760
- for (const pending of plan.images) {
761
- const payload = await toImagePayload(pending.src);
762
- if (!payload) {
763
- console.warn(`[mono-export] could not load image: ${pending.src.slice(0, 60)}`);
764
- continue;
765
- }
766
- const id = workbook.addImage({
767
- base64: payload.base64,
768
- extension: payload.extension
769
- });
770
- sheet.addImage(id, {
771
- tl: {
772
- col: pending.col - 1,
773
- row: pending.row - 1
774
- },
775
- ext: {
776
- width: pending.width,
777
- height: pending.height
778
- }
779
- });
780
- }
781
- }
782
-
783
- //#endregion
784
- export { renderExcel, DEFAULT_STYLES as t };