@svgrid/grid 1.2.21 → 1.2.23

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 (190) hide show
  1. package/dist/GridFooter.svelte +34 -20
  2. package/dist/GridFooter.svelte.d.ts +8 -0
  3. package/dist/SvAutoComplete.svelte +100 -0
  4. package/dist/SvAutoComplete.svelte.d.ts +16 -0
  5. package/dist/SvButton.svelte +97 -0
  6. package/dist/SvButton.svelte.d.ts +23 -0
  7. package/dist/SvCalendar.svelte +669 -0
  8. package/dist/SvCalendar.svelte.d.ts +70 -0
  9. package/dist/SvCheckBox.svelte +81 -0
  10. package/dist/SvCheckBox.svelte.d.ts +19 -0
  11. package/dist/SvColorInput.svelte +187 -0
  12. package/dist/SvColorInput.svelte.d.ts +15 -0
  13. package/dist/SvComboBox.svelte +130 -0
  14. package/dist/SvComboBox.svelte.d.ts +15 -0
  15. package/dist/SvCountryInput.svelte +123 -0
  16. package/dist/SvCountryInput.svelte.d.ts +14 -0
  17. package/dist/SvDateTimePicker.svelte +322 -0
  18. package/dist/SvDateTimePicker.svelte.d.ts +37 -0
  19. package/dist/SvDropDownList.svelte +143 -0
  20. package/dist/SvDropDownList.svelte.d.ts +15 -0
  21. package/dist/SvForm.svelte +147 -0
  22. package/dist/SvForm.svelte.d.ts +29 -0
  23. package/dist/SvGauge.svelte +99 -0
  24. package/dist/SvGauge.svelte.d.ts +31 -0
  25. package/dist/SvGrid.controller.svelte.d.ts +16 -2
  26. package/dist/SvGrid.controller.svelte.js +63 -8
  27. package/dist/SvGrid.css +84 -31
  28. package/dist/SvGrid.helpers.d.ts +4 -0
  29. package/dist/SvGrid.helpers.js +40 -12
  30. package/dist/SvGrid.svelte +84 -5
  31. package/dist/SvGrid.types.d.ts +36 -0
  32. package/dist/SvGridDropdown.svelte +16 -30
  33. package/dist/SvGridDropdown.svelte.d.ts +4 -0
  34. package/dist/SvListBox.svelte +122 -0
  35. package/dist/SvListBox.svelte.d.ts +19 -0
  36. package/dist/SvMaskedInput.svelte +0 -0
  37. package/dist/SvMaskedInput.svelte.d.ts +15 -0
  38. package/dist/SvNumberInput.svelte +0 -0
  39. package/dist/SvNumberInput.svelte.d.ts +22 -0
  40. package/dist/SvPasswordInput.svelte +110 -0
  41. package/dist/SvPasswordInput.svelte.d.ts +22 -0
  42. package/dist/SvPhoneInput.svelte +0 -0
  43. package/dist/SvPhoneInput.svelte.d.ts +19 -0
  44. package/dist/SvRadioGroup.svelte +108 -0
  45. package/dist/SvRadioGroup.svelte.d.ts +23 -0
  46. package/dist/SvRating.svelte +111 -0
  47. package/dist/SvRating.svelte.d.ts +20 -0
  48. package/dist/SvRepeatButton.svelte +89 -0
  49. package/dist/SvRepeatButton.svelte.d.ts +21 -0
  50. package/dist/SvSlider.svelte +182 -0
  51. package/dist/SvSlider.svelte.d.ts +25 -0
  52. package/dist/SvSwitchButton.svelte +94 -0
  53. package/dist/SvSwitchButton.svelte.d.ts +19 -0
  54. package/dist/SvTabs.svelte +122 -0
  55. package/dist/SvTabs.svelte.d.ts +26 -0
  56. package/dist/SvTagsInput.svelte +81 -0
  57. package/dist/SvTagsInput.svelte.d.ts +19 -0
  58. package/dist/SvTimePicker.svelte +359 -0
  59. package/dist/SvTimePicker.svelte.d.ts +27 -0
  60. package/dist/SvToggleButton.svelte +57 -0
  61. package/dist/SvToggleButton.svelte.d.ts +16 -0
  62. package/dist/SvTree.svelte +210 -0
  63. package/dist/SvTree.svelte.d.ts +29 -0
  64. package/dist/build-api.js +78 -1
  65. package/dist/cdn/svgrid.js +10022 -5904
  66. package/dist/cdn/svgrid.svelte-external.js +8531 -4429
  67. package/dist/cell-render.d.ts +4 -0
  68. package/dist/cell-render.js +35 -1
  69. package/dist/clipboard.js +19 -1
  70. package/dist/collaboration.d.ts +2 -0
  71. package/dist/collaboration.js +4 -0
  72. package/dist/core.d.ts +22 -1
  73. package/dist/countries.d.ts +15 -0
  74. package/dist/countries.js +61 -0
  75. package/dist/datetime/date-core.d.ts +88 -0
  76. package/dist/datetime/date-core.js +165 -0
  77. package/dist/datetime/date-format.d.ts +39 -0
  78. package/dist/datetime/date-format.js +371 -0
  79. package/dist/datetime/date-restrict.d.ts +23 -0
  80. package/dist/datetime/date-restrict.js +50 -0
  81. package/dist/datetime/date-selection.d.ts +25 -0
  82. package/dist/datetime/date-selection.js +94 -0
  83. package/dist/datetime/mask.d.ts +21 -0
  84. package/dist/datetime/mask.js +90 -0
  85. package/dist/editing.d.ts +1 -1
  86. package/dist/editing.js +7 -0
  87. package/dist/editors/cell-editors.d.ts +2 -2
  88. package/dist/editors/cell-editors.js +3 -1
  89. package/dist/export-format.d.ts +178 -0
  90. package/dist/export-format.js +419 -0
  91. package/dist/hyperformula-adapter.d.ts +4 -0
  92. package/dist/hyperformula-adapter.js +15 -6
  93. package/dist/index.d.ts +36 -0
  94. package/dist/index.js +41 -0
  95. package/dist/list-option.d.ts +10 -0
  96. package/dist/list-option.js +7 -0
  97. package/dist/menus.js +18 -2
  98. package/dist/popover.d.ts +48 -0
  99. package/dist/popover.js +65 -0
  100. package/dist/row-resize.d.ts +4 -4
  101. package/dist/row-resize.js +8 -5
  102. package/dist/selection.d.ts +6 -0
  103. package/dist/selection.js +31 -5
  104. package/dist/server-data-source.d.ts +43 -0
  105. package/dist/server-data-source.js +100 -0
  106. package/dist/sparkline.js +8 -4
  107. package/dist/svgrid-wrapper.types.d.ts +26 -1
  108. package/package.json +6 -1
  109. package/src/GridFooter.svelte +34 -20
  110. package/src/SvAutoComplete.svelte +100 -0
  111. package/src/SvButton.svelte +97 -0
  112. package/src/SvCalendar.svelte +669 -0
  113. package/src/SvCalendar.test.ts +211 -0
  114. package/src/SvCheckBox.svelte +81 -0
  115. package/src/SvColorInput.svelte +187 -0
  116. package/src/SvComboBox.svelte +130 -0
  117. package/src/SvCountryInput.svelte +123 -0
  118. package/src/SvDateTimePicker.svelte +322 -0
  119. package/src/SvDateTimePicker.test.ts +136 -0
  120. package/src/SvDropDownList.svelte +143 -0
  121. package/src/SvForm.svelte +147 -0
  122. package/src/SvGauge.svelte +99 -0
  123. package/src/SvGrid.controller.svelte.ts +74 -8
  124. package/src/SvGrid.css +84 -31
  125. package/src/SvGrid.helpers.ts +41 -12
  126. package/src/SvGrid.svelte +84 -5
  127. package/src/SvGrid.types.ts +33 -0
  128. package/src/SvGridDropdown.svelte +16 -30
  129. package/src/SvListBox.svelte +122 -0
  130. package/src/SvMaskedInput.svelte +0 -0
  131. package/src/SvNumberInput.svelte +0 -0
  132. package/src/SvPasswordInput.svelte +110 -0
  133. package/src/SvPhoneInput.svelte +0 -0
  134. package/src/SvRadioGroup.svelte +108 -0
  135. package/src/SvRating.svelte +111 -0
  136. package/src/SvRepeatButton.svelte +89 -0
  137. package/src/SvSlider.svelte +182 -0
  138. package/src/SvSwitchButton.svelte +94 -0
  139. package/src/SvTabs.svelte +122 -0
  140. package/src/SvTagsInput.svelte +81 -0
  141. package/src/SvTimePicker.svelte +359 -0
  142. package/src/SvTimePicker.test.ts +98 -0
  143. package/src/SvToggleButton.svelte +57 -0
  144. package/src/SvTree.svelte +210 -0
  145. package/src/build-api.ts +115 -0
  146. package/src/cell-render.test.ts +38 -0
  147. package/src/cell-render.ts +43 -0
  148. package/src/clipboard.test.ts +18 -0
  149. package/src/clipboard.ts +24 -1
  150. package/src/collaboration.test.ts +30 -0
  151. package/src/collaboration.ts +6 -0
  152. package/src/core.ts +27 -3
  153. package/src/countries.ts +71 -0
  154. package/src/datetime/date-core.test.ts +217 -0
  155. package/src/datetime/date-core.ts +204 -0
  156. package/src/datetime/date-format.test.ts +121 -0
  157. package/src/datetime/date-format.ts +317 -0
  158. package/src/datetime/date-restrict.ts +60 -0
  159. package/src/datetime/date-selection.test.ts +129 -0
  160. package/src/datetime/date-selection.ts +137 -0
  161. package/src/datetime/mask.test.ts +36 -0
  162. package/src/datetime/mask.ts +84 -0
  163. package/src/editing.test.ts +22 -0
  164. package/src/editing.ts +7 -1
  165. package/src/editors/cell-editors.ts +7 -1
  166. package/src/export-data-api.test.ts +126 -0
  167. package/src/export-format.test.ts +107 -0
  168. package/src/export-format.ts +598 -0
  169. package/src/hyperformula-adapter.test.ts +35 -1
  170. package/src/hyperformula-adapter.ts +18 -6
  171. package/src/index.ts +71 -0
  172. package/src/list-option.ts +15 -0
  173. package/src/menus.ts +21 -2
  174. package/src/popover.ts +79 -0
  175. package/src/row-resize.test.ts +25 -0
  176. package/src/row-resize.ts +8 -5
  177. package/src/selection.test.ts +36 -4
  178. package/src/selection.ts +30 -9
  179. package/src/server-data-source.test.ts +201 -0
  180. package/src/server-data-source.ts +148 -0
  181. package/src/sparkline.test.ts +9 -0
  182. package/src/sparkline.ts +9 -4
  183. package/src/svgrid-wrapper.types.ts +28 -1
  184. package/src/svgrid.behavior.test.ts +27 -0
  185. package/src/svgrid.new-features.wrapper.test.ts +27 -1
  186. package/src/ui-buttons.test.ts +105 -0
  187. package/src/ui-composite.test.ts +89 -0
  188. package/src/ui-inputs.test.ts +92 -0
  189. package/src/ui-range.test.ts +61 -0
  190. package/src/ui-selection.test.ts +106 -0
@@ -0,0 +1,371 @@
1
+ /**
2
+ * date-format - a small .NET/Smart-style date format-token engine used for
3
+ * `formatString` display and for parsing the masked DateTimePicker input. It is
4
+ * locale-aware for names (month/weekday, AM/PM) via `Intl`, and framework-free.
5
+ *
6
+ * Supported tokens (case-sensitive, matching Smart's `formatString`):
7
+ * d dd day of month (5, 05)
8
+ * ddd dddd weekday name (Mon, Monday) [locale]
9
+ * M MM month number (6, 06)
10
+ * MMM MMMM month name (Jun, June) [locale]
11
+ * yy yyyy year (26, 2026)
12
+ * H HH hour 24 (7, 07)
13
+ * h hh hour 12 (7, 07)
14
+ * m mm minute
15
+ * s ss second
16
+ * f ff fff fractional seconds (tenths..millis)
17
+ * t tt AM/PM designator (A, AM) [locale]
18
+ *
19
+ * Anything else is a literal. Wrap literal letters in single quotes ('T') or
20
+ * escape with a backslash (\T) to keep them out of token matching.
21
+ */
22
+ /** Ordered longest-first so the scanner is greedy and unambiguous. */
23
+ const TOKENS = [
24
+ 'dddd', 'ddd', 'dd', 'd',
25
+ 'MMMM', 'MMM', 'MM', 'M',
26
+ 'yyyy', 'yy',
27
+ 'HH', 'H',
28
+ 'hh', 'h',
29
+ 'mm', 'm',
30
+ 'ss', 's',
31
+ 'fff', 'ff', 'f',
32
+ 'tt', 't',
33
+ ];
34
+ /** Split a mask into an ordered list of token / literal parts. */
35
+ export function tokenizeMask(mask) {
36
+ const parts = [];
37
+ let i = 0;
38
+ while (i < mask.length) {
39
+ const ch = mask[i];
40
+ // Backslash escape: next char is a literal.
41
+ if (ch === '\\' && i + 1 < mask.length) {
42
+ parts.push({ literal: mask[i + 1] });
43
+ i += 2;
44
+ continue;
45
+ }
46
+ // Single-quoted literal run.
47
+ if (ch === "'") {
48
+ let j = i + 1;
49
+ let lit = '';
50
+ while (j < mask.length && mask[j] !== "'") {
51
+ lit += mask[j];
52
+ j++;
53
+ }
54
+ parts.push({ literal: lit });
55
+ i = j + 1; // skip closing quote (or run to end)
56
+ continue;
57
+ }
58
+ // Greedy longest-token match.
59
+ let matched = null;
60
+ for (const t of TOKENS) {
61
+ if (mask.startsWith(t, i)) {
62
+ matched = t;
63
+ break;
64
+ }
65
+ }
66
+ if (matched) {
67
+ parts.push({ token: matched });
68
+ i += matched.length;
69
+ }
70
+ else {
71
+ parts.push({ literal: ch });
72
+ i += 1;
73
+ }
74
+ }
75
+ return parts;
76
+ }
77
+ const pad = (n, len) => String(Math.abs(n)).padStart(len, '0');
78
+ // Intl name caches keyed by locale.
79
+ const monthCache = new Map();
80
+ const weekdayCache = new Map();
81
+ const dayperiodCache = new Map();
82
+ function monthNames(locale) {
83
+ let c = monthCache.get(locale);
84
+ if (!c) {
85
+ const long = new Intl.DateTimeFormat(locale, { month: 'long' });
86
+ const short = new Intl.DateTimeFormat(locale, { month: 'short' });
87
+ c = {
88
+ long: Array.from({ length: 12 }, (_, m) => long.format(new Date(2021, m, 1))),
89
+ short: Array.from({ length: 12 }, (_, m) => short.format(new Date(2021, m, 1))),
90
+ };
91
+ monthCache.set(locale, c);
92
+ }
93
+ return c;
94
+ }
95
+ function weekdayNames(locale) {
96
+ let c = weekdayCache.get(locale);
97
+ if (!c) {
98
+ const long = new Intl.DateTimeFormat(locale, { weekday: 'long' });
99
+ const short = new Intl.DateTimeFormat(locale, { weekday: 'short' });
100
+ // 2021-08-01 is a Sunday.
101
+ c = {
102
+ long: Array.from({ length: 7 }, (_, d) => long.format(new Date(2021, 7, 1 + d))),
103
+ short: Array.from({ length: 7 }, (_, d) => short.format(new Date(2021, 7, 1 + d))),
104
+ };
105
+ weekdayCache.set(locale, c);
106
+ }
107
+ return c;
108
+ }
109
+ function dayPeriods(locale) {
110
+ let c = dayperiodCache.get(locale);
111
+ if (!c) {
112
+ const f = new Intl.DateTimeFormat(locale, { hour: 'numeric', hour12: true });
113
+ const am = f.formatToParts(new Date(2021, 0, 1, 9)).find((p) => p.type === 'dayPeriod')?.value ?? 'AM';
114
+ const pm = f.formatToParts(new Date(2021, 0, 1, 21)).find((p) => p.type === 'dayPeriod')?.value ?? 'PM';
115
+ c = { am, pm };
116
+ dayperiodCache.set(locale, c);
117
+ }
118
+ return c;
119
+ }
120
+ /** Render `date` per `mask` in `locale`. */
121
+ export function formatDate(date, mask, locale = 'en-US') {
122
+ const parts = tokenizeMask(mask);
123
+ const h24 = date.getHours();
124
+ const h12 = h24 % 12 === 0 ? 12 : h24 % 12;
125
+ const ms = date.getMilliseconds();
126
+ let out = '';
127
+ for (const p of parts) {
128
+ if ('literal' in p) {
129
+ out += p.literal;
130
+ continue;
131
+ }
132
+ switch (p.token) {
133
+ case 'd':
134
+ out += String(date.getDate());
135
+ break;
136
+ case 'dd':
137
+ out += pad(date.getDate(), 2);
138
+ break;
139
+ case 'ddd':
140
+ out += weekdayNames(locale).short[date.getDay()];
141
+ break;
142
+ case 'dddd':
143
+ out += weekdayNames(locale).long[date.getDay()];
144
+ break;
145
+ case 'M':
146
+ out += String(date.getMonth() + 1);
147
+ break;
148
+ case 'MM':
149
+ out += pad(date.getMonth() + 1, 2);
150
+ break;
151
+ case 'MMM':
152
+ out += monthNames(locale).short[date.getMonth()];
153
+ break;
154
+ case 'MMMM':
155
+ out += monthNames(locale).long[date.getMonth()];
156
+ break;
157
+ case 'yy':
158
+ out += pad(date.getFullYear() % 100, 2);
159
+ break;
160
+ case 'yyyy':
161
+ out += pad(date.getFullYear(), 4);
162
+ break;
163
+ case 'H':
164
+ out += String(h24);
165
+ break;
166
+ case 'HH':
167
+ out += pad(h24, 2);
168
+ break;
169
+ case 'h':
170
+ out += String(h12);
171
+ break;
172
+ case 'hh':
173
+ out += pad(h12, 2);
174
+ break;
175
+ case 'm':
176
+ out += String(date.getMinutes());
177
+ break;
178
+ case 'mm':
179
+ out += pad(date.getMinutes(), 2);
180
+ break;
181
+ case 's':
182
+ out += String(date.getSeconds());
183
+ break;
184
+ case 'ss':
185
+ out += pad(date.getSeconds(), 2);
186
+ break;
187
+ case 'f':
188
+ out += pad(ms, 3).slice(0, 1);
189
+ break;
190
+ case 'ff':
191
+ out += pad(ms, 3).slice(0, 2);
192
+ break;
193
+ case 'fff':
194
+ out += pad(ms, 3);
195
+ break;
196
+ case 't':
197
+ out += (h24 < 12 ? dayPeriods(locale).am : dayPeriods(locale).pm).slice(0, 1);
198
+ break;
199
+ case 'tt':
200
+ out += h24 < 12 ? dayPeriods(locale).am : dayPeriods(locale).pm;
201
+ break;
202
+ default: out += p.token;
203
+ }
204
+ }
205
+ return out;
206
+ }
207
+ const MAX_DIGITS = {
208
+ d: 2, dd: 2, M: 2, MM: 2, yy: 2, yyyy: 4,
209
+ H: 2, HH: 2, h: 2, hh: 2, m: 2, mm: 2, s: 2, ss: 2, f: 1, ff: 2, fff: 3,
210
+ };
211
+ function readDigits(str, at, max) {
212
+ let len = 0;
213
+ while (len < max && at + len < str.length && str[at + len] >= '0' && str[at + len] <= '9')
214
+ len++;
215
+ if (len === 0)
216
+ return null;
217
+ return { value: parseInt(str.slice(at, at + len), 10), len };
218
+ }
219
+ function matchName(str, at, names) {
220
+ // Longest-first so 'June' wins over a hypothetical 'Jun' prefix collision.
221
+ const ordered = names
222
+ .map((name, index) => ({ name, index }))
223
+ .sort((a, b) => b.name.length - a.name.length);
224
+ const lower = str.slice(at).toLowerCase();
225
+ for (const { name, index } of ordered) {
226
+ if (name && lower.startsWith(name.toLowerCase()))
227
+ return { index, len: name.length };
228
+ }
229
+ return null;
230
+ }
231
+ /**
232
+ * Parse `str` against `mask` in `locale`. Returns a Date or null if the string
233
+ * does not satisfy the mask. `yearCutoff` disambiguates 2-digit years: values
234
+ * <= (yearCutoff % 100) map to 2000s, above to 1900s (Smart's default 1926 ->
235
+ * a 26 pivot). `base` supplies fields the mask omits (defaults to epoch-zero
236
+ * local midnight so a time-only mask yields a today-agnostic time).
237
+ */
238
+ export function parseDate(str, mask, locale = 'en-US', yearCutoff = 2029, base) {
239
+ const parts = tokenizeMask(mask);
240
+ const acc = {};
241
+ let i = 0;
242
+ const s = str;
243
+ for (const p of parts) {
244
+ if ('literal' in p) {
245
+ // Literals must match (whitespace-insensitive at the boundary).
246
+ for (const ch of p.literal) {
247
+ if (ch === ' ') {
248
+ while (s[i] === ' ')
249
+ i++;
250
+ continue;
251
+ }
252
+ if (s[i] !== ch)
253
+ return null;
254
+ i++;
255
+ }
256
+ continue;
257
+ }
258
+ const tok = p.token;
259
+ switch (tok) {
260
+ case 'ddd':
261
+ case 'dddd': {
262
+ const m = matchName(s, i, weekdayNames(locale)[tok === 'ddd' ? 'short' : 'long']);
263
+ if (!m)
264
+ return null;
265
+ i += m.len; // weekday is informational; day-of-month drives the date
266
+ break;
267
+ }
268
+ case 'MMM':
269
+ case 'MMMM': {
270
+ const m = matchName(s, i, monthNames(locale)[tok === 'MMM' ? 'short' : 'long']);
271
+ if (!m)
272
+ return null;
273
+ acc.month = m.index;
274
+ i += m.len;
275
+ break;
276
+ }
277
+ case 't':
278
+ case 'tt': {
279
+ const dp = dayPeriods(locale);
280
+ const lower = s.slice(i).toLowerCase();
281
+ if (lower.startsWith(dp.pm.toLowerCase())) {
282
+ acc.isPm = true;
283
+ i += dp.pm.length;
284
+ }
285
+ else if (lower.startsWith(dp.am.toLowerCase())) {
286
+ acc.isPm = false;
287
+ i += dp.am.length;
288
+ }
289
+ else if (lower.startsWith(dp.pm.slice(0, 1).toLowerCase())) {
290
+ acc.isPm = true;
291
+ i += 1;
292
+ }
293
+ else if (lower.startsWith(dp.am.slice(0, 1).toLowerCase())) {
294
+ acc.isPm = false;
295
+ i += 1;
296
+ }
297
+ else
298
+ return null;
299
+ break;
300
+ }
301
+ default: {
302
+ const r = readDigits(s, i, MAX_DIGITS[tok] ?? 2);
303
+ if (!r)
304
+ return null;
305
+ i += r.len;
306
+ const v = r.value;
307
+ switch (tok) {
308
+ case 'd':
309
+ case 'dd':
310
+ acc.day = v;
311
+ break;
312
+ case 'M':
313
+ case 'MM':
314
+ acc.month = v - 1;
315
+ break;
316
+ case 'yy':
317
+ acc.year = v <= yearCutoff % 100 ? 2000 + v : 1900 + v;
318
+ break;
319
+ case 'yyyy':
320
+ acc.year = v;
321
+ break;
322
+ case 'H':
323
+ case 'HH':
324
+ acc.hour = v;
325
+ break;
326
+ case 'h':
327
+ case 'hh':
328
+ acc.hour12 = v;
329
+ break;
330
+ case 'm':
331
+ case 'mm':
332
+ acc.minute = v;
333
+ break;
334
+ case 's':
335
+ case 'ss':
336
+ acc.second = v;
337
+ break;
338
+ case 'f':
339
+ acc.ms = v * 100;
340
+ break;
341
+ case 'ff':
342
+ acc.ms = v * 10;
343
+ break;
344
+ case 'fff':
345
+ acc.ms = v;
346
+ break;
347
+ }
348
+ }
349
+ }
350
+ }
351
+ // Trailing input that the mask did not consume is a mismatch.
352
+ if (i !== s.length && s.slice(i).trim() !== '')
353
+ return null;
354
+ const b = base ?? new Date(1970, 0, 1);
355
+ let hour = acc.hour;
356
+ if (hour == null && acc.hour12 != null) {
357
+ hour = acc.hour12 % 12;
358
+ if (acc.isPm)
359
+ hour += 12;
360
+ }
361
+ const year = acc.year ?? b.getFullYear();
362
+ const month = acc.month ?? b.getMonth();
363
+ const day = acc.day ?? b.getDate();
364
+ const result = new Date(year, month, day, hour ?? b.getHours(), acc.minute ?? b.getMinutes(), acc.second ?? b.getSeconds(), acc.ms ?? b.getMilliseconds());
365
+ // Reject impossible dates the constructor silently rolls over (e.g. Feb 31).
366
+ if ((acc.day != null && result.getDate() !== day) ||
367
+ (acc.month != null && result.getMonth() !== month)) {
368
+ return null;
369
+ }
370
+ return isNaN(result.getTime()) ? null : result;
371
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * date-restrict - predicates for calendar constraints: min/max bounds,
3
+ * `restrictedDates` (non-selectable), and `importantDates` (highlighted but
4
+ * still selectable). Mirrors Smart's calendar constraint model. Framework-free.
5
+ */
6
+ import { type DateLike } from './date-core';
7
+ export type RestrictOptions = {
8
+ min?: DateLike | null;
9
+ max?: DateLike | null;
10
+ /** Individual dates (or predicate) that cannot be selected. */
11
+ restrictedDates?: ReadonlyArray<DateLike> | ((d: Date) => boolean) | null;
12
+ };
13
+ /** True when `d` is outside the inclusive [min, max] day range. */
14
+ export declare function isOutOfRange(d: Date, min?: DateLike | null, max?: DateLike | null): boolean;
15
+ /** True when `d` is explicitly restricted (by list membership or predicate). */
16
+ export declare function isRestricted(d: Date, restrictedDates?: ReadonlyArray<DateLike> | ((day: Date) => boolean) | null): boolean;
17
+ /**
18
+ * True when `d` cannot be selected: out of [min, max] OR restricted. This is
19
+ * the single predicate the calendar/pickers use to disable a day.
20
+ */
21
+ export declare function isDisabledDay(d: Date, opts: RestrictOptions): boolean;
22
+ /** True when `d` is flagged important (highlighted). Never affects selectability. */
23
+ export declare function isImportant(d: Date, importantDates?: ReadonlyArray<DateLike> | ((day: Date) => boolean) | null): boolean;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * date-restrict - predicates for calendar constraints: min/max bounds,
3
+ * `restrictedDates` (non-selectable), and `importantDates` (highlighted but
4
+ * still selectable). Mirrors Smart's calendar constraint model. Framework-free.
5
+ */
6
+ import { compareDay, isSameDay, toDate } from './date-core';
7
+ function normalizeList(list) {
8
+ if (!list)
9
+ return [];
10
+ const out = [];
11
+ for (const item of list) {
12
+ const d = toDate(item);
13
+ if (d)
14
+ out.push(d);
15
+ }
16
+ return out;
17
+ }
18
+ /** True when `d` is outside the inclusive [min, max] day range. */
19
+ export function isOutOfRange(d, min, max) {
20
+ const lo = toDate(min ?? null);
21
+ const hi = toDate(max ?? null);
22
+ if (lo && compareDay(d, lo) < 0)
23
+ return true;
24
+ if (hi && compareDay(d, hi) > 0)
25
+ return true;
26
+ return false;
27
+ }
28
+ /** True when `d` is explicitly restricted (by list membership or predicate). */
29
+ export function isRestricted(d, restrictedDates) {
30
+ if (!restrictedDates)
31
+ return false;
32
+ if (typeof restrictedDates === 'function')
33
+ return restrictedDates(d);
34
+ return normalizeList(restrictedDates).some((r) => isSameDay(r, d));
35
+ }
36
+ /**
37
+ * True when `d` cannot be selected: out of [min, max] OR restricted. This is
38
+ * the single predicate the calendar/pickers use to disable a day.
39
+ */
40
+ export function isDisabledDay(d, opts) {
41
+ return isOutOfRange(d, opts.min, opts.max) || isRestricted(d, opts.restrictedDates);
42
+ }
43
+ /** True when `d` is flagged important (highlighted). Never affects selectability. */
44
+ export function isImportant(d, importantDates) {
45
+ if (!importantDates)
46
+ return false;
47
+ if (typeof importantDates === 'function')
48
+ return importantDates(d);
49
+ return normalizeList(importantDates).some((r) => isSameDay(r, d));
50
+ }
@@ -0,0 +1,25 @@
1
+ export type SelectionMode = 'none' | 'default' | 'one' | 'zeroOrOne' | 'oneExtended' | 'many' | 'zeroOrMany' | 'oneOrMany' | 'week' | 'range';
2
+ export type SelectionModifiers = {
3
+ ctrl?: boolean;
4
+ shift?: boolean;
5
+ };
6
+ export type SelectionState = {
7
+ /** Selected days (deduped, sorted ascending), always local-midnight. */
8
+ dates: Date[];
9
+ /** Anchor for shift-extend (oneExtended). */
10
+ anchor: Date | null;
11
+ /** Pending range start (range mode, between first and second click). */
12
+ rangeStart: Date | null;
13
+ };
14
+ export declare const emptySelection: () => SelectionState;
15
+ /** True when `day` is in `dates` (by calendar day). */
16
+ export declare function isSelected(dates: ReadonlyArray<Date>, day: Date): boolean;
17
+ /** Inclusive ordered list of day-midnight dates from a..b (either order). */
18
+ export declare function rangeDays(a: Date, b: Date): Date[];
19
+ /**
20
+ * Apply a click on `clicked` to `state` under `mode`. `firstDayOfWeek` only
21
+ * matters for `week` mode. Returns a fresh state (never mutates the input).
22
+ */
23
+ export declare function selectDate(state: SelectionState, clicked: Date, mode: SelectionMode, modifiers?: SelectionModifiers, firstDayOfWeek?: number): SelectionState;
24
+ /** Whether a mode ever holds more than one date (drives multi-select UI hints). */
25
+ export declare function isMultiSelectMode(mode: SelectionMode): boolean;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * date-selection - implements every Smart calendar `selectionMode` as a pure
3
+ * reducer over the current selection. The calendar component owns pointer/key
4
+ * handling and delegates the "what gets selected" decision here so the rules
5
+ * are unit-testable in isolation.
6
+ */
7
+ import { addDays, compareDay, isSameDay, startOfDay, startOfWeek } from './date-core';
8
+ export const emptySelection = () => ({ dates: [], anchor: null, rangeStart: null });
9
+ /** True when `day` is in `dates` (by calendar day). */
10
+ export function isSelected(dates, day) {
11
+ return dates.some((d) => isSameDay(d, day));
12
+ }
13
+ /** Inclusive ordered list of day-midnight dates from a..b (either order). */
14
+ export function rangeDays(a, b) {
15
+ const lo = compareDay(a, b) <= 0 ? startOfDay(a) : startOfDay(b);
16
+ const hi = compareDay(a, b) <= 0 ? startOfDay(b) : startOfDay(a);
17
+ const out = [];
18
+ let cursor = lo;
19
+ while (compareDay(cursor, hi) <= 0) {
20
+ out.push(cursor);
21
+ cursor = addDays(cursor, 1);
22
+ }
23
+ return out;
24
+ }
25
+ function sortDedup(dates) {
26
+ const uniq = [];
27
+ for (const d of dates)
28
+ if (!uniq.some((u) => isSameDay(u, d)))
29
+ uniq.push(startOfDay(d));
30
+ uniq.sort((a, b) => a.getTime() - b.getTime());
31
+ return uniq;
32
+ }
33
+ function toggle(dates, day) {
34
+ return isSelected(dates, day) ? dates.filter((d) => !isSameDay(d, day)) : [...dates, startOfDay(day)];
35
+ }
36
+ /**
37
+ * Apply a click on `clicked` to `state` under `mode`. `firstDayOfWeek` only
38
+ * matters for `week` mode. Returns a fresh state (never mutates the input).
39
+ */
40
+ export function selectDate(state, clicked, mode, modifiers = {}, firstDayOfWeek = 0) {
41
+ const day = startOfDay(clicked);
42
+ const single = () => ({ dates: [day], anchor: day, rangeStart: null });
43
+ switch (mode) {
44
+ case 'none':
45
+ return { ...state };
46
+ case 'default':
47
+ case 'one':
48
+ return single();
49
+ case 'zeroOrOne':
50
+ // Toggle: clicking the sole selected day clears it.
51
+ return isSelected(state.dates, day) && state.dates.length === 1
52
+ ? emptySelection()
53
+ : single();
54
+ case 'many':
55
+ case 'zeroOrMany': {
56
+ const next = sortDedup(toggle(state.dates, day));
57
+ return { dates: next, anchor: day, rangeStart: null };
58
+ }
59
+ case 'oneOrMany': {
60
+ const toggled = toggle(state.dates, day);
61
+ // Never allow emptying below one.
62
+ const next = toggled.length === 0 ? [day] : toggled;
63
+ return { dates: sortDedup(next), anchor: day, rangeStart: null };
64
+ }
65
+ case 'oneExtended': {
66
+ if (modifiers.shift && state.anchor) {
67
+ return { dates: rangeDays(state.anchor, day), anchor: state.anchor, rangeStart: null };
68
+ }
69
+ if (modifiers.ctrl) {
70
+ const toggled = sortDedup(toggle(state.dates, day));
71
+ return { dates: toggled, anchor: day, rangeStart: null };
72
+ }
73
+ return single();
74
+ }
75
+ case 'week': {
76
+ const start = startOfWeek(day, firstDayOfWeek);
77
+ const days = Array.from({ length: 7 }, (_, i) => addDays(start, i));
78
+ return { dates: days, anchor: start, rangeStart: null };
79
+ }
80
+ case 'range': {
81
+ // First click sets the start; second click completes the range.
82
+ if (!state.rangeStart) {
83
+ return { dates: [day], anchor: day, rangeStart: day };
84
+ }
85
+ return { dates: rangeDays(state.rangeStart, day), anchor: state.rangeStart, rangeStart: null };
86
+ }
87
+ default:
88
+ return single();
89
+ }
90
+ }
91
+ /** Whether a mode ever holds more than one date (drives multi-select UI hints). */
92
+ export function isMultiSelectMode(mode) {
93
+ return mode === 'many' || mode === 'zeroOrMany' || mode === 'oneOrMany' || mode === 'oneExtended' || mode === 'week' || mode === 'range';
94
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * mask - a tiny pattern-mask engine shared by SvMaskedInput / SvPhoneInput.
3
+ * Pattern tokens:
4
+ * # a digit (0-9)
5
+ * A a letter (a-z A-Z)
6
+ * * a letter or digit
7
+ * Any other character is a literal that is auto-inserted. Framework-free.
8
+ */
9
+ /**
10
+ * Apply `pattern` to raw input `value`, inserting literals and dropping chars
11
+ * that don't fit the next token. Returns the formatted string and the count of
12
+ * consumed input characters (useful for caret math).
13
+ */
14
+ export declare function applyMask(value: string, pattern: string): {
15
+ formatted: string;
16
+ filled: number;
17
+ };
18
+ /** Strip literals, returning only the data characters the user supplied. */
19
+ export declare function unmask(value: string, pattern: string): string;
20
+ /** True when every token position in the pattern is filled. */
21
+ export declare function isMaskComplete(value: string, pattern: string): boolean;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * mask - a tiny pattern-mask engine shared by SvMaskedInput / SvPhoneInput.
3
+ * Pattern tokens:
4
+ * # a digit (0-9)
5
+ * A a letter (a-z A-Z)
6
+ * * a letter or digit
7
+ * Any other character is a literal that is auto-inserted. Framework-free.
8
+ */
9
+ const isDigit = (c) => c >= '0' && c <= '9';
10
+ const isAlpha = (c) => /[a-zA-Z]/.test(c);
11
+ function matches(token, ch) {
12
+ if (token === '#')
13
+ return isDigit(ch);
14
+ if (token === 'A')
15
+ return isAlpha(ch);
16
+ if (token === '*')
17
+ return isDigit(ch) || isAlpha(ch);
18
+ return false;
19
+ }
20
+ const isToken = (c) => c === '#' || c === 'A' || c === '*';
21
+ /**
22
+ * Apply `pattern` to raw input `value`, inserting literals and dropping chars
23
+ * that don't fit the next token. Returns the formatted string and the count of
24
+ * consumed input characters (useful for caret math).
25
+ */
26
+ export function applyMask(value, pattern) {
27
+ let out = '';
28
+ let vi = 0;
29
+ let filled = 0;
30
+ for (let pi = 0; pi < pattern.length; pi++) {
31
+ const token = pattern[pi];
32
+ if (!isToken(token)) {
33
+ // Literal: emit it only once we have (or will have) more input to place.
34
+ out += token;
35
+ // Skip a matching literal the user may have typed.
36
+ if (value[vi] === token)
37
+ vi++;
38
+ continue;
39
+ }
40
+ // Advance through input until a char fits this token.
41
+ while (vi < value.length && !matches(token, value[vi]))
42
+ vi++;
43
+ if (vi >= value.length)
44
+ break; // no more input for this token
45
+ out += value[vi];
46
+ vi++;
47
+ filled++;
48
+ }
49
+ // Drop any trailing literals with no following data char.
50
+ return { formatted: trimTrailingLiterals(out), filled };
51
+ }
52
+ function trimTrailingLiterals(out) {
53
+ // Count how many token-positions are filled; cut the string at the last
54
+ // filled data char plus any literals strictly before the next token.
55
+ let lastData = -1;
56
+ for (let i = 0; i < out.length; i++) {
57
+ const c = out[i];
58
+ if (isDigit(c) || isAlpha(c))
59
+ lastData = i;
60
+ }
61
+ if (lastData < 0)
62
+ return '';
63
+ // Keep literals that sit between data chars but trim the tail after last data.
64
+ return out.slice(0, lastData + 1);
65
+ }
66
+ /** Strip literals, returning only the data characters the user supplied. */
67
+ export function unmask(value, pattern) {
68
+ let out = '';
69
+ let vi = 0;
70
+ for (let pi = 0; pi < pattern.length && vi < value.length; pi++) {
71
+ const token = pattern[pi];
72
+ if (!isToken(token)) {
73
+ if (value[vi] === token)
74
+ vi++;
75
+ continue;
76
+ }
77
+ while (vi < value.length && !matches(token, value[vi]))
78
+ vi++;
79
+ if (vi < value.length) {
80
+ out += value[vi];
81
+ vi++;
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+ /** True when every token position in the pattern is filled. */
87
+ export function isMaskComplete(value, pattern) {
88
+ const tokenCount = [...pattern].filter(isToken).length;
89
+ return unmask(value, pattern).length >= tokenCount;
90
+ }
package/dist/editing.d.ts CHANGED
@@ -21,7 +21,7 @@ export declare function createEditing<TFeatures extends TableFeatures = TableFea
21
21
  cancelFullRowEdit: () => void;
22
22
  saveEditingCell: () => void;
23
23
  applyHistoryStep: (step: HistoryStep, direction: "undo" | "redo") => void;
24
- updateEditingCellValue: (value: string) => void;
24
+ updateEditingCellValue: (value: unknown) => void;
25
25
  onEditorKeyDown: (event: KeyboardEvent) => void;
26
26
  focusOnMount: (node: HTMLInputElement | HTMLTextAreaElement) => void;
27
27
  onCellDoubleClick: (rowIndex: number, colIndex: number) => void;