@dbcdk/react-components 0.0.9 → 0.0.12

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 (113) hide show
  1. package/dist/components/accordion/Accordion.d.ts +27 -0
  2. package/dist/components/accordion/Accordion.js +66 -0
  3. package/dist/components/accordion/Accordion.module.css +87 -0
  4. package/dist/components/button/Button.module.css +1 -0
  5. package/dist/components/card/Card.d.ts +21 -3
  6. package/dist/components/card/Card.js +17 -2
  7. package/dist/components/card/Card.module.css +59 -0
  8. package/dist/components/circle/Circle.d.ts +5 -1
  9. package/dist/components/circle/Circle.js +2 -2
  10. package/dist/components/circle/Circle.module.css +60 -4
  11. package/dist/components/code-block/CodeBlock.js +1 -1
  12. package/dist/components/code-block/CodeBlock.module.css +30 -17
  13. package/dist/components/copy-button/CopyButton.d.ts +1 -0
  14. package/dist/components/copy-button/CopyButton.js +10 -2
  15. package/dist/components/datetime-picker/DateTimePicker.d.ts +4 -8
  16. package/dist/components/datetime-picker/DateTimePicker.js +72 -92
  17. package/dist/components/datetime-picker/dateTimeHelpers.d.ts +14 -12
  18. package/dist/components/datetime-picker/dateTimeHelpers.js +25 -45
  19. package/dist/components/filter-field/FilterField.js +16 -11
  20. package/dist/components/filter-field/FilterField.module.css +133 -12
  21. package/dist/components/forms/checkbox/Checkbox.d.ts +4 -10
  22. package/dist/components/forms/checkbox/Checkbox.js +3 -5
  23. package/dist/components/forms/checkbox-group/CheckboxGroup.js +1 -1
  24. package/dist/components/forms/checkbox-group/CheckboxGroup.module.css +1 -1
  25. package/dist/components/forms/input/Input.d.ts +1 -0
  26. package/dist/components/forms/input/Input.js +2 -4
  27. package/dist/components/forms/input/Input.module.css +10 -11
  28. package/dist/components/forms/input-container/InputContainer.d.ts +2 -1
  29. package/dist/components/forms/input-container/InputContainer.js +3 -3
  30. package/dist/components/forms/input-container/InputContainer.module.css +65 -0
  31. package/dist/components/forms/radio-buttons/RadioButton.d.ts +36 -0
  32. package/dist/components/forms/radio-buttons/RadioButton.js +26 -0
  33. package/dist/components/forms/radio-buttons/RadioButtonGroup.d.ts +25 -0
  34. package/dist/components/forms/radio-buttons/RadioButtonGroup.js +19 -0
  35. package/dist/components/forms/radio-buttons/RadioButtons.module.css +117 -0
  36. package/dist/components/forms/select/Select.d.ts +1 -1
  37. package/dist/components/forms/select/Select.js +3 -3
  38. package/dist/components/forms/text-area/Textarea.js +3 -3
  39. package/dist/components/forms/text-area/Textarea.module.css +8 -1
  40. package/dist/components/headline/Headline.d.ts +2 -7
  41. package/dist/components/headline/Headline.js +5 -2
  42. package/dist/components/headline/Headline.module.css +61 -2
  43. package/dist/components/hyperlink/Hyperlink.d.ts +19 -6
  44. package/dist/components/hyperlink/Hyperlink.js +35 -7
  45. package/dist/components/hyperlink/Hyperlink.module.css +50 -2
  46. package/dist/components/icon/Icon.module.css +1 -0
  47. package/dist/components/interval-select/IntervalSelect.js +1 -1
  48. package/dist/components/menu/Menu.d.ts +32 -0
  49. package/dist/components/menu/Menu.js +73 -13
  50. package/dist/components/menu/Menu.module.css +72 -4
  51. package/dist/components/nav-bar/NavBar.d.ts +24 -6
  52. package/dist/components/overlay/modal/Modal.module.css +2 -2
  53. package/dist/components/overlay/side-panel/SidePanel.d.ts +12 -4
  54. package/dist/components/overlay/side-panel/SidePanel.js +77 -4
  55. package/dist/components/overlay/side-panel/SidePanel.module.css +149 -28
  56. package/dist/components/overlay/side-panel/useSidePanel.d.ts +1 -1
  57. package/dist/components/overlay/side-panel/useSidePanel.js +2 -2
  58. package/dist/components/overlay/tooltip/useTooltipTrigger.js +4 -2
  59. package/dist/components/page-layout/PageLayout.js +0 -2
  60. package/dist/components/popover/Popover.js +1 -1
  61. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +5 -5
  62. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +36 -24
  63. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +0 -3
  64. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.d.ts +3 -1
  65. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +4 -3
  66. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +109 -79
  67. package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +16 -3
  68. package/dist/components/sidebar/components/sidebar-items/SidebarItems.module.css +20 -0
  69. package/dist/components/sidebar/providers/SidebarProvider.d.ts +4 -1
  70. package/dist/components/sidebar/providers/SidebarProvider.js +85 -58
  71. package/dist/components/skeleton-loader/SkeletonLoader.d.ts +1 -1
  72. package/dist/components/skeleton-loader/SkeletonLoader.js +15 -12
  73. package/dist/components/split-button/SplitButton.d.ts +1 -1
  74. package/dist/components/split-button/SplitButton.js +3 -1
  75. package/dist/components/split-button/SplitButton.module.css +4 -4
  76. package/dist/components/state-page/StatePage.d.ts +9 -0
  77. package/dist/components/state-page/StatePage.js +20 -0
  78. package/dist/components/state-page/StatePage.module.css +9 -0
  79. package/dist/components/state-page/empty.d.ts +2 -0
  80. package/dist/components/state-page/empty.js +2 -0
  81. package/dist/components/state-page/error.d.ts +2 -0
  82. package/dist/components/state-page/error.js +2 -0
  83. package/dist/components/state-page/notFound.d.ts +2 -0
  84. package/dist/components/state-page/notFound.js +2 -0
  85. package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +19 -0
  86. package/dist/components/sticky-footer-layout/StickyFooterLayout.js +27 -0
  87. package/dist/components/table/Table.d.ts +9 -4
  88. package/dist/components/table/Table.js +6 -9
  89. package/dist/components/table/Table.module.css +180 -59
  90. package/dist/components/table/components/empty-state/EmptyState.d.ts +1 -1
  91. package/dist/components/table/components/empty-state/EmptyState.js +6 -7
  92. package/dist/components/table/components/table-settings/TableSettings.d.ts +13 -3
  93. package/dist/components/table/components/table-settings/TableSettings.js +55 -4
  94. package/dist/components/table/tanstack.d.ts +12 -1
  95. package/dist/components/table/tanstack.js +75 -23
  96. package/dist/components/toast/Toast.js +5 -1
  97. package/dist/components/toast/Toast.module.css +40 -15
  98. package/dist/components/toast/provider/ToastProvider.js +1 -0
  99. package/dist/hooks/useTableSettings.d.ts +23 -4
  100. package/dist/hooks/useTableSettings.js +64 -17
  101. package/dist/hooks/useTimeDuration.js +9 -3
  102. package/dist/hooks/useViewportFill.js +1 -0
  103. package/dist/index.d.ts +6 -0
  104. package/dist/index.js +6 -1
  105. package/dist/src/styles/styles.css +60 -25
  106. package/dist/styles/animation.d.ts +5 -0
  107. package/dist/styles/animation.js +5 -0
  108. package/dist/styles/styles.css +60 -25
  109. package/dist/styles/themes/dbc/dark.css +1 -1
  110. package/dist/styles/themes/dbc/light.css +2 -1
  111. package/dist/utils/localStorage.utils.d.ts +19 -0
  112. package/dist/utils/localStorage.utils.js +78 -0
  113. package/package.json +1 -1
@@ -5,9 +5,9 @@ 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 { localDateFromYMD, maskRange, maskSingle, pad2, parseLooseDateOrDateTime, parseLooseRange, toMaskedFromDate, toMaskedFromYMD, utcMillisFromYMD, ymdFromLocalDate, ymdFromUTCDateOnly, } from './dateTimeHelpers';
8
+ import { isoFromLocalDate, isoFromLocalParts, localDateFromIso, maskRange, maskSingle, parseLooseDateOrDateTime, parseLooseRange, utcMillisFromIso, toMaskedFromDate, } from './dateTimeHelpers';
9
9
  import styles from './DateTimePicker.module.css';
10
- /* ---------- Date grid helpers (UTC) ---------- */
10
+ /* ---------- Date grid helpers (UTC date-only cells) ---------- */
11
11
  const dUTC = (y, m, day) => new Date(Date.UTC(y, m, day));
12
12
  const addDaysUTC = (utcDate, n) => dUTC(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate() + n);
13
13
  const startOfMonthUTC = (utcDate) => dUTC(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), 1);
@@ -15,14 +15,16 @@ 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
- const toUTCDateOnly = (local) => dUTC(local.getFullYear(), local.getMonth(), local.getDate());
18
+ // IMPORTANT: treat local calendar date as the source of truth for the grid.
19
+ // Build an equivalent "UTC date-only" for comparisons.
20
+ const toUTCDateOnlyFromLocal = (local) => dUTC(local.getFullYear(), local.getMonth(), local.getDate());
19
21
  const startOfWeekUTC = (utcDate, weekStartsOn) => {
20
22
  const dow = utcDate.getUTCDay();
21
23
  const diff = (dow - weekStartsOn + 7) % 7;
22
24
  return addDaysUTC(utcDate, -diff);
23
25
  };
24
26
  function buildMonthGrid(anchorLocalDate, weekStartsOn) {
25
- const anchorUTC = toUTCDateOnly(anchorLocalDate);
27
+ const anchorUTC = toUTCDateOnlyFromLocal(anchorLocalDate);
26
28
  const firstUTC = startOfWeekUTC(startOfMonthUTC(anchorUTC), weekStartsOn);
27
29
  const cells = [];
28
30
  for (let i = 0; i < 42; i++)
@@ -55,77 +57,72 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
55
57
  void formatRange;
56
58
  const popRef = useRef(null);
57
59
  const todayLocal = useMemo(() => new Date(), []);
58
- // ---- Derive a local anchor from the controlled value ----
60
+ // ---- local anchor from controlled value ----
59
61
  const initialAnchor = useMemo(() => {
60
62
  var _a, _b;
61
63
  if (mode === 'single') {
62
- if (enableTime && typeof value === 'number')
63
- return new Date(value); // local rendering
64
- if (!enableTime && typeof value === 'string')
65
- return (_a = localDateFromYMD(value)) !== null && _a !== void 0 ? _a : todayLocal;
64
+ if (typeof value === 'string')
65
+ return (_a = localDateFromIso(value)) !== null && _a !== void 0 ? _a : todayLocal;
66
66
  return todayLocal;
67
67
  }
68
68
  if (mode === 'range' && value && typeof value === 'object' && 'start' in value && value.start) {
69
- return (_b = localDateFromYMD(value.start)) !== null && _b !== void 0 ? _b : todayLocal;
69
+ return (_b = localDateFromIso(value.start)) !== null && _b !== void 0 ? _b : todayLocal;
70
70
  }
71
71
  return todayLocal;
72
- }, [mode, value, enableTime, todayLocal]);
72
+ }, [mode, value, todayLocal]);
73
73
  const [monthAnchor, setMonthAnchor] = useState(initialAnchor);
74
- // Keep month anchor in sync when external value changes (but don’t fight while typing)
75
- useEffect(() => {
76
- setMonthAnchor(initialAnchor);
77
- }, [initialAnchor]);
74
+ useEffect(() => setMonthAnchor(initialAnchor), [initialAnchor]);
75
+ // time defaults (local)
78
76
  const [timeHH, setTimeHH] = useState(todayLocal.getHours());
79
77
  const [timeMM, setTimeMM] = useState(Math.floor(todayLocal.getMinutes() / timeStep) * timeStep);
80
- // If value is a datetime and changes externally, keep dropdowns in sync
78
+ // If datetime value changes externally, keep HH/MM in sync
81
79
  useEffect(() => {
82
- if (mode === 'single' && enableTime && typeof value === 'number') {
83
- const d = new Date(value);
80
+ if (mode === 'single' && enableTime && typeof value === 'string') {
81
+ const d = localDateFromIso(value);
82
+ if (!d)
83
+ return;
84
84
  setTimeHH(d.getHours());
85
85
  setTimeMM(Math.floor(d.getMinutes() / timeStep) * timeStep);
86
86
  }
87
87
  }, [mode, enableTime, value, timeStep]);
88
88
  const [hoverUTC, setHoverUTC] = useState(null);
89
89
  const cellsUTC = useMemo(() => buildMonthGrid(monthAnchor, weekStartsOn), [monthAnchor, weekStartsOn]);
90
- const monthStartUTC = useMemo(() => startOfMonthUTC(toUTCDateOnly(monthAnchor)), [monthAnchor]);
91
- const monthEndUTC = useMemo(() => endOfMonthUTC(toUTCDateOnly(monthAnchor)), [monthAnchor]);
90
+ const monthStartUTC = useMemo(() => startOfMonthUTC(toUTCDateOnlyFromLocal(monthAnchor)), [monthAnchor]);
91
+ const monthEndUTC = useMemo(() => endOfMonthUTC(toUTCDateOnlyFromLocal(monthAnchor)), [monthAnchor]);
92
92
  const weekdayFmt = useMemo(() => new Intl.DateTimeFormat(locale, { weekday: 'short' }), [locale]);
93
93
  const monthFmt = useMemo(() => new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }), [locale]);
