@dbcdk/react-components 0.0.10 → 0.0.13

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 (106) hide show
  1. package/dist/components/accordion/Accordion.d.ts +2 -2
  2. package/dist/components/accordion/Accordion.js +34 -41
  3. package/dist/components/accordion/Accordion.module.css +13 -72
  4. package/dist/components/accordion/components/AccordionRow.d.ts +10 -0
  5. package/dist/components/accordion/components/AccordionRow.js +51 -0
  6. package/dist/components/accordion/components/AccordionRow.module.css +82 -0
  7. package/dist/components/breadcrumbs/Breadcrumbs.module.css +0 -1
  8. package/dist/components/button/Button.module.css +7 -7
  9. package/dist/components/card/Card.d.ts +15 -6
  10. package/dist/components/card/Card.js +39 -13
  11. package/dist/components/card/Card.module.css +22 -28
  12. package/dist/components/card/components/CardMeta.d.ts +15 -0
  13. package/dist/components/card/components/CardMeta.js +20 -0
  14. package/dist/components/card/components/CardMeta.module.css +51 -0
  15. package/dist/components/card-container/CardContainer.js +1 -1
  16. package/dist/components/card-container/CardContainer.module.css +3 -1
  17. package/dist/components/chip/Chip.module.css +7 -2
  18. package/dist/components/circle/Circle.d.ts +2 -1
  19. package/dist/components/circle/Circle.js +2 -2
  20. package/dist/components/circle/Circle.module.css +6 -2
  21. package/dist/components/code-block/CodeBlock.js +1 -1
  22. package/dist/components/code-block/CodeBlock.module.css +30 -17
  23. package/dist/components/copy-button/CopyButton.d.ts +1 -0
  24. package/dist/components/copy-button/CopyButton.js +10 -2
  25. package/dist/components/datetime-picker/DateTimePicker.d.ts +33 -8
  26. package/dist/components/datetime-picker/DateTimePicker.js +119 -78
  27. package/dist/components/datetime-picker/DateTimePicker.module.css +2 -0
  28. package/dist/components/datetime-picker/dateTimeHelpers.d.ts +15 -3
  29. package/dist/components/datetime-picker/dateTimeHelpers.js +137 -23
  30. package/dist/components/filter-field/FilterField.js +16 -11
  31. package/dist/components/filter-field/FilterField.module.css +137 -16
  32. package/dist/components/forms/checkbox/Checkbox.d.ts +2 -2
  33. package/dist/components/forms/checkbox-group/CheckboxGroup.js +1 -1
  34. package/dist/components/forms/checkbox-group/CheckboxGroup.module.css +1 -1
  35. package/dist/components/forms/form-select/FormSelect.d.ts +35 -0
  36. package/dist/components/forms/form-select/FormSelect.js +86 -0
  37. package/dist/components/forms/form-select/FormSelect.module.css +236 -0
  38. package/dist/components/forms/input/Input.d.ts +0 -3
  39. package/dist/components/forms/input/Input.js +1 -4
  40. package/dist/components/forms/input/Input.module.css +8 -7
  41. package/dist/components/forms/input-container/InputContainer.module.css +1 -1
  42. package/dist/components/forms/radio-buttons/RadioButtons.module.css +1 -0
  43. package/dist/components/forms/select/Select.js +55 -16
  44. package/dist/components/hyperlink/Hyperlink.d.ts +19 -7
  45. package/dist/components/hyperlink/Hyperlink.js +35 -11
  46. package/dist/components/hyperlink/Hyperlink.module.css +50 -2
  47. package/dist/components/interval-select/IntervalSelect.d.ts +9 -2
  48. package/dist/components/interval-select/IntervalSelect.js +21 -6
  49. package/dist/components/menu/Menu.d.ts +29 -0
  50. package/dist/components/menu/Menu.js +61 -16
  51. package/dist/components/menu/Menu.module.css +73 -5
  52. package/dist/components/overlay/modal/Modal.module.css +4 -3
  53. package/dist/components/overlay/modal/provider/ModalProvider.js +1 -3
  54. package/dist/components/overlay/side-panel/SidePanel.js +18 -1
  55. package/dist/components/overlay/side-panel/SidePanel.module.css +1 -3
  56. package/dist/components/overlay/tooltip/useTooltipTrigger.js +4 -2
  57. package/dist/components/page-layout/PageLayout.d.ts +16 -4
  58. package/dist/components/page-layout/PageLayout.js +57 -28
  59. package/dist/components/page-layout/PageLayout.module.css +153 -33
  60. package/dist/components/popover/Popover.d.ts +17 -4
  61. package/dist/components/popover/Popover.js +147 -65
  62. package/dist/components/popover/Popover.module.css +5 -0
  63. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +22 -18
  64. package/dist/components/sidebar/providers/SidebarProvider.d.ts +4 -1
  65. package/dist/components/sidebar/providers/SidebarProvider.js +66 -18
  66. package/dist/components/split-button/SplitButton.d.ts +1 -1
  67. package/dist/components/split-button/SplitButton.js +3 -1
  68. package/dist/components/split-button/SplitButton.module.css +4 -4
  69. package/dist/components/split-pane/SplitPane.d.ts +10 -24
  70. package/dist/components/split-pane/SplitPane.js +83 -54
  71. package/dist/components/split-pane/SplitPane.module.css +11 -6
  72. package/dist/components/split-pane/provider/SplitPaneContext.js +5 -11
  73. package/dist/components/state-page/StatePage.module.css +1 -1
  74. package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +3 -8
  75. package/dist/components/sticky-footer-layout/StickyFooterLayout.js +57 -20
  76. package/dist/components/table/Table.d.ts +8 -8
  77. package/dist/components/table/Table.js +37 -79
  78. package/dist/components/table/Table.module.css +62 -46
  79. package/dist/components/table/{tanstack.d.ts → TanstackTable.d.ts} +7 -3
  80. package/dist/components/table/TanstackTable.js +84 -0
  81. package/dist/components/table/components/column-resizer/ColumnResizer.js +1 -1
  82. package/dist/components/table/components/column-resizer/ColumnResizer.module.css +17 -7
  83. package/dist/components/table/components/table-settings/TableSettings.d.ts +13 -3
  84. package/dist/components/table/components/table-settings/TableSettings.js +55 -4
  85. package/dist/components/table/table.utils.d.ts +17 -0
  86. package/dist/components/table/table.utils.js +61 -0
  87. package/dist/components/table/tanstackTable.utils.d.ts +22 -0
  88. package/dist/components/table/tanstackTable.utils.js +104 -0
  89. package/dist/components/tabs/Tabs.d.ts +35 -12
  90. package/dist/components/tabs/Tabs.js +114 -26
  91. package/dist/components/tabs/Tabs.module.css +158 -71
  92. package/dist/hooks/useTableSettings.d.ts +23 -4
  93. package/dist/hooks/useTableSettings.js +64 -17
  94. package/dist/index.d.ts +1 -1
  95. package/dist/index.js +1 -1
  96. package/dist/src/styles/styles.css +38 -23
  97. package/dist/styles/animation.d.ts +5 -0
  98. package/dist/styles/animation.js +5 -0
  99. package/dist/styles/styles.css +38 -23
  100. package/dist/styles/themes/dbc/base.css +136 -0
  101. package/dist/styles/themes/dbc/dark.css +39 -202
  102. package/dist/styles/themes/dbc/light.css +17 -174
  103. package/dist/utils/localStorage.utils.d.ts +19 -0
  104. package/dist/utils/localStorage.utils.js +78 -0
  105. package/package.json +4 -4
  106. package/dist/components/table/tanstack.js +0 -162
@@ -5,7 +5,7 @@ import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'r
5
5
  import { Button } from '../../components/button/Button';
6
6
  import { Input } from '../../components/forms/input/Input';
7
7
  import { Popover } from '../../components/popover/Popover';
8
- import { isoFromLocalDate, isoFromLocalParts, localDateFromIso, maskRange, maskSingle, parseLooseDateOrDateTime, parseLooseRange, utcMillisFromIso, toMaskedFromDate, } from './dateTimeHelpers';
8
+ import { compareDateOnly, dateOnlyFromLocalParts, isoFromLocalDate, isoFromLocalParts, localDateFromDateOnly, localDateFromIso, maskRange, maskSingle, parseLooseDateOrDateTime, parseLooseRange, toMaskedFromDate, } from './dateTimeHelpers';
9
9
  import styles from './DateTimePicker.module.css';
10
10
  /* ---------- Date grid helpers (UTC date-only cells) ---------- */
11
11
  const dUTC = (y, m, day) => new Date(Date.UTC(y, m, day));
@@ -15,7 +15,7 @@ const endOfMonthUTC = (utcDate) => dUTC(utcDate.getUTCFullYear(), utcDate.getUTC
15
15
  const sameDayUTC = (a, b) => a.getUTCFullYear() === b.getUTCFullYear() &&
16
16
  a.getUTCMonth() === b.getUTCMonth() &&
17
17
  a.getUTCDate() === b.getUTCDate();
18
- // IMPORTANT: treat local calendar date as the source of truth for the grid.
18
+ // Treat local calendar date as the source of truth for the grid.
19
19
  // Build an equivalent "UTC date-only" for comparisons.
20
20
  const toUTCDateOnlyFromLocal = (local) => dUTC(local.getFullYear(), local.getMonth(), local.getDate());
21
21
  const startOfWeekUTC = (utcDate, weekStartsOn) => {
@@ -56,20 +56,30 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
56
56
  void formatDate;
57
57
  void formatRange;
58
58
  const popRef = useRef(null);
59
+ // Narrowed onChange call signatures (avoids union-of-functions intersection issues)
60
+ const onChangeRange = onChange;
61
+ const onChangeIso = onChange;
62
+ const onChangeDateOnly = onChange;
59
63
  const todayLocal = useMemo(() => new Date(), []);
60
64
  // ---- local anchor from controlled value ----
61
65
  const initialAnchor = useMemo(() => {
62
- var _a, _b;
63
66
  if (mode === 'single') {
64
- if (typeof value === 'string')
65
- return (_a = localDateFromIso(value)) !== null && _a !== void 0 ? _a : todayLocal;
67
+ if (typeof value === 'string') {
68
+ const d = enableTime
69
+ ? localDateFromIso(value)
70
+ : localDateFromDateOnly(value);
71
+ return d !== null && d !== void 0 ? d : todayLocal;
72
+ }
66
73
  return todayLocal;
67
74
  }
68
- if (mode === 'range' && value && typeof value === 'object' && 'start' in value && value.start) {
69
- return (_b = localDateFromIso(value.start)) !== null && _b !== void 0 ? _b : todayLocal;
75
+ // range: anchor from start if present
76
+ const v = value;
77
+ if (v === null || v === void 0 ? void 0 : v.start) {
78
+ const d = localDateFromDateOnly(v.start);
79
+ return d !== null && d !== void 0 ? d : todayLocal;
70
80
  }
71
81
  return todayLocal;
72
- }, [mode, value, todayLocal]);
82
+ }, [mode, value, enableTime, todayLocal]);
73
83
  const [monthAnchor, setMonthAnchor] = useState(initialAnchor);
74
84
  useEffect(() => setMonthAnchor(initialAnchor), [initialAnchor]);
75
85
  // time defaults (local)
@@ -97,25 +107,27 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
97
107
  return null;
98
108
  if (typeof value !== 'string')
99
109
  return null;
100
- const d = localDateFromIso(value);
110
+ const d = enableTime ? localDateFromIso(value) : localDateFromDateOnly(value);
101
111
  if (!d)
102
112
  return null;
103
113
  return toUTCDateOnlyFromLocal(d);
104
- }, [mode, value]);
114
+ }, [mode, value, enableTime]);
105
115
  const selectedUTC_start = useMemo(() => {
106
- if (mode !== 'range' || !value || typeof value !== 'object' || !('start' in value))
116
+ if (mode !== 'range')
107
117
  return null;
108
- if (!value.start)
118
+ const v = value;
119
+ if (!(v === null || v === void 0 ? void 0 : v.start))
109
120
  return null;
110
- const d = localDateFromIso(value.start);
121
+ const d = localDateFromDateOnly(v.start);
111
122
  return d ? toUTCDateOnlyFromLocal(d) : null;
112
123
  }, [mode, value]);
