@lavalogic/scoria 0.37.10 → 0.37.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.
@@ -205,6 +205,7 @@
205
205
  .virtual-spacer {
206
206
  display: block;
207
207
  border: none;
208
- background: transparent;
209
208
  pointer-events: none;
209
+ --row-stripe-height: var(--height, 3rem);
210
+ background-image: repeating-linear-gradient(to bottom, #ffffff 0, #ffffff var(--row-stripe-height), #f4f7f9 var(--row-stripe-height), #f4f7f9 calc(2 * var(--row-stripe-height)));
210
211
  }</style>
@@ -100,7 +100,14 @@ function commonProps(opts, kind) {
100
100
  const defaultWidth = KIND_DEFAULT_WIDTHS[kind];
101
101
  return {
102
102
  header: opts.header,
103
- minSize: opts.minWidth,
103
+ // Set minSize from the per-kind default too so
104
+ // `TableContext.registerDefs` does NOT run the canvas-based
105
+ // `measureHeaderWidth` fallback. That fallback floors every
106
+ // accessor column to ~130px when quick-filtering is enabled
107
+ // (it returns max(headerWidth, "Lorem Ipsum Dolor" width)),
108
+ // which equalises every accessor column width and erases the
109
+ // per-kind differentiation the factory just put in place.
110
+ minSize: opts.minWidth ?? defaultWidth,
104
111
  width: opts.width ?? defaultWidth,
105
112
  allowSorting: opts.allowSorting,
106
113
  allowFiltering: opts.allowFiltering,
@@ -249,9 +256,27 @@ export function createColumnFactory() {
249
256
  const normalisedOptions = opts.getOptions
250
257
  ? normaliseOptionsLoader(opts.getOptions)
251
258
  : undefined;
259
+ // Display columns have no intrinsic kind (the value comes from an
260
+ // arbitrary `value: (row) => ...` thunk) so the generic 'display'
261
+ // default (150px) is the fallback. When the consumer DOES pass a
262
+ // `filterValueType`, treat it as a hint about the rendered shape
263
+ // of the column and pick the matching per-kind default so a table
264
+ // full of `col.display` columns ends up visually differentiated
265
+ // instead of every column collapsing to the same 150px floor.
266
+ const inferredKind = opts.filterValueType === 'Number'
267
+ ? 'number'
268
+ : opts.filterValueType === 'Date'
269
+ ? 'date'
270
+ : opts.filterValueType === 'Bool'
271
+ ? 'checkbox'
272
+ : opts.filterValueType === 'Select'
273
+ ? 'select'
274
+ : opts.filterValueType === 'String'
275
+ ? 'text'
276
+ : 'display';
252
277
  const def = new DisplayDef({
253
278
  id,
254
- ...commonProps(opts, 'display'),
279
+ ...commonProps(opts, inferredKind),
255
280
  accessorFn: opts.value,
256
281
  modal: opts.modal,
257
282
  // Display columns have no implicit filter type (unlike the
@@ -307,7 +332,7 @@ export function createColumnFactory() {
307
332
  const def = new ValidityDef({
308
333
  id,
309
334
  header: opts?.header ?? '',
310
- minSize: opts?.minWidth,
335
+ minSize: opts?.minWidth ?? KIND_DEFAULT_WIDTHS.validity,
311
336
  width: opts?.width ?? KIND_DEFAULT_WIDTHS.validity,
312
337
  defaultHidden: opts?.defaultHidden,
313
338
  resizable: opts?.resizable ?? false,
@@ -356,17 +356,20 @@
356
356
  // 20px to match the existing visual).
357
357
  // - Each other track uses one of two strategies based on the
358
358
  // column's `userResized` flag:
359
- // - `userResized = false` (default): `minmax(width, 1fr)`. The
360
- // column flexes to share the row's remaining horizontal space
361
- // so the grid fills the container, and it automatically takes
362
- // back any space a sibling column gives up when shrunk - this
363
- // means the table never ends up narrower than its container
364
- // while any column is still in the flexible set.
359
+ // - `userResized = false` (default): `minmax(width, max-content)`.
360
+ // The column is at least `width` wide (the per-kind default
361
+ // set by the column factory, or any explicit `width` the caller
362
+ // supplied) and grows up to its content's natural width. This
363
+ // mirrors the legacy `<table>` element's auto-layout: columns
364
+ // visually differentiate by content rather than collapsing to
365
+ // equal `1fr` shares of leftover space. The grid container
366
+ // (`.datatable-grid`) has `min-width: max-content` and the
367
+ // scroll host (`.datatable`) has `overflow-x: auto`, so when
368
+ // the summed track widths exceed the viewport the table
369
+ // scrolls horizontally rather than crushing every column down.
365
370
  // - `userResized = true`: `${width}px` (fixed). The column sits
366
371
  // at exactly the size the user dragged it to, and dragging the
367
- // handle again only changes this column - other user-resized
368
- // columns keep their explicit widths and non-resized columns
369
- // just adjust their flex share.
372
+ // handle again only changes this column.
370
373
  // - Pinned and centre columns use the same per-column rule so
371
374
  // toggling a column's pin state does not change its width.
372
375
  // - Sticky-cell offsets are read from `_columnRenderedWidths`
@@ -377,7 +380,7 @@
377
380
  const track = (col: { columnDef: { width: number; userResized: boolean } }) =>
378
381
  col.columnDef.userResized
379
382
  ? `${col.columnDef.width}px`
380
- : `minmax(${col.columnDef.width}px, 1fr)`;
383
+ : `minmax(${col.columnDef.width}px, max-content)`;
381
384
  const tracks: Array<string> = ['20px'];
382
385
  for (const col of leftColumns) {
383
386
  tracks.push(track(col));
@@ -50,7 +50,17 @@ export class VirtualisationState {
50
50
  constructor(deps, options = {}) {
51
51
  this._deps = deps;
52
52
  this._virtualisationThreshold = options.threshold ?? 100;
53
- this._overscan = options.overscan ?? 10;
53
+ // Overscan controls how many rows above/below the viewport are
54
+ // kept rendered so a fast wheel-scroll does not surface an empty
55
+ // grey band before the next paint. The previous default of 10
56
+ // was too tight on tables with hundreds of rows: a single wheel
57
+ // flick easily moves the viewport past the overscan band before
58
+ // the `$derived` chain reschedules and re-renders the visible
59
+ // slice. 30 rows is enough to absorb a typical wheel flick at
60
+ // 60Hz while still keeping the rendered cell count bounded
61
+ // (~52 rows for a 600px viewport at the default 28-48px row
62
+ // height: 12 visible + 30 below + 10 above = 52).
63
+ this._overscan = options.overscan ?? 30;
54
64
  }
55
65
  get virtualisationThreshold() {
56
66
  return this._virtualisationThreshold;
@@ -3,6 +3,7 @@ import type { AnyEditEvent } from './AnyEditEvent.js';
3
3
  import type { ColumnFactory } from './ColumnFactory.js';
4
4
  import type { ColumnSpec } from './ColumnSpec.js';
5
5
  import type { LiteralRowIdKey } from './KeyConstraints.js';
6
+ import type { CustomRemoteFilters } from '../Filtering/CustomRemoteFilters.js';
6
7
  import type { RowSource } from './RowSource.js';
7
8
  import type { SelectionOptions } from './SelectionOptions.js';
8
9
  import type { TableInitialState } from './TableInitialState.js';
@@ -35,6 +36,16 @@ export interface CreateTableOptions<TRow extends object, TRowId extends Primitiv
35
36
  /** Column declarations. The closure receives a typed `ColumnFactory<TRow>`;
36
37
  * return the column specs in memory order. */
37
38
  columns: (col: ColumnFactory<TRow>) => ReadonlyArray<ColumnSpec<TRow>>;
39
+ /** Optional registry of custom server-driven filters, keyed by column id.
40
+ * Each entry provides the metadata the advanced filter panel needs to
41
+ * render an input for a column that does not have a built-in `filterFn`
42
+ * (such as `col.filterOnly(...)` chips or nested-path display columns).
43
+ *
44
+ * Passed as a thunk so the caller can return a `SvelteMap` whose
45
+ * reactive identity is preserved across re-renders. The same map is
46
+ * typically also handed to `createRemoteRowSource({ remoteFilters })`
47
+ * so the server query and the filter UI agree on the available keys. */
48
+ remoteFilters?: () => CustomRemoteFilters<unknown> | undefined;
38
49
  /** Selection. Defaults to `{ mode: 'multiple' }` with internal map ownership. */
39
50
  selection?: SelectionOptions<TRow, TRowId>;
40
51
  /** Persistence. Defaults to all layers enabled. Pass `false` to disable
@@ -1,6 +1,7 @@
1
1
  import type { RefWrapper } from '../../../../Types/Internal/RefWrapper.js';
2
2
  import type { ColumnFiltersState } from '../Filtering/ColumnFiltersState.js';
3
3
  import type { SortingState } from '../DataRepository/SortingState.js';
4
+ import type { CustomRemoteFilters } from '../Filtering/CustomRemoteFilters.js';
4
5
  /**
5
6
  * Data source for a Table. One of three variants:
6
7
  *
@@ -71,6 +72,14 @@ export interface CustomRowSource<TRow extends object> {
71
72
  * `AbortSignal` so consumers can cancel an in-flight request when its
72
73
  * result is superseded. */
73
74
  fetch: (request: RowFetchRequest) => Promise<RowFetchResponse<TRow>>;
75
+ /** Optional registry of server-driven filters keyed by column id. When
76
+ * set, the table's advanced filter panel will render a chip for each
77
+ * entry alongside the standard column-derived chips. Typically the
78
+ * custom-rows factory (e.g. `createRemoteRowSource`) populates this
79
+ * from the same map it uses to build the request URL, so the UI and
80
+ * the server query stay in sync without the caller having to wire the
81
+ * map twice. */
82
+ remoteFilters?: CustomRemoteFilters<unknown>;
74
83
  }
75
84
  /** Argument passed to `CustomRowSource.fetch`. */
76
85
  export interface RowFetchRequest {
@@ -69,7 +69,15 @@ export function createTable(options) {
69
69
  }
70
70
  return internal.def;
71
71
  });
72
- const defsWrapper = { defs };
72
+ // remoteFilters resolution order:
73
+ // 1. Explicit `options.remoteFilters` thunk on createTable.
74
+ // 2. `kind: 'custom'` row source's `remoteFilters` field (set by
75
+ // `createRemoteRowSource` from its own option of the same name).
76
+ // This means a caller can hand the same `SvelteMap` to the custom
77
+ // row-source factory once and the filter panel picks it up for free.
78
+ const remoteFilters = options.remoteFilters?.() ??
79
+ (options.rows.kind === 'custom' ? options.rows.remoteFilters : undefined);
80
+ const defsWrapper = remoteFilters ? { defs, remoteFilters } : { defs };
73
81
  // ── 3. Construct the repository for the chosen row source ──────
74
82
  const repository = makeRepository(options.name, options.rows, options.getUserId);
75
83
  // ── 4. Build the `onEditCell` wrapper ──────────────────────────
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.10",
4
+ "version": "0.37.12",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },