@mono-lit/helper 0.0.9 → 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-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/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 +22 -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 +118 -1
  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-e9ZVBlNN.js +0 -4240
  161. 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 };