113
124
  const selectedUTC_end = useMemo(() => {
114
- if (mode !== 'range' || !value || typeof value !== 'object' || !('end' in value))
125
+ if (mode !== 'range')
115
126
  return null;
116
- if (!value.end)
127
+ const v = value;
128
+ if (!(v === null || v === void 0 ? void 0 : v.end))
117
129
  return null;
118
- const d = localDateFromIso(value.end);
130
+ const d = localDateFromDateOnly(v.end);
119
131
  return d ? toUTCDateOnlyFromLocal(d) : null;
120
132
  }, [mode, value]);
121
133
  const isDisabledUTC = useCallback((utcDay) => {
@@ -126,7 +138,7 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
126
138
  return true;
127
139
  return false;
128
140
  }, [min, max]);
129
- const selectDay = (utcDay) => {
141
+ const selectDay = useCallback((utcDay) => {
130
142
  var _a, _b;
131
143
  if (isDisabledUTC(utcDay))
132
144
  return;
@@ -135,29 +147,51 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
135
147
  const m0 = utcDay.getUTCMonth();
136
148
  const d = utcDay.getUTCDate();
137
149
  if (mode === 'single') {
138
- const iso = enableTime
139
- ? isoFromLocalParts(y, m0, d, timeHH, timeMM)
140
- : isoFromLocalParts(y, m0, d, 0, 0);
141
- onChange(iso);
150
+ if (enableTime) {
151
+ const iso = isoFromLocalParts(y, m0, d, timeHH, timeMM);
152
+ if (!iso)
153
+ return; // DST-invalid time; you may want to surface validation UI
154
+ onChangeIso(iso);
155
+ }
156
+ else {
157
+ const dateOnly = dateOnlyFromLocalParts(y, m0, d);
158
+ onChangeDateOnly(dateOnly);
159
+ }
142
160
  (_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close();
143
161
  return;
144
162
  }
145
- // range (date-only in UI, but emitted as ISO instants at local midnight)
146
- const curr = value && typeof value === 'object' && 'start' in value
147
- ? value
148
- : { start: null, end: null };
149
- const picked = isoFromLocalParts(y, m0, d, 0, 0);
163
+ // range: date-only
164
+ const curr = value;
165
+ const picked = dateOnlyFromLocalParts(y, m0, d);
150
166
  if (!curr.start || (curr.start && curr.end)) {
151
- onChange({ start: picked, end: null });
167
+ onChangeRange({ start: picked, end: null });
152
168
  return;
153
169
  }
154
- const a = utcMillisFromIso(curr.start);
155
- const b = utcMillisFromIso(picked);
156
- const start = a <= b ? curr.start : picked;
157
- const end = a <= b ? picked : curr.start;
158
- onChange({ start, end });
170
+ const start = compareDateOnly(curr.start, picked) <= 0 ? curr.start : picked;
171
+ const end = compareDateOnly(curr.start, picked) <= 0 ? picked : curr.start;
172
+ onChangeRange({ start, end });
159
173
  (_b = popRef.current) === null || _b === void 0 ? void 0 : _b.close();
160
- };
174
+ }, [
175
+ enableTime,
176
+ isDisabledUTC,
177
+ mode,
178
+ onChangeDateOnly,
179
+ onChangeIso,
180
+ onChangeRange,
181
+ timeHH,
182
+ timeMM,
183
+ value,
184
+ ]);
185
+ function addMonthsLocal(dt, n) {
186
+ const y = dt.getFullYear();
187
+ const m = dt.getMonth();
188
+ return new Date(y, m + n, 1);
189
+ }
190
+ function addDaysLocal(dt, n) {
191
+ const d = new Date(dt);
192
+ d.setDate(d.getDate() + n);
193
+ return d;
194
+ }
161
195
  const gridRef = useRef(null);
162
196
  useEffect(() => {
163
197
  const el = gridRef.current;
@@ -179,33 +213,35 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
179
213
  ].includes(e.key)) {
180
214
  e.preventDefault();
181
215
  }
182
- const anchor = toUTCDateOnlyFromLocal(monthAnchor);
183
- const move = (days) => setMonthAnchor(prev => addDaysLocal(prev, days));
184
216
  switch (e.key) {
185
217
  case 'ArrowLeft':
186
- move(-1);
218
+ setMonthAnchor(prev => addDaysLocal(prev, -1));
187
219
  break;
188
220
  case 'ArrowRight':
189
- move(1);
221
+ setMonthAnchor(prev => addDaysLocal(prev, 1));
190
222
  break;
191
223
  case 'ArrowUp':
192
- move(-7);
224
+ setMonthAnchor(prev => addDaysLocal(prev, -7));
193
225
  break;
194
226
  case 'ArrowDown':
195
- move(7);
227
+ setMonthAnchor(prev => addDaysLocal(prev, 7));
196
228
  break;
197
229
  case 'PageUp':
198
- setMonthAnchor(addMonthsLocal(monthAnchor, -1));
230
+ setMonthAnchor(prev => addMonthsLocal(prev, -1));
199
231
  break;
200
232
  case 'PageDown':
201
- setMonthAnchor(addMonthsLocal(monthAnchor, 1));
233
+ setMonthAnchor(prev => addMonthsLocal(prev, 1));
202
234
  break;
203
- case 'Home':
204
- setMonthAnchor(new Date(anchor.getUTCFullYear(), anchor.getUTCMonth(), 1));
235
+ case 'Home': {
236
+ const a = toUTCDateOnlyFromLocal(monthAnchor);
237
+ setMonthAnchor(new Date(a.getUTCFullYear(), a.getUTCMonth(), 1));
205
238
  break;
206
- case 'End':
207
- setMonthAnchor(new Date(anchor.getUTCFullYear(), anchor.getUTCMonth() + 1, 0));
239
+ }
240
+ case 'End': {
241
+ const a = toUTCDateOnlyFromLocal(monthAnchor);
242
+ setMonthAnchor(new Date(a.getUTCFullYear(), a.getUTCMonth() + 1, 0));
208
243
  break;
244
+ }
209
245
  }
210
246
  };
