@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,612 +0,0 @@
1
- //#region src/import/coerce.ts
2
- /**
3
- * Normalise a header for lookup: NBSP → space, drop dots/underscores (`No. Dok`
4
- * and `No_Dok` should both find `No Dok`), collapse whitespace, lowercase.
5
- */
6
- function normalizeHeader(value) {
7
- return String(value ?? "").replace(/\u00A0/g, " ").replace(/[._]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
8
- }
9
- /** Default normalisation for key comparison — forgiving about spacing and case. */
10
- function normalizeKeyValue(value) {
11
- return String(value ?? "").replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
12
- }
13
- /** Read a display string out of whatever ExcelJS put in a cell. */
14
- function cellText(value) {
15
- if (value == null) return "";
16
- if (typeof value === "string") return value;
17
- if (typeof value === "number" || typeof value === "boolean") return String(value);
18
- if (value instanceof Date) return value.toISOString();
19
- const v = value;
20
- if (Array.isArray(v.richText)) return v.richText.map((r) => r.text).join("");
21
- if (v.text != null) return String(v.text);
22
- if (v.result != null) return String(v.result);
23
- if (v.hyperlink != null && v.text == null) return String(v.hyperlink);
24
- return String(value);
25
- }
26
- /**
27
- * Parse a number written in an unknown locale.
28
- *
29
- * `'auto'` infers the separators: with both `.` and `,` present the **rightmost**
30
- * is the decimal point; with only one, its position decides — exactly three
31
- * trailing digits (or several occurrences) means thousands grouping, otherwise
32
- * it's a decimal point. So `1.234` is one thousand two hundred, while `1.23` is
33
- * one and a bit. Returns `null` when the text isn't a number at all.
34
- */
35
- function parseNumber(raw, mode = "auto", allowNegative = true) {
36
- if (raw == null) return null;
37
- if (typeof raw === "number") return Number.isFinite(raw) ? raw : null;
38
- const original = String(raw).replace(/\u00A0/g, " ").trim();
39
- if (!original || !/[0-9]/.test(original)) return null;
40
- const parenthesised = /^\(.*\)$/.test(original);
41
- let s = original.replace(/[()]/g, "");
42
- const negative = parenthesised || /^-/.test(s);
43
- s = s.replace(/[^\d.,]/g, "");
44
- if (!s) return null;
45
- let groupSep = null;
46
- let decimalSep = null;
47
- if (mode === "us") {
48
- groupSep = ",";
49
- decimalSep = ".";
50
- } else if (mode === "eu") {
51
- groupSep = ".";
52
- decimalSep = ",";
53
- } else {
54
- const lastDot = s.lastIndexOf(".");
55
- const lastComma = s.lastIndexOf(",");
56
- if (lastDot === -1 && lastComma === -1) {} else if (lastDot !== -1 && lastComma !== -1) {
57
- if (lastDot > lastComma) {
58
- decimalSep = ".";
59
- groupSep = ",";
60
- } else {
61
- decimalSep = ",";
62
- groupSep = ".";
63
- }
64
- } else {
65
- const only = lastDot !== -1 ? "." : ",";
66
- const pos = only === "." ? lastDot : lastComma;
67
- const trailing = s.length - pos - 1;
68
- if (s.split(only).length - 1 > 1) groupSep = only;
69
- else if (trailing === 3) groupSep = only;
70
- else decimalSep = only;
71
- }
72
- }
73
- if (groupSep) s = s.split(groupSep).join("");
74
- if (decimalSep) s = s.replace(decimalSep, ".");
75
- if (!/^\d+(\.\d+)?$/.test(s)) return null;
76
- const n = Number(s);
77
- if (!Number.isFinite(n)) return null;
78
- if (negative) {
79
- if (!allowNegative) return null;
80
- return -n;
81
- }
82
- return n;
83
- }
84
- /** Parse a date from a Date, an ISO-ish string, or a spreadsheet serial number. */
85
- function parseDate(raw) {
86
- if (raw == null || raw === "") return null;
87
- if (raw instanceof Date) return Number.isNaN(raw.getTime()) ? null : raw;
88
- if (typeof raw === "number" && Number.isFinite(raw)) {
89
- const ms = Math.round((raw - 25569) * 86400 * 1e3);
90
- const d = new Date(ms);
91
- return Number.isNaN(d.getTime()) ? null : d;
92
- }
93
- const text = String(raw).trim();
94
- if (!text) return null;
95
- const dmy = /^(\d{1,2})[/\-.](\d{1,2})[/\-.](\d{4})$/.exec(text);
96
- if (dmy) {
97
- const [, d, m, y] = dmy;
98
- const date = new Date(Number(y), Number(m) - 1, Number(d));
99
- return Number.isNaN(date.getTime()) ? null : date;
100
- }
101
- const parsed = new Date(text);
102
- return Number.isNaN(parsed.getTime()) ? null : parsed;
103
- }
104
- const TRUE_WORDS = /* @__PURE__ */ new Set([
105
- "true",
106
- "yes",
107
- "y",
108
- "1",
109
- "ya",
110
- "benar",
111
- "x",
112
- "✓"
113
- ]);
114
- const FALSE_WORDS = /* @__PURE__ */ new Set([
115
- "false",
116
- "no",
117
- "n",
118
- "0",
119
- "tidak",
120
- "salah",
121
- ""
122
- ]);
123
- /** Parse a boolean from the many things a human types into a yes/no column. */
124
- function parseBoolean(raw) {
125
- if (typeof raw === "boolean") return raw;
126
- if (raw == null) return null;
127
- const text = String(raw).trim().toLowerCase();
128
- if (TRUE_WORDS.has(text)) return true;
129
- if (FALSE_WORDS.has(text)) return false;
130
- return null;
131
- }
132
- /**
133
- * Coerce one cell for a declared column type. `undefined` means "unparseable —
134
- * skip this cell" so a stray `-` in a number column can't stage `NaN`.
135
- */
136
- function coerceValue(raw, type, opts = {}) {
137
- const text = cellText(raw);
138
- switch (type) {
139
- case "number": {
140
- const n = parseNumber(raw, opts.numberFormat ?? "auto", opts.allowNegative ?? true);
141
- return n === null ? void 0 : n;
142
- }
143
- case "date": {
144
- const d = parseDate(raw instanceof Date || typeof raw === "number" ? raw : text);
145
- return d === null ? void 0 : d;
146
- }
147
- case "boolean": {
148
- const b = parseBoolean(text);
149
- return b === null ? void 0 : b;
150
- }
151
- default: return text.trim();
152
- }
153
- }
154
- /**
155
- * Whether a staged value would actually change the row. Numbers are compared
156
- * numerically so a stored `5000` isn't "changed" by a sheet's `"5.000"`, and
157
- * dates by timestamp so two equal instants don't churn.
158
- */
159
- function isSameValue(current, next, type) {
160
- if (type === "number") {
161
- const a = parseNumber(current, "auto");
162
- const b = typeof next === "number" ? next : parseNumber(next, "auto");
163
- return a !== null && b !== null && a === b;
164
- }
165
- if (type === "date") {
166
- const a = parseDate(current);
167
- const b = parseDate(next);
168
- return !!a && !!b && a.getTime() === b.getTime();
169
- }
170
- if (type === "boolean") return Boolean(current) === Boolean(next);
171
- if (current == null && (next === "" || next == null)) return true;
172
- return String(current ?? "") === String(next ?? "");
173
- }
174
-
175
- //#endregion
176
- //#region src/import/match.ts
177
- /**
178
- * Pairing a sheet row with a table row.
179
- *
180
- * Real spreadsheets are lossy: a column gets deleted, a code is blank on some
181
- * rows, a name is spelled differently. So instead of demanding one exact
182
- * composite key, every non-empty **subset** of the declared key fields is
183
- * indexed, and a row is matched on the strongest subset that resolves to
184
- * exactly one candidate. A weaker subset is only consulted when the stronger
185
- * ones can't be built (a blank cell) or find nothing.
186
- *
187
- * When a subset resolves to *several* rows the match is reported **ambiguous**
188
- * rather than guessed at — quietly picking the first would corrupt data in a
189
- * way nobody notices until much later.
190
- *
191
- * Ported from `use-import-excel.ts` (`buildAllSubsets` / `buildIndexForScheme`).
192
- */
193
- /**
194
- * Every non-empty subset of `match` with at least `minFields` legs, ordered
195
- * strongest (most legs) first.
196
- *
197
- * Bit-counting rather than recursion: with `n` legs there are `2^n - 1`
198
- * subsets, and `n` is a handful of key columns in practice.
199
- */
200
- function buildAllSubsets(match, minFields = 2) {
201
- const out = [];
202
- const n = match.length;
203
- const min = Math.max(1, Math.min(minFields, n));
204
- for (let mask = (1 << n) - 1; mask >= 1; mask -= 1) {
205
- const subset = [];
206
- for (let i = 0; i < n; i += 1) if (mask & 1 << i) subset.push(match[i]);
207
- if (subset.length >= min) out.push(subset);
208
- }
209
- out.sort((a, b) => b.length - a.length);
210
- return out;
211
- }
212
- /** Build the composite key for one side, or `null` when a leg is missing. */
213
- function keyOf(scheme, read, side, normalize) {
214
- const parts = [];
215
- for (const leg of scheme) {
216
- let raw = read(leg);
217
- if (raw === void 0 || raw === null || String(raw).trim() === "") return null;
218
- if (leg.transform) raw = leg.transform(raw, side);
219
- parts.push(`${leg.field}=${normalize(raw)}`);
220
- }
221
- return parts.join("|");
222
- }
223
- /** Index the target rows by one scheme's key. Collisions are kept, not dropped. */
224
- function buildSchemeIndex(scheme, target, opts = {}) {
225
- const normalize = opts.normalize ?? normalizeKeyValue;
226
- const index = /* @__PURE__ */ new Map();
227
- for (let i = 0; i < target.length; i += 1) {
228
- const row = target[i];
229
- if (opts.targetFilter && !opts.targetFilter(row)) continue;
230
- const key = keyOf(scheme, (leg) => row?.[leg.field], "table", normalize);
231
- if (key === null) continue;
232
- const bucket = index.get(key);
233
- if (bucket) bucket.push({
234
- row,
235
- index: i
236
- });
237
- else index.set(key, [{
238
- row,
239
- index: i
240
- }]);
241
- }
242
- return index;
243
- }
244
- function createMatcher(match, target, opts = {}) {
245
- const normalize = opts.normalize ?? normalizeKeyValue;
246
- const schemes = buildAllSubsets(match, opts.minKeyFields ?? 2);
247
- return {
248
- schemes,
249
- indexes: schemes.map((s) => buildSchemeIndex(s, target, {
250
- targetFilter: opts.targetFilter,
251
- normalize
252
- })),
253
- normalize
254
- };
255
- }
256
- /**
257
- * Find the table row for one sheet row, strongest scheme first.
258
- *
259
- * An ambiguous hit is remembered but doesn't stop the search — a weaker scheme
260
- * can't disambiguate, but a *different* subset of the same size might, and only
261
- * if nothing unique is ever found is the ambiguity reported.
262
- */
263
- function findMatch(matcher, readExcel) {
264
- let ambiguous = null;
265
- for (let i = 0; i < matcher.schemes.length; i += 1) {
266
- const scheme = matcher.schemes[i];
267
- const key = keyOf(scheme, readExcel, "excel", matcher.normalize);
268
- if (key === null) continue;
269
- const bucket = matcher.indexes[i].get(key);
270
- if (!bucket || !bucket.length) continue;
271
- if (bucket.length === 1) return {
272
- kind: "hit",
273
- entry: bucket[0],
274
- scheme
275
- };
276
- if (!ambiguous) ambiguous = {
277
- candidates: bucket.length,
278
- scheme
279
- };
280
- }
281
- if (ambiguous) return {
282
- kind: "ambiguous",
283
- ...ambiguous
284
- };
285
- return { kind: "miss" };
286
- }
287
-
288
- //#endregion
289
- //#region src/import/parse.ts
290
- /**
291
- * Getting a `string[][]` grid out of whatever the user handed over — a real
292
- * `.xlsx` file or the text they copied out of one.
293
- */
294
- /** Decide how to read `data` when the caller didn't say. */
295
- function inferType(data) {
296
- return typeof data === "string" ? "copy-paste" : "excel";
297
- }
298
- /**
299
- * Pick the delimiter from the text itself. Tab first: that's what a real
300
- * copy out of Excel produces, and a pasted cell may legitimately contain
301
- * commas or semicolons.
302
- */
303
- function guessDelimiter(raw) {
304
- if (raw.includes(" ")) return " ";
305
- const firstLine = raw.split(/\r?\n/, 1)[0] ?? "";
306
- if (firstLine.split(";").length > firstLine.split(",").length) return ";";
307
- return ",";
308
- }
309
- /**
310
- * Split delimited text, honouring `"` quoting so a quoted cell can contain the
311
- * delimiter or a newline — which CSV exports out of Excel routinely produce.
312
- */
313
- function splitDelimited(raw, delimiter) {
314
- const out = [];
315
- let row = [];
316
- let cell = "";
317
- let quoted = false;
318
- for (let i = 0; i < raw.length; i += 1) {
319
- const ch = raw[i];
320
- if (quoted) {
321
- if (ch === "\"") {
322
- if (raw[i + 1] === "\"") {
323
- cell += "\"";
324
- i += 1;
325
- } else quoted = false;
326
- } else cell += ch;
327
- continue;
328
- }
329
- if (ch === "\"") {
330
- quoted = true;
331
- continue;
332
- }
333
- if (ch === delimiter) {
334
- row.push(cell);
335
- cell = "";
336
- continue;
337
- }
338
- if (ch === "\r") continue;
339
- if (ch === "\n") {
340
- row.push(cell);
341
- out.push(row);
342
- row = [];
343
- cell = "";
344
- continue;
345
- }
346
- cell += ch;
347
- }
348
- if (cell !== "" || row.length) {
349
- row.push(cell);
350
- out.push(row);
351
- }
352
- while (out.length && out[out.length - 1].every((c) => String(c).trim() === "")) out.pop();
353
- return out.map((r) => r.map((c) => c.trim()));
354
- }
355
- /** Load the optional `exceljs` peer, with an actionable error when it's absent. */
356
- async function loadExcelJs() {
357
- try {
358
- const mod = await import("exceljs");
359
- return mod?.default ?? mod;
360
- } catch (err) {
361
- throw new Error("[mono-import] reading an .xlsx needs the optional peer dependency \"exceljs\". Install it in your app: pnpm add exceljs" + (err instanceof Error ? `\n (resolution failed: ${err.message})` : ""));
362
- }
363
- }
364
- /** Normalise every accepted binary shape into an ArrayBuffer. */
365
- async function toArrayBuffer(data) {
366
- if (data instanceof ArrayBuffer) return data;
367
- if (ArrayBuffer.isView(data)) return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
368
- if (typeof data.arrayBuffer === "function") return data.arrayBuffer();
369
- throw new Error("[mono-import] `data` must be a File, Blob, ArrayBuffer, Uint8Array or string.");
370
- }
371
- /** Read a worksheet into a dense matrix of raw cell values. */
372
- async function readWorkbook(data, sheet) {
373
- const workbook = new (await (loadExcelJs())).Workbook();
374
- await workbook.xlsx.load(await toArrayBuffer(data));
375
- const worksheet = typeof sheet === "string" ? workbook.getWorksheet(sheet) : workbook.worksheets[typeof sheet === "number" ? sheet : 0];
376
- if (!worksheet) {
377
- const names = workbook.worksheets.map((w) => w.name).join(", ");
378
- throw new Error(`[mono-import] worksheet ${JSON.stringify(sheet)} not found. Available: ${names}`);
379
- }
380
- const matrix = [];
381
- worksheet.eachRow({ includeEmpty: true }, (row) => {
382
- const cells = [];
383
- const values = row.values;
384
- for (let c = 1; c < values.length; c += 1) cells.push(values[c]);
385
- matrix.push(cells);
386
- });
387
- return matrix;
388
- }
389
- /**
390
- * Parse the caller's `data` into headers + rows.
391
- *
392
- * Rows above `headerRow` are dropped, which is what lets a sheet carry a title
393
- * block above its table.
394
- */
395
- async function parseSheet(options) {
396
- const type = options.type ?? inferType(options.data);
397
- let matrix;
398
- if (type === "copy-paste") {
399
- if (typeof options.data !== "string") throw new Error("[mono-import] type 'copy-paste' needs `data` to be a string.");
400
- const delimiter = options.delimiter ?? guessDelimiter(options.data);
401
- matrix = splitDelimited(options.data, delimiter);
402
- } else {
403
- if (typeof options.data === "string") throw new Error("[mono-import] type 'excel' needs `data` to be a File, Blob or ArrayBuffer.");
404
- matrix = await readWorkbook(options.data, options.sheet);
405
- }
406
- const headerIndex = Math.max(1, Math.floor(options.headerRow ?? 1)) - 1;
407
- return {
408
- headers: (matrix[headerIndex] ?? []).map((h) => cellText(h).trim()),
409
- rows: matrix.slice(headerIndex + 1).filter((r) => r.some((c) => cellText(c).trim() !== ""))
410
- };
411
- }
412
-
413
- //#endregion
414
- //#region src/import/index.ts
415
- /**
416
- * `@mono-lit/helper` table import — read an edited spreadsheet back into the grid.
417
- *
418
- * The pipeline: parse the sheet (or the pasted text) into headers + rows, pair
419
- * each sheet row with a table row by composite key, coerce the declared columns,
420
- * and stage the differences. Nothing touches the underlying data — the result
421
- * lands in the grid's pending-changes buffer, to be reviewed and then saved or
422
- * discarded.
423
- *
424
- * @example
425
- * const result = await table.import({
426
- * type: 'excel',
427
- * data: file,
428
- * match: [{ excel: 'Brand', field: 'BrandNama' }, { excel: 'CH.', field: 'Channel' }],
429
- * columns: [{ excel: 'JAN', field: 'Jan', type: 'number' }],
430
- * })
431
- * if (!result.ok) warn(`${result.unmatched} rows didn't match`)
432
- */
433
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
434
- /** Build header → column-index, including any declared aliases. */
435
- function buildHeaderIndex(headers, aliases) {
436
- const index = /* @__PURE__ */ new Map();
437
- headers.forEach((h, i) => {
438
- const key = normalizeHeader(h);
439
- if (key && !index.has(key)) index.set(key, i);
440
- });
441
- if (aliases) for (const [canonical, alts] of Object.entries(aliases)) {
442
- const target = normalizeHeader(canonical);
443
- if (index.has(target)) continue;
444
- for (const alt of alts) {
445
- const found = index.get(normalizeHeader(alt));
446
- if (found !== void 0) {
447
- index.set(target, found);
448
- break;
449
- }
450
- }
451
- }
452
- return index;
453
- }
454
- /** Read the sheet's raw grid into `{ header: value }` for the user callbacks. */
455
- function toSheetRecord(headers, raw) {
456
- const out = {};
457
- headers.forEach((h, i) => {
458
- if (h) out[h] = raw[i] ?? "";
459
- });
460
- return out;
461
- }
462
- /** Run an import against a grid. The implementation behind `table.import()`. */
463
- async function importTable(options, bridge) {
464
- if (!options?.match?.length) throw new Error("[mono-import] `match` is required — an import needs a key to pair rows on.");
465
- if (!options?.columns?.length) throw new Error("[mono-import] `columns` is required — nothing would be imported otherwise.");
466
- const { headers, rows } = await parseSheet(options);
467
- const headerIndex = buildHeaderIndex(headers, options.headerAliases);
468
- const missingHeaders = [...options.match, ...options.columns].map((m) => m.excel).filter((h) => !headerIndex.has(normalizeHeader(h)));
469
- const activeColumns = options.columns.filter((c) => headerIndex.has(normalizeHeader(c.excel)));
470
- const activeMatch = options.match.filter((m) => headerIndex.has(normalizeHeader(m.excel)));
471
- const target = options.target ?? await bridge.getData();
472
- const result = {
473
- ok: false,
474
- total: 0,
475
- matched: 0,
476
- unmatched: 0,
477
- ambiguous: 0,
478
- changed: 0,
479
- skipped: 0,
480
- headers,
481
- missingHeaders,
482
- changes: [],
483
- unmatchedRows: [],
484
- ambiguousRows: [],
485
- skippedRows: [],
486
- merged: target,
487
- apply: () => {}
488
- };
489
- if (!activeMatch.length || !activeColumns.length) {
490
- if (options.debug) console.warn("[mono-import] nothing to do — missing headers:", missingHeaders);
491
- return result;
492
- }
493
- const matcher = createMatcher(activeMatch, target, {
494
- minKeyFields: options.minKeyFields,
495
- targetFilter: options.targetFilter,
496
- normalize: options.normalizeKey
497
- });
498
- const patchesByIndex = /* @__PURE__ */ new Map();
499
- const numberFormat = options.numberFormat ?? "auto";
500
- const allowNegative = options.allowNegative ?? true;
501
- const chunkSize = Math.max(1, Math.floor(options.chunkSize ?? 300));
502
- const yieldMs = Math.max(0, options.yieldMs ?? 0);
503
- const cellOf = (raw, excel) => {
504
- const i = headerIndex.get(normalizeHeader(excel));
505
- return i === void 0 ? void 0 : raw[i];
506
- };
507
- for (let start = 0; start < rows.length; start += chunkSize) {
508
- const end = Math.min(start + chunkSize, rows.length);
509
- for (let ri = start; ri < end; ri += 1) {
510
- const raw = rows[ri];
511
- const record = toSheetRecord(headers, raw);
512
- if (options.rowFilter && !options.rowFilter(record, ri)) continue;
513
- result.total += 1;
514
- const outcome = findMatch(matcher, (leg) => cellOf(raw, leg.excel));
515
- if (outcome.kind === "miss") {
516
- result.unmatched += 1;
517
- result.unmatchedRows.push({
518
- index: ri,
519
- row: record
520
- });
521
- continue;
522
- }
523
- if (outcome.kind === "ambiguous") {
524
- result.ambiguous += 1;
525
- result.ambiguousRows.push({
526
- index: ri,
527
- row: record,
528
- candidates: outcome.candidates,
529
- reason: `matched ${outcome.candidates} rows on ${outcome.scheme.map((s) => s.field).join(" + ")}`
530
- });
531
- continue;
532
- }
533
- result.matched += 1;
534
- const { row, index } = outcome.entry;
535
- let touched = false;
536
- for (const column of activeColumns) {
537
- const veto = options.readOnly?.(row, column.field);
538
- if (veto) {
539
- result.skipped += 1;
540
- result.skippedRows.push({
541
- index: ri,
542
- row: record,
543
- reason: typeof veto === "string" ? veto : `${column.field} is read-only`
544
- });
545
- continue;
546
- }
547
- const rawCell = cellOf(raw, column.excel);
548
- let value = coerceValue(rawCell, column.type, {
549
- numberFormat,
550
- allowNegative
551
- });
552
- if (value === void 0) continue;
553
- if (column.transform) {
554
- value = column.transform(value, {
555
- row,
556
- field: column.field,
557
- raw: rawCell
558
- });
559
- if (value === void 0) continue;
560
- }
561
- const current = row[column.field];
562
- if (isSameValue(current, value, column.type)) continue;
563
- const entry = patchesByIndex.get(index) ?? {
564
- row,
565
- patch: {}
566
- };
567
- entry.patch[column.field] = value;
568
- patchesByIndex.set(index, entry);
569
- touched = true;
570
- }
571
- if (touched) result.changed += 1;
572
- }
573
- options.onProgress?.(end, rows.length);
574
- if (yieldMs >= 0 && end < rows.length) await sleep(yieldMs);
575
- }
576
- result.changes = [...patchesByIndex.entries()].map(([index, { row, patch }]) => ({
577
- rowKey: bridge.rowKeyOf(row, index),
578
- key: bridge.serverKeyOf(row),
579
- row,
580
- patch
581
- }));
582
- result.merged = target.map((row, i) => {
583
- const entry = patchesByIndex.get(i);
584
- return entry ? {
585
- ...row,
586
- ...entry.patch
587
- } : row;
588
- });
589
- result.ok = result.total > 0 && result.matched === result.total && result.unmatched === 0 && result.ambiguous === 0;
590
- if (options.debug) console.debug("[mono-import]", {
591
- headers,
592
- missingHeaders,
593
- schemes: matcher.schemes.map((s) => s.map((l) => l.field)),
594
- total: result.total,
595
- matched: result.matched,
596
- unmatched: result.unmatched,
597
- ambiguous: result.ambiguous,
598
- changed: result.changed,
599
- skipped: result.skipped
600
- });
601
- let applied = false;
602
- result.apply = () => {
603
- if (applied || !result.changes.length) return;
604
- applied = true;
605
- bridge.stageImported(result.changes);
606
- };
607
- if ((options.apply ?? "stage") === "stage") result.apply();
608
- return result;
609
- }
610
-
611
- //#endregion
612
- export { importTable };