@lavalogic/scoria 0.37.18 → 0.37.19
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.
- package/dist/Components/Table/Body/TableBody.svelte +5 -0
- package/dist/Components/Table/ColumnFactory.svelte.d.ts +0 -12
- package/dist/Components/Table/ColumnFactory.svelte.js +41 -1
- package/dist/Components/Table/Misc/FilterInput.svelte +22 -4
- package/dist/Components/Table/NestedTable.svelte +6 -1
- package/dist/Components/Table/Table.svelte +12 -1
- package/package.json +1 -1
|
@@ -183,6 +183,11 @@
|
|
|
183
183
|
font-size: 0.875rem;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
.datatable-row.quick-filter-row > .cell {
|
|
187
|
+
border-right: solid 1px #cbd4da;
|
|
188
|
+
box-sizing: border-box;
|
|
189
|
+
}
|
|
190
|
+
|
|
186
191
|
.datatable-row.quick-filter-row {
|
|
187
192
|
transition: line-height 0.2s ease, height 0.2s ease;
|
|
188
193
|
height: 61px;
|
|
@@ -28,16 +28,4 @@ export interface ColumnSpecInternals<TRow extends object> {
|
|
|
28
28
|
* spec was not produced by `createColumnFactory(...)` (defensive; the
|
|
29
29
|
* public type's `_scoriaColumnSpec` brand prevents this at compile time). */
|
|
30
30
|
export declare function unwrapColumnSpec<TRow extends object>(spec: ColumnSpec<TRow>): ColumnSpecInternals<TRow>;
|
|
31
|
-
/**
|
|
32
|
-
* Construct a `ColumnFactory<TRow>` for use inside the `columns:` closure of
|
|
33
|
-
* `createTable({ ... })`. Each method on the returned object is a typed
|
|
34
|
-
* builder that wraps the matching internal `<Name>Def` constructor and
|
|
35
|
-
* returns a public `ColumnSpec` whose internal payload is hidden behind a
|
|
36
|
-
* symbol-keyed property.
|
|
37
|
-
*
|
|
38
|
-
* Consumers do not call this directly. It is exported so the `createTable`
|
|
39
|
-
* implementation can instantiate it inside the user-supplied closure and so
|
|
40
|
-
* tests can exercise individual factory methods without round-tripping
|
|
41
|
-
* through a full `createTable(...)` call.
|
|
42
|
-
*/
|
|
43
31
|
export declare function createColumnFactory<TRow extends object>(): ColumnFactory<TRow>;
|
|
@@ -178,6 +178,30 @@ function normaliseOptionsLoader(options) {
|
|
|
178
178
|
* tests can exercise individual factory methods without round-tripping
|
|
179
179
|
* through a full `createTable(...)` call.
|
|
180
180
|
*/
|
|
181
|
+
/** Format a Date or ISO-8601 date string for display using the browser
|
|
182
|
+
* locale. Returns null / empty input unchanged. Used by `col.display` when
|
|
183
|
+
* the column declares `filterValueType: 'Date'` so backend timestamps
|
|
184
|
+
* surface as a properly localised date+time string instead of the raw
|
|
185
|
+
* ISO payload (`2026-04-16T09:44:11+01:00`). */
|
|
186
|
+
function formatLocaleDate(value) {
|
|
187
|
+
if (value == null || value === '') {
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
let d;
|
|
191
|
+
if (value instanceof Date) {
|
|
192
|
+
d = value;
|
|
193
|
+
}
|
|
194
|
+
else if (typeof value === 'string' || typeof value === 'number') {
|
|
195
|
+
const parsed = new Date(value);
|
|
196
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
197
|
+
d = parsed;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (d == null) {
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
return d.toLocaleString();
|
|
204
|
+
}
|
|
181
205
|
export function createColumnFactory() {
|
|
182
206
|
const factory = {
|
|
183
207
|
// ─── Accessor kinds ─────────────────────────────────────────
|
|
@@ -301,10 +325,26 @@ export function createColumnFactory() {
|
|
|
301
325
|
: opts.filterValueType === 'String'
|
|
302
326
|
? 'text'
|
|
303
327
|
: 'display';
|
|
328
|
+
// When the consumer flagged the column as a Date filter, auto-
|
|
329
|
+
// format the displayed value via `toLocaleString` so timestamps
|
|
330
|
+
// surface in the user's locale rather than the raw ISO 8601
|
|
331
|
+
// payload returned by the server. Promise / scalar / null
|
|
332
|
+
// inputs are all handled. Filter / sort logic still operates
|
|
333
|
+
// on the raw accessor value below.
|
|
334
|
+
const rawValue = opts.value;
|
|
335
|
+
const valueAccessor = opts.filterValueType === 'Date'
|
|
336
|
+
? (row, index) => {
|
|
337
|
+
const v = rawValue(row, index);
|
|
338
|
+
if (v instanceof Promise) {
|
|
339
|
+
return v.then(formatLocaleDate);
|
|
340
|
+
}
|
|
341
|
+
return formatLocaleDate(v);
|
|
342
|
+
}
|
|
343
|
+
: rawValue;
|
|
304
344
|
const def = new DisplayDef({
|
|
305
345
|
id,
|
|
306
346
|
...commonProps(opts, inferredKind),
|
|
307
|
-
accessorFn:
|
|
347
|
+
accessorFn: valueAccessor,
|
|
308
348
|
modal: opts.modal,
|
|
309
349
|
// Display columns have no implicit filter type (unlike the
|
|
310
350
|
// typed kinds where the factory pins one based on the
|
|
@@ -35,6 +35,24 @@
|
|
|
35
35
|
|
|
36
36
|
const customFilter = $derived(customFilters?.get(def.id));
|
|
37
37
|
|
|
38
|
+
/** Human-readable label used by every inner input (QuerySelect /
|
|
39
|
+
* MultiSelect / SingleSelect / etc.). Priority:
|
|
40
|
+
* 1. The column's `header` (when it is a plain string, which is the
|
|
41
|
+
* usual case in flowms migrations).
|
|
42
|
+
* 2. The customFilter's `label` (set on the remoteFilters entry).
|
|
43
|
+
* 3. The raw `customFilter.id` (last-ditch fallback).
|
|
44
|
+
* Avoids surfacing dotted-path / camelCase IDs like
|
|
45
|
+
* `orderLines.allowSplitFulfilment` as the visible label. */
|
|
46
|
+
const displayLabel: string = $derived.by(() => {
|
|
47
|
+
if (typeof def.header === 'string' && def.header.length > 0) {
|
|
48
|
+
return def.header;
|
|
49
|
+
}
|
|
50
|
+
if (customFilter?.label) {
|
|
51
|
+
return customFilter.label;
|
|
52
|
+
}
|
|
53
|
+
return customFilter?.id ?? def.id;
|
|
54
|
+
});
|
|
55
|
+
|
|
38
56
|
const isRemoteSelect = $derived(customFilter?.type === CustomFilterType.Select);
|
|
39
57
|
|
|
40
58
|
let value: unknown = $state(null);
|
|
@@ -263,7 +281,7 @@
|
|
|
263
281
|
{#if isSearchable}
|
|
264
282
|
<!-- TODO remove this check once datePicker is supported -->
|
|
265
283
|
{#if def instanceof DateInputDef || def instanceof CheckboxDef}
|
|
266
|
-
<label for={inputId}>{
|
|
284
|
+
<label for={inputId}>{displayLabel}</label>
|
|
267
285
|
{/if}
|
|
268
286
|
<div class="row">
|
|
269
287
|
{#if def instanceof DateInputDef}
|
|
@@ -353,7 +371,7 @@
|
|
|
353
371
|
{#await options then options}
|
|
354
372
|
{#if hasQuery(customFilter)}
|
|
355
373
|
{#if customFilter.valueAsObject !== true}
|
|
356
|
-
{@const label =
|
|
374
|
+
{@const label = displayLabel}
|
|
357
375
|
|
|
358
376
|
<!-- TODO: multiselect -->
|
|
359
377
|
|
|
@@ -370,7 +388,7 @@
|
|
|
370
388
|
onchange={generateOnChangePrimitive(label)}
|
|
371
389
|
/>
|
|
372
390
|
{:else}
|
|
373
|
-
{@const label =
|
|
391
|
+
{@const label = displayLabel}
|
|
374
392
|
|
|
375
393
|
<!-- TODO: multiselect -->
|
|
376
394
|
|
|
@@ -389,7 +407,7 @@
|
|
|
389
407
|
{/if}
|
|
390
408
|
{:else}
|
|
391
409
|
<MultiSelect
|
|
392
|
-
label={
|
|
410
|
+
label={displayLabel}
|
|
393
411
|
labelProp="label"
|
|
394
412
|
valueProp="value"
|
|
395
413
|
name={customFilter.objectName ?? 'Item'}
|
|
@@ -28,7 +28,12 @@
|
|
|
28
28
|
> = Object.freeze({
|
|
29
29
|
allowCopy: false,
|
|
30
30
|
enableResize: false,
|
|
31
|
-
|
|
31
|
+
// Was `false`; flipped to `true` so nested-table columns can be
|
|
32
|
+
// reordered and pinned by default like the outer table. The
|
|
33
|
+
// `inner.allowReorder` option on `col.expand` (and the matching
|
|
34
|
+
// `allowReorder` on top-level `createTable`) still lets callers
|
|
35
|
+
// opt out per inner table when they want to lock the layout.
|
|
36
|
+
allowColumnReordering: true,
|
|
32
37
|
allowQuickFiltering: true,
|
|
33
38
|
allowAdvancedFiltering: true,
|
|
34
39
|
toolbar: ToolbarPosition.None,
|
|
@@ -129,10 +129,21 @@
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
element?.focus({ preventScroll: true });
|
|
132
|
+
// Same pinned-cell horizontal-scroll skip as the focusEnd
|
|
133
|
+
// effect below. Sticky-pinned cells are always visually
|
|
134
|
+
// in view, but their underlying layout position is at
|
|
135
|
+
// their real column track, so an unconditional
|
|
136
|
+
// `inline: 'nearest'` here scrolls the table sideways
|
|
137
|
+
// every time focus lands inside a pinned column (e.g.
|
|
138
|
+
// every click on a right-pinned Actions button).
|
|
139
|
+
const startCell = element?.closest('[role="cell"]');
|
|
140
|
+
const isStartPinned =
|
|
141
|
+
startCell?.classList.contains('pinned-left') ||
|
|
142
|
+
startCell?.classList.contains('pinned-right');
|
|
132
143
|
element?.scrollIntoView({
|
|
133
144
|
behavior: 'instant',
|
|
134
145
|
block: 'nearest',
|
|
135
|
-
inline: 'nearest',
|
|
146
|
+
inline: isStartPinned ? undefined : 'nearest',
|
|
136
147
|
});
|
|
137
148
|
}
|
|
138
149
|
});
|