@lavalogic/scoria 0.37.48 → 0.37.50

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.
@@ -717,8 +717,7 @@
717
717
  div.input-cell {
718
718
  background-color: #f4f7f9;
719
719
  height: var(--height);
720
- overflow-x: clip;
721
- overflow-y: visible;
720
+ overflow: visible;
722
721
  padding-left: 0;
723
722
  padding-right: 0;
724
723
  border-right: none;
@@ -780,6 +779,8 @@ div.input-wrapper-parent {
780
779
  gap: 0.25rem;
781
780
  flex-grow: 1;
782
781
  --input-border: none;
782
+ overflow-x: clip;
783
+ overflow-y: visible;
783
784
  }
784
785
  div.input-wrapper-parent div.input-wrapper {
785
786
  position: relative;
@@ -8,7 +8,12 @@
8
8
  import { TableContext } from '../../../Types/Context/TableContext.svelte.js';
9
9
  import { type Primitive } from '../../../Types/Context/TableInitOptions.js';
10
10
  import type { TableFocusDetails } from '../../../Types/Focus/TableFocusDetails.svelte.js';
11
- import { devCatch, isValidityTuple, loadingText } from '../../../../../Helpers/Helpers.svelte.js';
11
+ import {
12
+ devCatch,
13
+ formatDateTime,
14
+ isValidityTuple,
15
+ loadingText,
16
+ } from '../../../../../Helpers/Helpers.svelte.js';
12
17
  import type { SelectOption } from '../../../../../Types/Internal/SelectOption.js';
13
18
  import { Validity } from '../../../../../Types/Internal/Validity.js';
14
19
  import { getContext, tick } from 'svelte';
@@ -47,11 +52,10 @@
47
52
  if (d == null) {
48
53
  return undefined;
49
54
  }
50
- // Use `toLocaleString` for both lines so the visual format matches
51
- // (only the time zone differs). The `timeZone: 'UTC'` override on
52
- // the second call renders the same date / time but reinterpreted
53
- // in UTC, which is what the user expects to see on hover.
54
- return `Local: ${d.toLocaleString()}\nUTC: ${d.toLocaleString(undefined, { timeZone: 'UTC' })}`;
55
+ // Both lines go through `formatDateTime` so the tooltip matches
56
+ // every other date surface in the app; only the timezone differs —
57
+ // the second call overrides it to UTC.
58
+ return `Local: ${formatDateTime(d)}\nUTC: ${formatDateTime(d, undefined, 'UTC')}`;
55
59
  });
56
60
 
57
61
  const tableContext = getContext<TableContext<T, RowIdType>>(TableContext.identifier);