94
- // ---- Selection state for the grid (always compared in UTC date-only space) ----
94
+ // ---- selection state for grid (compare in UTC date-only) ----
95
95
  const selectedUTC_single = useMemo(() => {
96
- if (mode !== 'single' || !value)
96
+ if (mode !== 'single')
97
97
  return null;
98
- if (enableTime) {
99
- if (typeof value !== 'number')
100
- return null;
101
- return toUTCDateOnly(new Date(value));
102
- }
103
98
  if (typeof value !== 'string')
104
99
  return null;
105
- const local = localDateFromYMD(value);
106
- return local ? toUTCDateOnly(local) : null;
107
- }, [mode, value, enableTime]);
100
+ const d = localDateFromIso(value);
101
+ if (!d)
102
+ return null;
103
+ return toUTCDateOnlyFromLocal(d);
104
+ }, [mode, value]);
108
105
  const selectedUTC_start = useMemo(() => {
109
106
  if (mode !== 'range' || !value || typeof value !== 'object' || !('start' in value))
110
107
  return null;
111
108
  if (!value.start)
112
109
  return null;
113
- const local = localDateFromYMD(value.start);
114
- return local ? toUTCDateOnly(local) : null;
110
+ const d = localDateFromIso(value.start);
111
+ return d ? toUTCDateOnlyFromLocal(d) : null;
115
112
  }, [mode, value]);
116
113
  const selectedUTC_end = useMemo(() => {
117
114
  if (mode !== 'range' || !value || typeof value !== 'object' || !('end' in value))
118
115
  return null;
119
116
  if (!value.end)
120
117
  return null;
121
- const local = localDateFromYMD(value.end);
122
- return local ? toUTCDateOnly(local) : null;
118
+ const d = localDateFromIso(value.end);
119
+ return d ? toUTCDateOnlyFromLocal(d) : null;
123
120
  }, [mode, value]);
124
121
  const isDisabledUTC = useCallback((utcDay) => {
125
- // min/max are Dates (instants). Treat them as local-day constraints for UI.
126
- if (min && utcDay < toUTCDateOnly(min))
122
+ // Interpret min/max as local-day constraints for UI
123
+ if (min && utcDay < toUTCDateOnlyFromLocal(min))
127
124
  return true;
128
- if (max && utcDay > toUTCDateOnly(max))
125
+ if (max && utcDay > toUTCDateOnlyFromLocal(max))
129
126
  return true;
130
127
  return false;
131
128
  }, [min, max]);
@@ -133,33 +130,29 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
133
130
  var _a, _b;
134
131
  if (isDisabledUTC(utcDay))
135
132
  return;
133
+ // utcDay's UTC Y/M/D corresponds to the *local calendar day* shown in UI.
134
+ const y = utcDay.getUTCFullYear();
135
+ const m0 = utcDay.getUTCMonth();
136
+ const d = utcDay.getUTCDate();
136
137
  if (mode === 'single') {
137
- if (enableTime) {
138
- // User picked a local wall time; emit UTC instant as millis
139
- const y = utcDay.getUTCFullYear();
140
- const m = utcDay.getUTCMonth();
141
- const d = utcDay.getUTCDate();
142
- const local = new Date(y, m, d, timeHH, timeMM, 0, 0);
143
- onChange(local.getTime());
144
- }
145
- else {
146
- // Date-only: emit timezone-free day label
147
- onChange(ymdFromUTCDateOnly(utcDay));
148
- }
138
+ const iso = enableTime
139
+ ? isoFromLocalParts(y, m0, d, timeHH, timeMM)
140
+ : isoFromLocalParts(y, m0, d, 0, 0);
141
+ onChange(iso);
149
142
  (_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close();
150
143
  return;
151
144
  }
152
- // RANGE: date-only
145
+ // range (date-only in UI, but emitted as ISO instants at local midnight)
153
146
  const curr = value && typeof value === 'object' && 'start' in value
154
147
  ? value
155
148
  : { start: null, end: null };
156
- const picked = ymdFromUTCDateOnly(utcDay);
149
+ const picked = isoFromLocalParts(y, m0, d, 0, 0);
157
150
  if (!curr.start || (curr.start && curr.end)) {
158
151
  onChange({ start: picked, end: null });
159
152
  return;
160
153
  }
161
- const a = utcMillisFromYMD(curr.start);
162
- const b = utcMillisFromYMD(picked);
154
+ const a = utcMillisFromIso(curr.start);
155
+ const b = utcMillisFromIso(picked);
163
156
  const start = a <= b ? curr.start : picked;
164
157
  const end = a <= b ? picked : curr.start;
165
158
  onChange({ start, end });
@@ -186,7 +179,7 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
186
179
  ].includes(e.key)) {
187
180
  e.preventDefault();
188
181
  }
189
- const anchor = toUTCDateOnly(monthAnchor);
182
+ const anchor = toUTCDateOnlyFromLocal(monthAnchor);
190
183
  const move = (days) => setMonthAnchor(prev => addDaysLocal(prev, days));
191
184
  switch (e.key) {
192
185
  case 'ArrowLeft':
@@ -223,31 +216,26 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
223
216
  // ---- Input display: always local ----
224
217
  const formatted = useMemo(() => {
225
218
  if (mode === 'single') {
226
- if (!value)
227
- return '';
228
- if (enableTime) {
229
- if (typeof value !== 'number')
230
- return '';
231
- return toMaskedFromDate(new Date(value), true);
232
- }
233
219
  if (typeof value !== 'string')
234
220
  return '';
235
- return toMaskedFromYMD(value);
221
+ const d = localDateFromIso(value);
222
+ return d ? toMaskedFromDate(d, enableTime) : '';
236
223
  }
237
- // range (date-only)
238
224
  const v = value;
239
- const s = typeof (v === null || v === void 0 ? void 0 : v.start) === 'string' ? toMaskedFromYMD(v.start) : '';
240
- const e = typeof (v === null || v === void 0 ? void 0 : v.end) === 'string' ? toMaskedFromYMD(v.end) : '';
241
- if (s && e)
242
- return `${s} ${e}`;
243
- if (s)
244
- return `${s} –`;
245
- if (e)
246
- return `– ${e}`;
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;
227
+ const ss = s ? toMaskedFromDate(s, false) : '';
228
+ const ee = e ? toMaskedFromDate(e, false) : '';
229
+ if (ss && ee)
230
+ return `${ss} – ${ee}`;
231
+ if (ss)
232
+ return `${ss} –`;
233
+ if (ee)
234
+ return `– ${ee}`;
247
235
  return '';
248
236
  }, [mode, value, enableTime]);
249
237
  const [text, setText] = useState(formatted);
250
- const [dirty, setDirty] = useState(false); // while user is typing
238
+ const [dirty, setDirty] = useState(false);
251
239
  useEffect(() => {
252
240
  if (!dirty)
253
241
  setText(formatted);
@@ -265,28 +253,21 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
265
253
  const dLocal = parseLooseDateOrDateTime(text);
266
254
  if (!dLocal)
267
255
  return;
268
- if (enableTime) {
269
- // Emit UTC instant millis
270
- onChange(dLocal.getTime());
271
- }
272
- else {
273
- // Emit date-only string (local calendar day)
274
- onChange(ymdFromLocalDate(dLocal));
275
- }
256
+ // If enableTime=false, parseLooseDateOrDateTime returns 00:00 local -> still OK.
257
+ onChange(isoFromLocalDate(dLocal));
276
258
  setMonthAnchor(dLocal);
277
259
  setDirty(false);
278
260
  return;
279
261
  }
280
262
  const r = parseLooseRange(text);
281
263
  if (r) {
282
- // Range is date-only, based on local calendar parts
283
- const start = `${r.start.getFullYear()}-${pad2(r.start.getMonth() + 1)}-${pad2(r.start.getDate())}`;
284
- const end = `${r.end.getFullYear()}-${pad2(r.end.getMonth() + 1)}-${pad2(r.end.getDate())}`;
285
- onChange({ start, end });
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 });
286
267
  setMonthAnchor(r.start);
287
268
  setDirty(false);
288
269
  }
289
- }, [text, mode, onChange, enableTime]);
270
+ }, [text, mode, onChange]);
290
271
  const clear = useCallback(() => {
291
272
  if (mode === 'single')
292
273
  onChange(null);
@@ -308,7 +289,7 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
308
289
  const fallbackPlaceholder = mode === 'single' ? 'Vælg dato' : 'Vælg interval';
309
290
  return (_jsx(Popover, { ref: popRef, trigger: toggle => {
310
291
  var _a, _b;
311
- return (_jsx("div", { onClick: toggle, className: styles.triggerWrap, children: _jsx(Input, { ...inputProps, placeholder: (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.placeholder) !== null && _a !== void 0 ? _a : fallbackPlaceholder, value: dirty ? text : formatted, onInput: e => {
292
+ 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 => {
312
293
  setDirty(true);
313
294
  const raw = e.target.value;
314
295
  const masked = mode === 'single' ? maskSingle(raw, enableTime) : maskRange(raw, false);
@@ -326,17 +307,16 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
326
307
  }, 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: () => {
327
308
  var _a;
328
309
  const r = p.getRange();
329
- // Presets -> date-only range
330
- const start = `${r.start.getFullYear()}-${pad2(r.start.getMonth() + 1)}-${pad2(r.start.getDate())}`;
331
- const end = `${r.end.getFullYear()}-${pad2(r.end.getMonth() + 1)}-${pad2(r.end.getDate())}`;
332
- onChange({ start, end });
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 });
333
313
  setDirty(false);
334
- setText(`${toMaskedFromYMD(start)} – ${toMaskedFromYMD(end)}`);
314
+ setText(`${toMaskedFromDate(r.start, false)} – ${toMaskedFromDate(r.end, false)}`);
335
315
  setMonthAnchor(r.start);
336
316
  (_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close();
337
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) => {
338
318
  const inThisMonth = utcDay >= monthStartUTC && utcDay <= monthEndUTC;
339
- const isToday = sameDayUTC(utcDay, toUTCDateOnly(todayLocal));
319
+ const isToday = sameDayUTC(utcDay, toUTCDateOnlyFromLocal(todayLocal));
340
320
  const disabledDay = isDisabledUTC(utcDay);
341
321
  let selected = false;
342
322
  let inRange = false;
@@ -2,20 +2,22 @@ export declare const digits: (s: string) => string;
2
2
  export declare function maskDateEU(text: string): string;
3
3
  export declare function maskTimeHM(text: string): string;
4
4
  export declare function maskSingle(text: string, enableTime: boolean): string;
5
- export declare function maskRange(text: string, enableTime: boolean): string;
5
+ export declare function maskRange(text: string, _enableTime: boolean): string;
6
6
  export declare const pad2: (n: number) => string;
7
- export type DateOnly = string;
8
- export declare function parseYMD(ymd: string): {
9
- y: number;
10
- m: number;
11
- d: number;
12
- } | null;
13
- export declare function ymdFromLocalDate(dLocal: Date): DateOnly;
14
- export declare function ymdFromUTCDateOnly(utcDay: Date): DateOnly;
15
- export declare function utcMillisFromYMD(ymd: DateOnly): number;
16
- export declare function localDateFromYMD(ymd: DateOnly): Date | null;
7
+ export type UtcIsoString = string;
8
+ export declare function isUtcIsoString(v: unknown): v is UtcIsoString;
9
+ export declare function utcMillisFromIso(iso: UtcIsoString): number;
10
+ /**
11
+ * Build a *local* Date from y/m/d/hh/mm and return UTC ISO string (Z).
12
+ * This keeps the UI meaning "local wall time", while emitting a stable UTC instant.
13
+ */
14
+ 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;
18
+ /** For anchoring the calendar safely from a UTC ISO string. */
19
+ export declare function localDateFromIso(iso: UtcIsoString): Date | null;
17
20
  export declare function toMaskedFromDate(d: Date, enableTime: boolean): string;
18
- export declare function toMaskedFromYMD(ymd: DateOnly): string;
19
21
  export declare function parseLooseDateOrDateTime(input: string): Date | null;
20
22
  export declare function parseLooseRange(input: string): {
21
23
  start: Date;
@@ -35,56 +35,42 @@ export function maskSingle(text, enableTime) {
35
35
  return timePart ? `${datePart} ${timePart}` : datePart;
36
36
  }
37
37
  // Range: mask both sides around common separators (–, -, to, til)
38
- export function maskRange(text, enableTime) {
38
+ export function maskRange(text, _enableTime) {
39
+ // NOTE: range is date-only in the UI
39
40
  const sepRe = /\s*(?:–|-|to|til)\s*/i;
40
41
  const parts = text.split(sepRe);
41
- if (parts.length === 1) {
42
- // user typing first side
43
- return maskSingle(parts[0], enableTime);
44
- }
45
- const a = maskSingle(parts[0], enableTime);
46
- const b = maskSingle(parts.slice(1).join(' '), enableTime); // everything after first sep
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);
47
46
  return `${a} – ${b}`.trim();
48
47
  }
49
48
  // Pad helper
50
49
  export const pad2 = (n) => String(n).padStart(2, '0');
51
- export function parseYMD(ymd) {
52
- const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd);
53
- if (!m)
54
- return null;
55
- const y = +m[1];
56
- const mo = +m[2];
57
- const d = +m[3];
58
- if (mo < 1 || mo > 12 || d < 1 || d > 31)
59
- return null;
60
- return { y, m: mo, d };
50
+ export function isUtcIsoString(v) {
51
+ return typeof v === 'string' && /Z$/.test(v) && !Number.isNaN(Date.parse(v));
61
52
  }
62
- export function ymdFromLocalDate(dLocal) {
63
- const y = dLocal.getFullYear();
64
- const m = dLocal.getMonth() + 1;
65
- const d = dLocal.getDate();
66
- return `${y}-${pad2(m)}-${pad2(d)}`;
53
+ export function utcMillisFromIso(iso) {
54
+ return Date.parse(iso);
67
55
  }
68
- export function ymdFromUTCDateOnly(utcDay) {
69
- const y = utcDay.getUTCFullYear();
70
- const m = utcDay.getUTCMonth() + 1;
71
- const d = utcDay.getUTCDate();
72
- return `${y}-${pad2(m)}-${pad2(d)}`;
56
+ /**
57
+ * Build a *local* Date from y/m/d/hh/mm and return UTC ISO string (Z).
58
+ * This keeps the UI meaning "local wall time", while emitting a stable UTC instant.
59
+ */
60
+ export function isoFromLocalParts(y, m0, // 0-based
61
+ d, hh = 0, mm = 0) {
62
+ return new Date(y, m0, d, hh, mm, 0, 0).toISOString();
73
63
  }
74
- // Used only for ordering/comparing date-only values.
75
- export function utcMillisFromYMD(ymd) {
76
- const p = parseYMD(ymd);
77
- if (!p)
78
- return NaN;
79
- return Date.UTC(p.y, p.m - 1, p.d);
64
+ /** Convert a local Date to a UTC ISO string (Z). */
65
+ export function isoFromLocalDate(dLocal) {
66
+ return dLocal.toISOString();
80
67
  }
81
- // For anchoring the calendar grid safely from a date-only value.
82
- export function localDateFromYMD(ymd) {
83
- const p = parseYMD(ymd);
84
- if (!p)
68
+ /** For anchoring the calendar safely from a UTC ISO string. */
69
+ export function localDateFromIso(iso) {
70
+ const ms = Date.parse(iso);
71
+ if (Number.isNaN(ms))
85
72
  return null;
86
- // local noon avoids DST edge cases for date-only anchors
87
- return new Date(p.y, p.m - 1, p.d, 12, 0, 0, 0);
73
+ return new Date(ms); // local rendering
88
74
  }
89
75
  /* ---------- Formatting (UI shows local) ---------- */
90
76
  // From Date → "DD-MM-YYYY" or "DD-MM-YYYY HH:mm" (local time)
@@ -97,12 +83,6 @@ export function toMaskedFromDate(d, enableTime) {
97
83
  out += ` ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
98
84
  return out;
99
85
  }
100
- export function toMaskedFromYMD(ymd) {
101
- const p = parseYMD(ymd);
102
- if (!p)
103
- return '';
104
- return `${pad2(p.d)}-${pad2(p.m)}-${p.y}`;
105
- }
106
86
  /* ---------- Parsing helpers (no deps) ---------- */
107
87
  // Accepts: YYYY-MM-DD, DD-MM-YYYY, DD/MM/YYYY, DD.MM.YYYY (+ optional HH:mm)
108
88
  export function parseLooseDateOrDateTime(input) {
@@ -61,10 +61,18 @@ function OperatorDropdown({ value, onChange, operators, size = 'sm', disabled, }
61
61
  return (_jsx(Menu.Item, { active: selected, children: _jsxs("button", { type: "button", onClick: () => handleSelect(op), disabled: disabled, children: [_jsx("span", { style: { width: 16, display: 'inline-flex', justifyContent: 'center' }, children: selected ? _jsx(Check, { size: 16 }) : null }), LABELS[op]] }) }, op));
62
62
  }) }) }));
63
63
  }
64
+ function isFilterActive(value) {
65
+ if (Array.isArray(value))
66
+ return value.length > 0;
67
+ if (typeof value === 'string')
68
+ return value.trim().length > 0;
69
+ return value != null;
70
+ }
64
71
  export function FilterField({ field, control, operator, value, onChange, operators, options = [], single = true, size = 'md', label, placeholder = 'Type value…', disabled, 'data-cy': dataCy, ...inputProps }) {
65
72
  var _a, _b;
66
73
  const [selectedOperator, setSelectedOperator] = useState(operator);
67
74
  const ops = useMemo(() => operators !== null && operators !== void 0 ? operators : DEFAULT_TEXT_OPERATORS, [operators]);
75
+ const active = isFilterActive(value);
68
76
  // Local state ONLY for input control (to avoid URL->props lag)
69
77
  const [localValue, setLocalValue] = useState((_a = value) !== null && _a !== void 0 ? _a : '');
70
78
  const debounceRef = useRef(null);
@@ -92,7 +100,7 @@ export function FilterField({ field, control, operator, value, onChange, operato
92
100
  debounceRef.current = setTimeout(() => {
93
101
  isTypingRef.current = false;
94
102
  emit({ value: nextVal });
95
- }, 250); // tweak debounce as needed
103
+ }, 250);
96
104
  };
97
105
  // Sync internal value when parent value changes (e.g. URL updates)
98
106
  useEffect(() => {
@@ -100,7 +108,6 @@ export function FilterField({ field, control, operator, value, onChange, operato
100
108
  if (control !== 'input')
101
109
  return;
102
110
  const incoming = (_a = value) !== null && _a !== void 0 ? _a : '';
103
- // don't fight the user mid-typing; once parent catches up, we allow sync again
104
111
  if (!isTypingRef.current && incoming !== localValue) {
105
112
  setLocalValue(incoming);
106
113
  }
@@ -115,25 +122,23 @@ export function FilterField({ field, control, operator, value, onChange, operato
115
122
  clearTimeout(debounceRef.current);
116
123
  };
117
124
  }, []);
118
- return (_jsxs("div", { ...(dataCy ? { 'data-cy': dataCy } : {}), className: ` ${styles.filterField} ${styles[size]} ${value ? styles.active : ''}`, children: [label ? _jsx("span", { className: `${styles.label} ${styles[size]}`, children: label }) : null, _jsx(OperatorDropdown, { value: selectedOperator, onChange: op => emit({ operator: op }), operators: ops, size: size, disabled: disabled }), _jsx("div", { className: `${control === 'input' ? 'dbc-flex' : styles.valueWrapper}`, children: control === 'input' ? (_jsx(Input, { ...inputProps, value: localValue, onChange: e => {
125
+ return (_jsxs("div", { ...(dataCy ? { 'data-cy': dataCy } : {}), className: `${styles.filterField} ${styles[size]} ${active ? styles.active : ''}`, children: [label ? _jsx("span", { className: `${styles.label} ${styles[size]}`, children: label }) : null, _jsx(OperatorDropdown, { value: selectedOperator, onChange: op => emit({ operator: op }), operators: ops, size: size, disabled: disabled }), _jsx("div", { className: `${control === 'input' ? 'dbc-flex dbc-flex-grow' : styles.valueWrapper}`, children: control === 'input' ? (_jsx(Input, { ...inputProps, value: localValue, onChange: e => {
119
126
  const next = e.currentTarget.value;
120
127
  isTypingRef.current = true;
121
- setLocalValue(next); // immediate UI update
122
- scheduleEmitValue(next); // debounced parent update
123
- }, inputSize: size, placeholder: placeholder, width: "160px", minWidth: "120px", disabled: disabled, onClear: () => {
128
+ setLocalValue(next);
129
+ scheduleEmitValue(next);
130
+ }, fullWidth: true, inputSize: size, placeholder: placeholder, width: "160px", minWidth: "120px", disabled: disabled, onClear: () => {
124
131
  isTypingRef.current = false;
125
132
  if (debounceRef.current)
126
133
  clearTimeout(debounceRef.current);
127
134
  setLocalValue('');
128
- emit({ value: '' }); // clear should be immediate
135
+ emit({ value: '' });
129
136
  } })) : single ? (_jsx(Select, { options: options, selectedValue: (_b = value) !== null && _b !== void 0 ? _b : null, onChange: v => emit({ value: v }), placeholder: placeholder, size: size, variant: "inline", onClear: () => emit({ value: '' }) })) : (_jsx(MultiSelect, { options: options, size: size, variant: "inline", selectedValues: (Array.isArray(value) ? value : []), onChange: v => {
130
137
  const current = new Set((Array.isArray(value) ? value : []));
131
- if (current.has(v)) {
138
+ if (current.has(v))
132
139
  current.delete(v);
133
- }
134
- else {
140
+ else
135
141
  current.add(v);
136
- }
137
142
  emit({ value: Array.from(current) });
138
143
  }, onClear: () => emit({ value: [] }), children: placeholder })) })] }));
139
144
  }