211
247
  el.addEventListener('keydown', onKey);
@@ -218,12 +254,12 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
218
254
  if (mode === 'single') {
219
255
  if (typeof value !== 'string')
220
256
  return '';
221
- const d = localDateFromIso(value);
257
+ const d = enableTime ? localDateFromIso(value) : localDateFromDateOnly(value);
222
258
  return d ? toMaskedFromDate(d, enableTime) : '';
223
259
  }
224
260
  const v = value;
225
- const s = typeof (v === null || v === void 0 ? void 0 : v.start) === 'string' ? localDateFromIso(v.start) : null;
226
- const e = typeof (v === null || v === void 0 ? void 0 : v.end) === 'string' ? localDateFromIso(v.end) : null;
261
+ const s = typeof (v === null || v === void 0 ? void 0 : v.start) === 'string' ? localDateFromDateOnly(v.start) : null;
262
+ const e = typeof (v === null || v === void 0 ? void 0 : v.end) === 'string' ? localDateFromDateOnly(v.end) : null;
227
263
  const ss = s ? toMaskedFromDate(s, false) : '';
228
264
  const ee = e ? toMaskedFromDate(e, false) : '';
229
265
  if (ss && ee)
@@ -242,10 +278,15 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
242
278
  }, [formatted, dirty]);
243
279
  const commitTypedValue = useCallback(() => {
244
280
  if (!text.trim()) {
245
- if (mode === 'single')
246
- onChange(null);
247
- else
248
- onChange({ start: null, end: null });
281
+ if (mode === 'single') {
282
+ if (enableTime)
283
+ onChangeIso(null);
284
+ else
285
+ onChangeDateOnly(null);
286
+ }
287
+ else {
288
+ onChangeRange({ start: null, end: null });
289
+ }
249
290
  setDirty(false);
250
291
  return;
251
292
  }
@@ -253,41 +294,41 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
253
294
  const dLocal = parseLooseDateOrDateTime(text);
254
295
  if (!dLocal)
255
296
  return;
256
- // If enableTime=false, parseLooseDateOrDateTime returns 00:00 local -> still OK.
257
- onChange(isoFromLocalDate(dLocal));
297
+ if (enableTime) {
298
+ onChangeIso(isoFromLocalDate(dLocal));
299
+ }
300
+ else {
301
+ onChangeDateOnly(dateOnlyFromLocalParts(dLocal.getFullYear(), dLocal.getMonth(), dLocal.getDate()));
302
+ }
258
303
  setMonthAnchor(dLocal);
259
304
  setDirty(false);
260
305
  return;
261
306
  }
307
+ // range: date-only
262
308
  const r = parseLooseRange(text);
263
309
  if (r) {
264
- const startIso = isoFromLocalParts(r.start.getFullYear(), r.start.getMonth(), r.start.getDate(), 0, 0);
265
- const endIso = isoFromLocalParts(r.end.getFullYear(), r.end.getMonth(), r.end.getDate(), 0, 0);
266
- onChange({ start: startIso, end: endIso });
310
+ const start = dateOnlyFromLocalParts(r.start.getFullYear(), r.start.getMonth(), r.start.getDate());
311
+ const end = dateOnlyFromLocalParts(r.end.getFullYear(), r.end.getMonth(), r.end.getDate());
312
+ onChangeRange({ start, end });
267
313
  setMonthAnchor(r.start);
268
314
  setDirty(false);
269
315
  }
270
- }, [text, mode, onChange]);
316
+ }, [text, mode, enableTime, onChangeDateOnly, onChangeIso, onChangeRange]);
271
317
  const clear = useCallback(() => {
272
- if (mode === 'single')
273
- onChange(null);
274
- else
275
- onChange({ start: null, end: null });
318
+ if (mode === 'single') {
319
+ if (enableTime)
320
+ onChangeIso(null);
321
+ else
322
+ onChangeDateOnly(null);
323
+ }
324
+ else {
325
+ onChangeRange({ start: null, end: null });
326
+ }
276
327
  setDirty(false);
277
328
  setText('');
278
- }, [mode, onChange]);
279
- function addMonthsLocal(dt, n) {
280
- const y = dt.getFullYear();
281
- const m = dt.getMonth();
282
- return new Date(y, m + n, 1);
283
- }
284
- function addDaysLocal(dt, n) {
285
- const d = new Date(dt);
286
- d.setDate(d.getDate() + n);
287
- return d;
288
- }
329
+ }, [mode, enableTime, onChangeDateOnly, onChangeIso, onChangeRange]);
289
330
  const fallbackPlaceholder = mode === 'single' ? 'Vælg dato' : 'Vælg interval';
290
- return (_jsx(Popover, { ref: popRef, trigger: toggle => {
331
+ return (_jsx(Popover, { matchTriggerWidth: false, ref: popRef, trigger: toggle => {
291
332
  var _a, _b;
292
333
  return (_jsx("div", { onClick: toggle, className: styles.triggerWrap, children: _jsx(Input, { ...inputProps, autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: "false", placeholder: (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.placeholder) !== null && _a !== void 0 ? _a : fallbackPlaceholder, value: dirty ? text : formatted, onInput: e => {
293
334
  setDirty(true);
@@ -307,14 +348,14 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
307
348
  }, viewportPadding: 8, children: _jsxs("div", { className: cx(styles.panel, !!(presets === null || presets === void 0 ? void 0 : presets.length) && styles.panelWithPresets), children: [(presets === null || presets === void 0 ? void 0 : presets.length) ? (_jsxs("div", { className: styles.presetsCol, children: [_jsx("div", { className: styles.presetsLabel, children: "Forvalg" }), _jsxs("div", { className: styles.presetsList, children: [presets.map(p => (_jsx(Button, { variant: "outlined", size: "sm", onClick: () => {
308
349
  var _a;
309
350
  const r = p.getRange();
310
- const startIso = isoFromLocalParts(r.start.getFullYear(), r.start.getMonth(), r.start.getDate(), 0, 0);
311
- const endIso = isoFromLocalParts(r.end.getFullYear(), r.end.getMonth(), r.end.getDate(), 0, 0);
312
- onChange({ start: startIso, end: endIso });
351
+ const start = dateOnlyFromLocalParts(r.start.getFullYear(), r.start.getMonth(), r.start.getDate());
352
+ const end = dateOnlyFromLocalParts(r.end.getFullYear(), r.end.getMonth(), r.end.getDate());
353
+ onChangeRange({ start, end });
313
354
  setDirty(false);
314
355
  setText(`${toMaskedFromDate(r.start, false)} – ${toMaskedFromDate(r.end, false)}`);
315
356
  setMonthAnchor(r.start);
316
357
  (_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close();
317
- }, children: p.label }, p.label))), mode === 'range' && (_jsx(Button, { variant: "danger", size: "sm", onClick: clear, icon: _jsx(X, { size: 14 }), children: "Ryd" }))] })] })) : null, _jsxs("div", { className: styles.calendarArea, children: [_jsxs("div", { className: styles.header, children: [_jsx(Button, { variant: "outlined", size: "sm", "aria-label": "Forrige m\u00E5ned", icon: _jsx(ChevronLeft, { size: 16 }), onClick: () => setMonthAnchor(addMonthsLocal(monthAnchor, -1)) }), _jsx("div", { "aria-live": "polite", className: styles.headerTitle, children: monthFmt.format(monthAnchor) }), _jsx(Button, { variant: "outlined", size: "sm", "aria-label": "N\u00E6ste m\u00E5ned", icon: _jsx(ChevronRight, { size: 16 }), onClick: () => setMonthAnchor(addMonthsLocal(monthAnchor, 1)) })] }), _jsx("div", { className: styles.weekRow, "aria-hidden": true, children: Array.from({ length: 7 }, (_, i) => (i + weekStartsOn) % 7).map(dow => (_jsx("div", { className: styles.weekCell, children: weekdayFmt.format(dUTC(2024, 8, dow + 1)).slice(0, 2) }, dow))) }), _jsx("div", { ref: gridRef, role: "grid", "aria-label": "Kalender", tabIndex: 0, className: styles.grid, onMouseLeave: () => setHoverUTC(null), children: cellsUTC.map((utcDay, idx) => {
358
+ }, children: p.label }, p.label))), mode === 'range' && (_jsx(Button, { variant: "danger", size: "sm", onClick: clear, icon: _jsx(X, { size: 14 }), children: "Ryd" }))] })] })) : null, _jsxs("div", { className: styles.calendarArea, children: [_jsxs("div", { className: styles.header, children: [_jsx(Button, { variant: "outlined", size: "sm", "aria-label": "Forrige m\u00E5ned", icon: _jsx(ChevronLeft, { size: 16 }), onClick: () => setMonthAnchor(prev => addMonthsLocal(prev, -1)) }), _jsx("div", { "aria-live": "polite", className: styles.headerTitle, children: monthFmt.format(monthAnchor) }), _jsx(Button, { variant: "outlined", size: "sm", "aria-label": "N\u00E6ste m\u00E5ned", icon: _jsx(ChevronRight, { size: 16 }), onClick: () => setMonthAnchor(prev => addMonthsLocal(prev, 1)) })] }), _jsx("div", { className: styles.weekRow, "aria-hidden": true, children: Array.from({ length: 7 }, (_, i) => (i + weekStartsOn) % 7).map(dow => (_jsx("div", { className: styles.weekCell, children: weekdayFmt.format(dUTC(2024, 8, dow + 1)).slice(0, 2) }, dow))) }), _jsx("div", { ref: gridRef, role: "grid", "aria-label": "Kalender", tabIndex: 0, className: styles.grid, onMouseLeave: () => setHoverUTC(null), children: cellsUTC.map((utcDay, idx) => {
318
359
  const inThisMonth = utcDay >= monthStartUTC && utcDay <= monthEndUTC;
319
360
  const isToday = sameDayUTC(utcDay, toUTCDateOnlyFromLocal(todayLocal));
320
361
  const disabledDay = isDisabledUTC(utcDay);
@@ -5,6 +5,7 @@
5
5
  .panel {
6
6
  display: grid;
7
7
  grid-template-columns: 1fr;
8
+ width: fit-content;
8
9
  gap: 8px;
9
10
  padding: 8px;
10
11
  background: var(--color-bg-surface);
@@ -32,6 +33,7 @@
32
33
 
33
34
  .calendarArea {
34
35
  display: grid;
36
+ width: max-content;
35
37
  gap: 8px;
36
38
  }
37
39
 
@@ -5,18 +5,30 @@ export declare function maskSingle(text: string, enableTime: boolean): string;
5
5
  export declare function maskRange(text: string, _enableTime: boolean): string;
6
6
  export declare const pad2: (n: number) => string;
7
7
  export type UtcIsoString = string;
8
+ export type DateOnlyString = string;
8
9
  export declare function isUtcIsoString(v: unknown): v is UtcIsoString;
10
+ export declare function isDateOnlyString(v: unknown): v is DateOnlyString;
9
11
  export declare function utcMillisFromIso(iso: UtcIsoString): number;
12
+ /** Convert a local DateTime to a UTC ISO string (Z). */
13
+ export declare function isoFromLocalDate(dLocal: Date): UtcIsoString;
10
14
  /**
11
15
  * Build a *local* Date from y/m/d/hh/mm and return UTC ISO string (Z).
12
16
  * This keeps the UI meaning "local wall time", while emitting a stable UTC instant.
17
+ *
18
+ * DST NOTE:
19
+ * If the requested local wall time does not exist (spring-forward), JS will auto-correct.
20
+ * We detect that and return null so caller can handle it (e.g., show validation error).
13
21
  */
14
22
  export declare function isoFromLocalParts(y: number, m0: number, // 0-based
15
- d: number, hh?: number, mm?: number): UtcIsoString;
16
- /** Convert a local Date to a UTC ISO string (Z). */
17
- export declare function isoFromLocalDate(dLocal: Date): UtcIsoString;
23
+ d: number, hh?: number, mm?: number): UtcIsoString | null;
18
24
  /** For anchoring the calendar safely from a UTC ISO string. */
19
25
  export declare function localDateFromIso(iso: UtcIsoString): Date | null;
26
+ /** Date-only: build 'YYYY-MM-DD' from local parts */
27
+ export declare function dateOnlyFromLocalParts(y: number, m0: number, d: number): DateOnlyString;
28
+ /** Parse 'YYYY-MM-DD' into a local Date at midnight */
29
+ export declare function localDateFromDateOnly(s: DateOnlyString): Date | null;
30
+ /** Compare date-only strings by UTC date-only time (stable ordering) */
31
+ export declare function compareDateOnly(a: DateOnlyString, b: DateOnlyString): number;
20
32
  export declare function toMaskedFromDate(d: Date, enableTime: boolean): string;
21
33
  export declare function parseLooseDateOrDateTime(input: string): Date | null;
22
34
  export declare function parseLooseRange(input: string): {
@@ -1,3 +1,49 @@
1
+ /* ---------- Range separator helpers ---------- */
2
+ /**
3
+ * IMPORTANT:
4
+ * "-" is only treated as a range separator when surrounded by whitespace: " - "
5
+ * so we don't split inside "DD-MM-YYYY".
6
+ *
7
+ * Supported separators:
8
+ * - En dash: "–" (with optional surrounding spaces)
9
+ * - Hyphen as range sep: " - " (spaces required)
10
+ * - Words: "to", "til" (with optional surrounding spaces)
11
+ *
12
+ * COMPAT:
13
+ * - Also supports digit-only ranges like "01022026-02022026" (no spaces),
14
+ * because that cannot be confused with "DD-MM-YYYY".
15
+ */
16
+ const RANGE_SEP_RE = /\s*(?:–|\bto\b|\btil\b|\s-\s)\s*/i;
17
+ function normalizeSpaces(s) {
18
+ return s.trim().replace(/\s+/g, ' ');
19
+ }
20
+ /**
21
+ * Special-case for digit-only ranges: 8 digits + '-' + 8 digits (optional spaces).
22
+ * This keeps legacy UX working without reintroducing the DD-MM-YYYY split bug.
23
+ */
24
+ function splitDigitOnlyRangeParts(input) {
25
+ const m = /^\s*(\d{8})\s*-\s*(\d{8})\s*$/.exec(input);
26
+ if (!m)
27
+ return null;
28
+ return [m[1], m[2]];
29
+ }
30
+ function splitRangeParts(input) {
31
+ var _a;
32
+ // 1) digit-only fast path: 01022026-02022026
33
+ const digitOnly = splitDigitOnlyRangeParts(input);
34
+ if (digitOnly)
35
+ return digitOnly;
36
+ // 2) normal path: use safe separators (–, to, til, " - ")
37
+ const txt = normalizeSpaces(input);
38
+ const parts = txt.split(RANGE_SEP_RE);
39
+ if (parts.length < 2)
40
+ return null;
41
+ const left = ((_a = parts[0]) !== null && _a !== void 0 ? _a : '').trim();
42
+ const right = parts.slice(1).join(' ').trim();
43
+ if (!left || !right)
44
+ return null;
45
+ return [left, right];
46
+ }
1
47
  /* ---------- Mask helpers (no deps) ---------- */
2
48
  // Pull only 0-9
3
49
  export const digits = (s) => (s.match(/\d/g) || []).join('');
@@ -23,7 +69,7 @@ export function maskTimeHM(text) {
23
69
  }
24
70
  // Single: "DD-MM-YYYY" or "DD-MM-YYYY HH:mm"
25
71
  export function maskSingle(text, enableTime) {
26
- let t = text.trim().replace(/\s+/g, ' ');
72
+ const t = normalizeSpaces(text);
27
73
  if (!enableTime)
28
74
  return maskDateEU(t);
29
75
  // split date + time by first space or 'T'
@@ -34,15 +80,15 @@ export function maskSingle(text, enableTime) {
34
80
  const timePart = maskTimeHM(m[2]);
35
81
  return timePart ? `${datePart} ${timePart}` : datePart;
36
82
  }
37
- // Range: mask both sides around common separators (–, -, to, til)
83
+ // Range: mask both sides around common separators (–, " - ", to, til, or digit-only "01022026-02022026")
38
84
  export function maskRange(text, _enableTime) {
39
85
  // NOTE: range is date-only in the UI
40
- const sepRe = /\s*(?:–|-|to|til)\s*/i;
41
- const parts = text.split(sepRe);
42
- if (parts.length === 1)
43
- return maskSingle(parts[0], false);
44
- const a = maskSingle(parts[0], false);
45
- const b = maskSingle(parts.slice(1).join(' '), false);
86
+ const parts = splitRangeParts(text);
87
+ if (!parts)
88
+ return maskSingle(text, false);
89
+ const [left, right] = parts;
90
+ const a = maskSingle(left, false);
91
+ const b = maskSingle(right, false);
46
92
  return `${a} – ${b}`.trim();
47
93
  }
48
94
  // Pad helper
@@ -50,20 +96,52 @@ export const pad2 = (n) => String(n).padStart(2, '0');
50
96
  export function isUtcIsoString(v) {
51
97
  return typeof v === 'string' && /Z$/.test(v) && !Number.isNaN(Date.parse(v));
52
98
  }
99
+ export function isDateOnlyString(v) {
100
+ if (typeof v !== 'string')
101
+ return false;
102
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
103
+ if (!m)
104
+ return false;
105
+ const y = +m[1];
106
+ const mo = +m[2];
107
+ const d = +m[3];
108
+ if (mo < 1 || mo > 12)
109
+ return false;
110
+ if (d < 1 || d > 31)
111
+ return false;
112
+ // Validate exact date (guard against 2026-02-31 etc.)
113
+ const local = new Date(y, mo - 1, d, 0, 0, 0, 0);
114
+ return local.getFullYear() === y && local.getMonth() === mo - 1 && local.getDate() === d;
115
+ }
53
116
  export function utcMillisFromIso(iso) {
54
117
  return Date.parse(iso);
55
118
  }
119
+ /** Convert a local DateTime to a UTC ISO string (Z). */
120
+ export function isoFromLocalDate(dLocal) {
121
+ return dLocal.toISOString();
122
+ }
56
123
  /**
57
124
  * Build a *local* Date from y/m/d/hh/mm and return UTC ISO string (Z).
58
125
  * This keeps the UI meaning "local wall time", while emitting a stable UTC instant.
126
+ *
127
+ * DST NOTE:
128
+ * If the requested local wall time does not exist (spring-forward), JS will auto-correct.
129
+ * We detect that and return null so caller can handle it (e.g., show validation error).
59
130
  */
60
131
  export function isoFromLocalParts(y, m0, // 0-based
61
132
  d, hh = 0, mm = 0) {
62
- return new Date(y, m0, d, hh, mm, 0, 0).toISOString();
63
- }
64
- /** Convert a local Date to a UTC ISO string (Z). */
65
- export function isoFromLocalDate(dLocal) {
66
- return dLocal.toISOString();
133
+ const local = new Date(y, m0, d, hh, mm, 0, 0);
134
+ if (Number.isNaN(local.getTime()))
135
+ return null;
136
+ // Guard: JS autocorrects invalid dates/times; re-validate exact parts
137
+ if (local.getFullYear() !== y ||
138
+ local.getMonth() !== m0 ||
139
+ local.getDate() !== d ||
140
+ local.getHours() !== hh ||
141
+ local.getMinutes() !== mm) {
142
+ return null;
143
+ }
144
+ return local.toISOString();
67
145
  }
68
146
  /** For anchoring the calendar safely from a UTC ISO string. */
69
147
  export function localDateFromIso(iso) {
@@ -72,6 +150,37 @@ export function localDateFromIso(iso) {
72
150
  return null;
73
151
  return new Date(ms); // local rendering
74
152
  }
153
+ /** Date-only: build 'YYYY-MM-DD' from local parts */
154
+ export function dateOnlyFromLocalParts(y, m0, d) {
155
+ return `${String(y).padStart(4, '0')}-${pad2(m0 + 1)}-${pad2(d)}`;
156
+ }
157
+ /** Parse 'YYYY-MM-DD' into a local Date at midnight */
158
+ export function localDateFromDateOnly(s) {
159
+ if (!isDateOnlyString(s))
160
+ return null;
161
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
162
+ if (!m)
163
+ return null;
164
+ const y = +m[1];
165
+ const mo0 = +m[2] - 1;
166
+ const d = +m[3];
167
+ const local = new Date(y, mo0, d, 0, 0, 0, 0);
168
+ if (Number.isNaN(local.getTime()))
169
+ return null;
170
+ if (local.getFullYear() !== y || local.getMonth() !== mo0 || local.getDate() !== d)
171
+ return null;
172
+ return local;
173
+ }
174
+ /** Compare date-only strings by UTC date-only time (stable ordering) */
175
+ export function compareDateOnly(a, b) {
176
+ const da = localDateFromDateOnly(a);
177
+ const db = localDateFromDateOnly(b);
178
+ if (!da || !db)
179
+ return 0;
180
+ const ta = Date.UTC(da.getFullYear(), da.getMonth(), da.getDate());
181
+ const tb = Date.UTC(db.getFullYear(), db.getMonth(), db.getDate());
182
+ return ta - tb;
183
+ }
75
184
  /* ---------- Formatting (UI shows local) ---------- */
76
185
  // From Date → "DD-MM-YYYY" or "DD-MM-YYYY HH:mm" (local time)
77
186
  export function toMaskedFromDate(d, enableTime) {
@@ -86,7 +195,7 @@ export function toMaskedFromDate(d, enableTime) {
86
195
  /* ---------- Parsing helpers (no deps) ---------- */
87
196
  // Accepts: YYYY-MM-DD, DD-MM-YYYY, DD/MM/YYYY, DD.MM.YYYY (+ optional HH:mm)
88
197
  export function parseLooseDateOrDateTime(input) {
89
- const txt = input.trim().replace(/\s+/g, ' ');
198
+ const txt = normalizeSpaces(input);
90
199
  const dateTimeMatch = /^(?<date>[\d./-]{8,10})(?:[ T](?<hh>\d{1,2}):(?<mm>\d{2}))?$/i.exec(txt);
91
200
  if (!(dateTimeMatch === null || dateTimeMatch === void 0 ? void 0 : dateTimeMatch.groups))
92
201
  return null;
@@ -95,7 +204,7 @@ export function parseLooseDateOrDateTime(input) {
95
204
  const mm = dateTimeMatch.groups.mm ? parseInt(dateTimeMatch.groups.mm, 10) : 0;
96
205
  if (hh < 0 || hh > 23 || mm < 0 || mm > 59)
97
206
  return null;
98
- // Try YYYY-MM-DD first
207
+ // Try YYYY-MM-DD first (still accepts 1-2 digits for month/day in ISO form)
99
208
  let y, m, d;
100
209
  const mIso = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(raw);
101
210
  if (mIso) {
@@ -104,8 +213,8 @@ export function parseLooseDateOrDateTime(input) {
104
213
  d = +mIso[3];
105
214
  }
106
215
  else {
107
- // Try DD-MM-YYYY or DD/MM/YYYY or DD.MM.YYYY
108
- const mEu = /^(\d{1,2})[./-](\d{1,2})[./-](\d{4})$/.exec(raw);
216
+ // EU formats must be strict DD-MM-YYYY / DD/MM/YYYY / DD.MM.YYYY
217
+ const mEu = /^(\d{2})[./-](\d{2})[./-](\d{4})$/.exec(raw);
109
218
  if (!mEu)
110
219
  return null;
111
220
  d = +mEu[1];
@@ -115,17 +224,22 @@ export function parseLooseDateOrDateTime(input) {
115
224
  const local = new Date(y, m, d, hh, mm, 0, 0);
116
225
  if (Number.isNaN(local.getTime()))
117
226
  return null;
118
- // Guard: JS autocorrects invalid dates; re-validate exact Y/M/D
119
- if (local.getFullYear() !== y || local.getMonth() !== m || local.getDate() !== d)
227
+ // Guard: JS autocorrects invalid dates; re-validate exact Y/M/D/H/M
228
+ if (local.getFullYear() !== y ||
229
+ local.getMonth() !== m ||
230
+ local.getDate() !== d ||
231
+ local.getHours() !== hh ||
232
+ local.getMinutes() !== mm) {
120
233
  return null;
234
+ }
121
235
  return local;
122
236
  }
123
- // Parse a range string with separators: "–", "-", "to", "til"
237
+ // Parse a range string with separators: "–", " - ", "to", "til", or digit-only "01022026-02022026"
124
238
  export function parseLooseRange(input) {
125
- const sep = /\s*(?:–|-|to|til)\s*/i;
126
- const [a, b] = input.split(sep);
127
- if (!a || !b)
239
+ const parts = splitRangeParts(input);
240
+ if (!parts)
128
241
  return null;
242
+ const [a, b] = parts;
129
243
  const s = parseLooseDateOrDateTime(a);
130
244
  const e = parseLooseDateOrDateTime(b);
131
245
  if (!s || !e)