@mono-lit/helper 0.0.9 → 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.
Files changed (164) 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-DTqDBca7.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-B2iRpJuI.js → mono-data-dropdown-Bgj2PDfm.js} +1 -1
  38. package/dist/{mono-data-grid-llJCX9MC.js → mono-data-grid-DJkp96N-.js} +144 -46
  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/nuxt.js +1 -1
  45. package/dist/otp-core-DXg3vFLh.js +758 -0
  46. package/dist/radio-utils-Dk9Tl1jz.js +319 -0
  47. package/dist/rich-text-editor-core-Ddd_QaLn.js +1280 -0
  48. package/dist/select-utils-BgArYHk8.js +2140 -0
  49. package/dist/sidebar-core-CD6txDo3.js +583 -0
  50. package/dist/slider-core-vcWr321-.js +1014 -0
  51. package/dist/src/components/dropdown-table/dropdown-table-core.d.ts +2 -2
  52. package/dist/src/components/dropdown-table/dropdown-table-types.d.ts +1 -1
  53. package/dist/src/components/dropdown-table/index.d.ts +1 -1
  54. package/dist/src/components/dropdown-table/index.shadow.d.ts +1 -1
  55. package/dist/src/components/dropdown-table/mono-data-dropdown.d.ts +1 -1
  56. package/dist/src/components/form/form-types.d.ts +9 -3
  57. package/dist/src/components/input/input-core.d.ts +1 -1
  58. package/dist/src/components/input/input-types.d.ts +1 -1
  59. package/dist/src/components/otp/otp-core.d.ts +1 -1
  60. package/dist/src/components/select/select-core.d.ts +1 -1
  61. package/dist/src/components/select/select-types.d.ts +1 -1
  62. package/dist/src/components/slider/slider-core.d.ts +1 -1
  63. package/dist/src/components/slider/slider-types.d.ts +1 -1
  64. package/dist/src/components/table/filter-suggestion.d.ts +171 -0
  65. package/dist/src/components/table/index.d.ts +5 -4
  66. package/dist/src/components/table/index.shadow.d.ts +5 -4
  67. package/dist/src/components/table/mono-data-grid.d.ts +22 -3
  68. package/dist/src/components/table/mono-table-empty-core.d.ts +37 -0
  69. package/dist/src/components/table/mono-table-search-core.d.ts +22 -3
  70. package/dist/src/components/table/table-types.d.ts +74 -4
  71. package/dist/src/components/tag-input/tag-input-core.d.ts +1 -1
  72. package/dist/src/components/tag-input/tag-input-types.d.ts +1 -1
  73. package/dist/src/components/textarea/textarea-core.d.ts +1 -1
  74. package/dist/src/composables/mono-event.d.ts +7 -1
  75. package/dist/src/entries/index.d.ts +8 -6
  76. package/dist/src/entries/index.node.d.ts +8 -6
  77. package/dist/src/search/index.d.ts +1 -1
  78. package/dist/src/utils/group-count.d.ts +36 -0
  79. package/dist/switch-utils-CTZ4id2V.js +338 -0
  80. package/dist/tabs-core-r5-B8k_N.js +317 -0
  81. package/dist/tag-input-utils-D2JA90DQ.js +3060 -0
  82. package/dist/textarea-utils-VU1KeXm8.js +821 -0
  83. package/dist/ui/accordion.js +2 -274
  84. package/dist/ui/alert.js +2 -131
  85. package/dist/ui/breadcrumb.js +4 -745
  86. package/dist/ui/button-dropdown.js +4 -652
  87. package/dist/ui/button.js +2 -63
  88. package/dist/ui/card.js +2 -310
  89. package/dist/ui/chart.js +4 -800
  90. package/dist/ui/checkbox.js +4 -335
  91. package/dist/ui/chip.js +4 -402
  92. package/dist/ui/date.js +3 -732
  93. package/dist/ui/drawer.js +3 -757
  94. package/dist/ui/dropdown-table.js +5 -1341
  95. package/dist/ui/dropdown.js +4 -509
  96. package/dist/ui/file-upload.js +3 -622
  97. package/dist/ui/filter.js +5 -380
  98. package/dist/ui/form.js +1 -1
  99. package/dist/ui/index.css +118 -1
  100. package/dist/ui/input.js +3 -1141
  101. package/dist/ui/menu.js +4 -828
  102. package/dist/ui/modal.js +4 -843
  103. package/dist/ui/nav.js +4 -223
  104. package/dist/ui/otp.js +4 -756
  105. package/dist/ui/radio.js +4 -317
  106. package/dist/ui/rich-text-editor.js +4 -1278
  107. package/dist/ui/select.js +5 -2138
  108. package/dist/ui/shadow/accordion.js +4 -273
  109. package/dist/ui/shadow/alert.js +4 -130
  110. package/dist/ui/shadow/breadcrumb.js +5 -744
  111. package/dist/ui/shadow/button-dropdown.js +6 -652
  112. package/dist/ui/shadow/button.js +3 -63
  113. package/dist/ui/shadow/card.js +4 -309
  114. package/dist/ui/shadow/chart.js +6 -1576
  115. package/dist/ui/shadow/checkbox.js +4 -334
  116. package/dist/ui/shadow/chip.js +4 -396
  117. package/dist/ui/shadow/date.js +7 -731
  118. package/dist/ui/shadow/drawer.js +4 -756
  119. package/dist/ui/shadow/dropdown-table.js +8 -1762
  120. package/dist/ui/shadow/dropdown.js +4 -508
  121. package/dist/ui/shadow/file-upload.js +4 -620
  122. package/dist/ui/shadow/filter.js +7 -379
  123. package/dist/ui/shadow/input.js +6 -1140
  124. package/dist/ui/shadow/menu.js +5 -828
  125. package/dist/ui/shadow/modal.js +6 -1063
  126. package/dist/ui/shadow/nav.js +5 -223
  127. package/dist/ui/shadow/otp.js +6 -754
  128. package/dist/ui/shadow/radio.js +4 -316
  129. package/dist/ui/shadow/rich-text-editor.js +4 -1276
  130. package/dist/ui/shadow/select.js +6 -2136
  131. package/dist/ui/shadow/sidebar.js +5 -581
  132. package/dist/ui/shadow/slider.js +5 -1010
  133. package/dist/ui/shadow/switch.js +4 -335
  134. package/dist/ui/shadow/table.js +10 -4501
  135. package/dist/ui/shadow/tabs.js +6 -315
  136. package/dist/ui/shadow/tag-input.js +7 -3057
  137. package/dist/ui/shadow/textarea.js +6 -817
  138. package/dist/ui/sidebar.js +3 -581
  139. package/dist/ui/slider.js +4 -1012
  140. package/dist/ui/switch.js +4 -336
  141. package/dist/ui/table.js +9 -4502
  142. package/dist/ui/tabs.js +4 -315
  143. package/dist/ui/tag-input.js +5 -3058
  144. package/dist/ui/textarea.js +4 -819
  145. package/dist/{vite-CivRCJaf.js → vite-CfW7z2wn.js} +21 -14
  146. package/dist/vite.js +1 -1
  147. package/dist/vue.d.ts +3 -2
  148. package/package.json +10 -11
  149. package/dist/chip-strip-bNfLn52Z.js +0 -215
  150. package/dist/data-source-controller-Dx6IQ2CN.js +0 -243
  151. package/dist/data-source-options-BM4r4PRw.js +0 -558
  152. package/dist/data-source-read-HyBCRg1t.js +0 -242
  153. package/dist/excel-BXzr3uI1.js +0 -784
  154. package/dist/export-ixRdCS2y.js +0 -2388
  155. package/dist/field-tooltip-addon-CWCp19mJ.js +0 -702
  156. package/dist/field-tooltip.css_raw-U5UeTa2d.js +0 -269
  157. package/dist/filter-builder-C0T3pMx2.js +0 -208
  158. package/dist/form-control-core-BbiACMtL.js +0 -226
  159. package/dist/icon-ClxnYhBG.js +0 -19
  160. package/dist/import-Dc4eXC02.js +0 -612
  161. package/dist/light-slots-_no0r2Z-.js +0 -246
  162. package/dist/markdown-uGlE_OfJ.js +0 -20191
  163. package/dist/mono-data-grid-e9ZVBlNN.js +0 -4240
  164. 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 };