@@ -64,7 +64,11 @@
64
64
  {#if filteredChoices.length === 0}
65
65
  <span>No Results</span>
66
66
  {/if}
67
- {#each filteredChoices as choice (choice.label)}
67
+ <!-- Keyed by index, not `choice.label`: labels are not guaranteed
68
+ unique (e.g. a courier can expose two services with the same
69
+ name), and a duplicate `{#each}` key throws `each_key_duplicate`.
70
+ The buttons hold no per-item state, so index keying is safe. -->
71
+ {#each filteredChoices as choice, index (index)}
68
72
  {#if selected === choice.label}
69
73
  {#if choice.callback}
70
74
  <button
@@ -148,8 +148,12 @@ export declare const defaultDateLocale = "en-GB";
148
148
  * `'de-DE'`) selects the display conventions: it drives the
149
149
  * day/month/year ordering and separators — `en-GB` → `19/05/2026,
150
150
  * 14:30`, `en-US` → `05/19/2026, 14:30`. It defaults to
151
- * {@link defaultDateLocale}. The clock is always 24-hour and the
152
- * timezone is the runtime-resolved one regardless of locale.
151
+ * {@link defaultDateLocale}. The clock is always 24-hour.
152
+ *
153
+ * The optional `timeZone` (an IANA zone such as `'UTC'`) overrides the
154
+ * zone the timestamp is rendered in; it defaults to the runtime-resolved
155
+ * zone. Pass `'UTC'` to render a value in UTC — e.g. for a Local-vs-UTC
156
+ * tooltip.
153
157
  *
154
158
  * - `Date` values format directly.
155
159
  * - `string` / `number` values are parsed via `new Date(...)`. An
@@ -161,7 +165,7 @@ export declare const defaultDateLocale = "en-GB";
161
165
  * no time use {@link formatDate}; for the SQL-backend serialisation
162
166
  * shapes use {@link SQLFriendly} / {@link SQLFriendlyWithLocalTime}.
163
167
  */
164
- export declare function formatDateTime(value: Date | string | number | null | undefined, locale?: string): string;
168
+ export declare function formatDateTime(value: Date | string | number | null | undefined, locale?: string, timeZone?: string): string;
165
169
  /**
166
170
  * Canonical date-only display formatter. Use this for values that carry
167
171
  * no meaningful time component — due dates, expiry dates, pod-line date
@@ -252,24 +252,29 @@ export const localTimeFormat = new Intl.DateTimeFormat('en-GB', {
252
252
  */
253
253
  export const defaultDateLocale = 'en-GB';
254
254
  /**
255
- * Per-locale caches of `Intl.DateTimeFormat` instances. Constructing a
256
- * formatter is comparatively expensive, so the first call for a given
257
- * locale builds it and every later call for that locale reuses it. The
258
- * caches only ever grow and hold immutable formatters, so they are safe
259
- * to share across SSR requests.
255
+ * The runtime-resolved IANA timezone, read once per environment. The
256
+ * default timezone for {@link formatDate} / {@link formatDateTime}.
257
+ */
258
+ const runtimeTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
259
+ /**
260
+ * Caches of `Intl.DateTimeFormat` instances — keyed by `locale` (and, for
261
+ * date+time, also by timezone). Constructing a formatter is comparatively
262
+ * expensive, so the first call for a given key builds it and every later
263
+ * call reuses it. The caches only ever grow and hold immutable
264
+ * formatters, so they are safe to share across SSR requests.
260
265
  */
261
266
  const dateTimeFormatters = {};
262
267
  const dateFormatters = {};
263
- /** Build (and cache) a full date + time formatter for `locale`. */
264
- function dateTimeFormatterFor(locale) {
265
- return (dateTimeFormatters[locale] ??= new Intl.DateTimeFormat(locale, {
268
+ /** Build (and cache) a full date + time formatter for `locale` / `timeZone`. */
269
+ function dateTimeFormatterFor(locale, timeZone) {
270
+ return (dateTimeFormatters[`${locale}|${timeZone}`] ??= new Intl.DateTimeFormat(locale, {
266
271
  day: '2-digit',
267
272
  month: '2-digit',
268
273
  year: 'numeric',
269
274
  hour: '2-digit',
270
275
  hourCycle: 'h23',
271
276
  minute: '2-digit',
272
- timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
277
+ timeZone,
273
278
  }));
274
279
  }
275
280
  /** Build (and cache) a date-only formatter for `locale`. */
@@ -278,7 +283,7 @@ function dateFormatterFor(locale) {
278
283
  day: '2-digit',
279
284
  month: '2-digit',
280
285
  year: 'numeric',
281
- timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
286
+ timeZone: runtimeTimeZone,
282
287
  }));
283
288
  }
284
289
  /**
@@ -290,8 +295,12 @@ function dateFormatterFor(locale) {
290
295
  * `'de-DE'`) selects the display conventions: it drives the
291
296
  * day/month/year ordering and separators — `en-GB` → `19/05/2026,
292
297
  * 14:30`, `en-US` → `05/19/2026, 14:30`. It defaults to
293
- * {@link defaultDateLocale}. The clock is always 24-hour and the
294
- * timezone is the runtime-resolved one regardless of locale.
298
+ * {@link defaultDateLocale}. The clock is always 24-hour.
299
+ *
300
+ * The optional `timeZone` (an IANA zone such as `'UTC'`) overrides the
301
+ * zone the timestamp is rendered in; it defaults to the runtime-resolved
302
+ * zone. Pass `'UTC'` to render a value in UTC — e.g. for a Local-vs-UTC
303
+ * tooltip.
295
304
  *
296
305
  * - `Date` values format directly.
297
306
  * - `string` / `number` values are parsed via `new Date(...)`. An
@@ -303,7 +312,7 @@ function dateFormatterFor(locale) {
303
312
  * no time use {@link formatDate}; for the SQL-backend serialisation
304
313
  * shapes use {@link SQLFriendly} / {@link SQLFriendlyWithLocalTime}.
305
314
  */
306
- export function formatDateTime(value, locale = defaultDateLocale) {
315
+ export function formatDateTime(value, locale = defaultDateLocale, timeZone = runtimeTimeZone) {
307
316
  if (value == null || value === '') {
308
317
  return '';
309
318
  }
@@ -312,7 +321,7 @@ export function formatDateTime(value, locale = defaultDateLocale) {
312
321
  if (Number.isNaN(date.getTime())) {
313
322
  return typeof value === 'string' ? value : '';
314
323
  }
315
- return dateTimeFormatterFor(locale).format(date);
324
+ return dateTimeFormatterFor(locale, timeZone).format(date);
316
325
  }
317
326
  /**
318
327
  * Canonical date-only display formatter. Use this for values that carry
@@ -1905,16 +1905,13 @@
1905
1905
  div.input-cell {
1906
1906
  background-color: colours.$ui-blue-1;
1907
1907
  height: var(--height);
1908
- // `overflow-x: clip` so a narrow filter cell's internal widget
1909
- // (select chevron, filter-mode funnel, etc.) cannot paint past
1910
- // the column boundary. The column-separator border now lives on
1911
- // the outer grid item (`.quick-filter-row > .cell` in
1912
- // `TableBody.svelte`), so clipping here keeps both the widget
1913
- // AND the cell's own background inside the track and the border
1914
- // stays visible. `overflow-y: visible` preserves the inline
1915
- // dropdown spillage for select / date popups.
1916
- overflow-x: clip;
1917
- overflow-y: visible;
1908
+ // The cell itself must NOT clip: the filter-mode popup
1909
+ // (`div.floating`) is an absolutely-positioned child that spills
1910
+ // past the column boundary on purpose. Internal-widget overflow
1911
+ // (a select wider than a narrow column, chevrons, etc.) is clipped
1912
+ // one level in, on `div.input-wrapper-parent`, so the popup still
1913
+ // escapes while widgets stay inside their track.
1914
+ overflow: visible;
1918
1915
  padding-left: 0;
1919
1916
  padding-right: 0;
1920
1917
  // Inner widget no longer paints a border-right - the outer cell
@@ -1982,6 +1979,13 @@
1982
1979
  gap: 0.25rem;
1983
1980
  flex-grow: 1;
1984
1981
  --input-border: none;
1982
+ // Clip internal widget overflow (a select min-width wider than a
1983
+ // narrow column, chevrons, etc.) at the column boundary here
1984
+ // rather than on `div.input-cell` — clipping the cell would also
1985
+ // clip the filter-mode popup. `overflow-y: visible` keeps select
1986
+ // option dropdowns spilling downward.
1987
+ overflow-x: clip;
1988
+ overflow-y: visible;
1985
1989
 
1986
1990
  div.input-wrapper {
1987
1991
  position: relative;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lavalogic/scoria",
3
3
  "description": "Svelte components used for the FloWMS Web Frontend",
4
- "version": "0.37.48",
4
+ "version": "0.37.50",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },