@lavalogic/scoria 0.37.14 → 0.37.16

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.
@@ -111,6 +111,12 @@ function commonProps(opts, kind) {
111
111
  width: opts.width ?? defaultWidth,
112
112
  maxWidth: opts.maxWidth,
113
113
  wrapValue: opts.wrapValue,
114
+ // `widthIsFixed` is true only when the caller passed an explicit
115
+ // `width`. The grid template uses this to render the track as
116
+ // fixed `${W}px` for explicit widths, leaving the per-kind-default
117
+ // columns on `minmax(W, 1fr)` so they absorb any horizontal space
118
+ // the fixed columns leave behind.
119
+ widthIsFixed: opts.width != null,
114
120
  allowSorting: opts.allowSorting,
115
121
  allowFiltering: opts.allowFiltering,
116
122
  defaultHidden: opts.defaultHidden,
@@ -133,7 +139,12 @@ function mintSyntheticId(prefix, hint) {
133
139
  * required by the Def's signature; we spread to drop readonly. */
134
140
  function normaliseOptionsLoader(options) {
135
141
  if (typeof options === 'function') {
136
- return () => options().then((res) => [...res]);
142
+ // `options()` may return either a Promise or a plain array - some
143
+ // project helpers (e.g. flowms `ColumnDefRepository.getAccounts()`
144
+ // vs. `getOutboundStatuses()`) settle on different conventions per
145
+ // data source. `Promise.resolve` accepts both so the loader hands
146
+ // the consumer a uniform Promise either way.
147
+ return () => Promise.resolve(options()).then((res) => [...res]);
137
148
  }
138
149
  const snapshot = [...options];
139
150
  return () => Promise.resolve(snapshot);
@@ -285,7 +296,12 @@ export function createColumnFactory() {
285
296
  // typed kinds where the factory pins one based on the
286
297
  // column kind). Consumers opt in by passing the type they
287
298
  // want the filter input rendered as.
288
- filterValueType: opts.filterValueType,
299
+ // Default to string-typed filter when the caller did not specify
300
+ // one, so a display column with no explicit `filterValueType`
301
+ // still appears in the filter UI (matches the `DisplayDef →
302
+ // stringFilter (undefined case)` mapping in
303
+ // `setupDefaultColumnDefs`).
304
+ filterValueType: opts.filterValueType ?? FilterValueType.String,
289
305
  getSortingFn: opts.getSortingFn,
290
306
  getOptions: normalisedOptions,
291
307
  });
@@ -293,12 +309,21 @@ export function createColumnFactory() {
293
309
  },
294
310
  bubble(opts) {
295
311
  const id = mintSyntheticId('bubble', opts.idHint);
312
+ const normalisedOptions = opts.getOptions
313
+ ? normaliseOptionsLoader(opts.getOptions)
314
+ : undefined;
296
315
  const def = new BubbleDef({
297
316
  id,
298
317
  ...commonProps(opts, 'bubble'),
299
318
  accessorFn: opts.value,
300
319
  getColour: opts.colour,
301
- filterValueType: opts.filterValueType,
320
+ // Default to a string-typed filter so bubble columns appear in
321
+ // the quick-filter row and advanced filter panel by default
322
+ // (matches the `BubbleDef → stringFilter` mapping in
323
+ // `setupDefaultColumnDefs`). Caller can override to any other
324
+ // `filterValueType`, or pass `allowFiltering: false` to opt out.
325
+ filterValueType: opts.filterValueType ?? FilterValueType.String,
326
+ getOptions: normalisedOptions,
302
327
  });
303
328
  return makeSpec('bubble', def);
304
329
  },
@@ -319,12 +344,19 @@ export function createColumnFactory() {
319
344
  return userColour(row, index, fraction);
320
345
  }
321
346
  : () => 'grey';
347
+ const progressOptions = opts.getOptions
348
+ ? normaliseOptionsLoader(opts.getOptions)
349
+ : undefined;
322
350
  const def = new ProgressBarDef({
323
351
  id,
324
352
  ...commonProps(opts, 'progress'),
325
353
  accessorFn: tupleAccessor,
326
354
  getColour,
327
- filterValueType: opts.filterValueType,
355
+ // Default to number-typed filter so progress columns appear in
356
+ // the filter UI by default (matches `ProgressBarDef →
357
+ // numberFilter` in `setupDefaultColumnDefs`).
358
+ filterValueType: opts.filterValueType ?? FilterValueType.Number,
359
+ getOptions: progressOptions,
328
360
  });
329
361
  return makeSpec('progress', def);
330
362
  },
@@ -336,6 +368,7 @@ export function createColumnFactory() {
336
368
  header: opts?.header ?? '',
337
369
  minSize: opts?.minWidth ?? KIND_DEFAULT_WIDTHS.validity,
338
370
  width: opts?.width ?? KIND_DEFAULT_WIDTHS.validity,
371
+ widthIsFixed: opts?.width != null,
339
372
  defaultHidden: opts?.defaultHidden,
340
373
  resizable: opts?.resizable ?? false,
341
374
  debug: opts?.debug,
@@ -354,6 +387,7 @@ export function createColumnFactory() {
354
387
  // fallback and share equal `1fr` track space with wider columns.
355
388
  def.width = KIND_DEFAULT_WIDTHS.rowSelection;
356
389
  def.minSize = KIND_DEFAULT_WIDTHS.rowSelection;
390
+ def.widthIsFixed = true;
357
391
  return makeSpec('rowSelection', def);
358
392
  },
359
393
  actions(opts) {
@@ -378,6 +412,7 @@ export function createColumnFactory() {
378
412
  });
379
413
  // Actions has no public width option - apply the per-kind default.
380
414
  def.width = KIND_DEFAULT_WIDTHS.actions;
415
+ def.widthIsFixed = true;
381
416
  return makeSpec('actions', def);
382
417
  },
383
418
  expand(opts) {
@@ -423,6 +458,7 @@ export function createColumnFactory() {
423
458
  // Expand has no public width option - apply the per-kind default.
424
459
  def.width = KIND_DEFAULT_WIDTHS.expand;
425
460
  def.minSize = KIND_DEFAULT_WIDTHS.expand;
461
+ def.widthIsFixed = true;
426
462
  return makeSpec('expand', def);
427
463
  },
428
464
  filterOnly(opts) {
@@ -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, maxWidth ?? 1fr)`.
360
- // The column is at least `width` wide and grows to share any
361
- // leftover horizontal space with its sibling tracks. If the
362
- // column has a `maxWidth`, that pixel value is the upper bound
363
- // instead - the column never grows past `maxWidth`, and cells
364
- // whose content exceeds the track ellipsise (or wrap, when the
365
- // column opts in to `wrapValue`). The `1fr` upper bound was
366
- // chosen over `max-content` because `max-content` is unstable
367
- // under row virtualisation: tracks resize as rows scroll in
368
- // and out of the rendered window because the auto-min/max
369
- // consults only the currently-rendered cells.
359
+ // - `userResized = false`: track depends on whether the consumer
360
+ // supplied an explicit `width` and / or `maxWidth`:
361
+ // - `widthIsFixed` (consumer-supplied `width`, no `maxWidth`):
362
+ // fixed `${width}px` track. Stays at exactly that pixel value
363
+ // regardless of leftover container width.
364
+ // - `maxWidth` set (with or without explicit width): the track
365
+ // becomes `minmax(${width}px, ${maxWidth}px)`, a bounded flex
366
+ // that grows up to `maxWidth` and is then capped.
367
+ // - Neither (the column is on its per-kind default width):
368
+ // `minmax(${width}px, 1fr)`, the column flexes to absorb any
369
+ // horizontal space the fixed-width tracks leave behind.
370
+ // This produces the "specify widths for the columns you care
371
+ // about; let the rest fill the gap" behaviour that mirrors the
372
+ // legacy `<table>` element when only some columns set a width.
370
373
  // - `userResized = true`: `${width}px` (fixed). The column sits
371
374
  // at exactly the size the user dragged it to, and dragging the
372
375
  // handle again only changes this column.
@@ -378,14 +381,23 @@
378
381
  const colsTemplate = $derived.by(() => {
379
382
  void tableContext.columnSizing;
380
383
  const track = (col: {
381
- columnDef: { width: number; maxWidth?: number; userResized: boolean };
384
+ columnDef: {
385
+ width: number;
386
+ maxWidth?: number;
387
+ userResized: boolean;
388
+ widthIsFixed: boolean;
389
+ };
382
390
  }) => {
383
391
  if (col.columnDef.userResized) {
384
392
  return `${col.columnDef.width}px`;
385
393
  }
386
- const upper =
387
- col.columnDef.maxWidth != null ? `${col.columnDef.maxWidth}px` : '1fr';
388
- return `minmax(${col.columnDef.width}px, ${upper})`;
394
+ if (col.columnDef.maxWidth != null) {
395
+ return `minmax(${col.columnDef.width}px, ${col.columnDef.maxWidth}px)`;
396
+ }
397
+ if (col.columnDef.widthIsFixed) {
398
+ return `${col.columnDef.width}px`;
399
+ }
400
+ return `minmax(${col.columnDef.width}px, 1fr)`;
389
401
  };
390
402
  const tracks: Array<string> = ['20px'];
391
403
  for (const col of leftColumns) {
@@ -53,6 +53,14 @@ export interface ColumnDefProps<T extends object, in FilterFnType, out OptionsTy
53
53
  * Cell content beyond the track is ellipsised, or wraps when
54
54
  * `wrapValue` is also set. */
55
55
  maxWidth?: Pixels;
56
+ /** Set by the column factory when the consumer supplied an explicit
57
+ * `width` (vs. the factory's per-kind default). The grid template
58
+ * treats an explicit width as fixed (`${width}px` track) and a
59
+ * per-kind default as flexible (`minmax(width, 1fr)` track), so a
60
+ * table whose specified widths total less than the container leaves
61
+ * the unspecified columns to absorb the trailing space rather than
62
+ * every column expanding by equal `1fr` shares. */
63
+ widthIsFixed?: boolean;
56
64
  /** When true, cell content that does not fit wraps onto new lines
57
65
  * and the row's height grows to fit. When false (default) overflow
58
66
  * is clipped with ellipsis. Surfaces as the `--wrap-value` CSS
@@ -148,6 +156,12 @@ export declare abstract class ColumnDef<T extends object, in FilterFnType = neve
148
156
  * clipped with ellipsis (false). */
149
157
  get wrapValue(): boolean;
150
158
  set wrapValue(v: boolean);
159
+ private _widthIsFixed;
160
+ /** Set by the column factory when the consumer supplied an explicit
161
+ * `width`. Drives the grid-template branch: true = fixed `${width}px`
162
+ * track, false = `minmax(width, 1fr)` flex track. */
163
+ get widthIsFixed(): boolean;
164
+ set widthIsFixed(v: boolean);
151
165
  private _isResizing;
152
166
  /** Whether the resize handle is currently active. */
153
167
  get isResizing(): boolean;
@@ -38,6 +38,7 @@ export class ColumnDef {
38
38
  this._width = $state(props.width ?? props.minSize ?? 48);
39
39
  this._maxWidth = $state(props.maxWidth);
40
40
  this._wrapValue = $state(props.wrapValue ?? false);
41
+ this._widthIsFixed = $state(props.widthIsFixed ?? false);
41
42
  this.isEditable = $derived(props.isEditable ?? (() => Promise.resolve(false)));
42
43
  this.resizable = $derived(props.resizable ?? true);
43
44
  this.header = $derived(props.header);
@@ -108,6 +109,16 @@ export class ColumnDef {
108
109
  set wrapValue(v) {
109
110
  this._wrapValue = v;
110
111
  }
112
+ _widthIsFixed;
113
+ /** Set by the column factory when the consumer supplied an explicit
114
+ * `width`. Drives the grid-template branch: true = fixed `${width}px`
115
+ * track, false = `minmax(width, 1fr)` flex track. */
116
+ get widthIsFixed() {
117
+ return this._widthIsFixed;
118
+ }
119
+ set widthIsFixed(v) {
120
+ this._widthIsFixed = v;
121
+ }
111
122
  _isResizing = $state(false);
112
123
  /** Whether the resize handle is currently active. */
113
124
  get isResizing() {
@@ -201,6 +201,13 @@ export interface BubbleColumnOptions<TRow extends object, TValue = unknown> exte
201
201
  * where the displayed values are strings but the server filter is a select.
202
202
  */
203
203
  filterValueType?: FilterValueType;
204
+ /**
205
+ * Option list for `filterValueType: 'Select'`. Drives the filter row's
206
+ * dropdown contents. Returning a `Promise` lets consumers fetch
207
+ * options lazily on first read; returning a plain array is also
208
+ * supported (scoria internally wraps it in `Promise.resolve`).
209
+ */
210
+ getOptions?: () => ReadonlyArray<SelectOption<TValue>> | Promise<ReadonlyArray<SelectOption<TValue>>>;
204
211
  }
205
212
  /** Options for `col.progress({ header, value, ... })`. Renders a horizontal
206
213
  * progress bar from a numeric ratio. */
@@ -216,6 +223,11 @@ export interface ProgressColumnOptions<TRow extends object> extends BaseColumnOp
216
223
  * "percent complete" field, in which case set this to `'Number'`.
217
224
  */
218
225
  filterValueType?: FilterValueType;
226
+ /**
227
+ * Option list for `filterValueType: 'Select'`. Drives the filter row's
228
+ * dropdown contents.
229
+ */
230
+ getOptions?: () => ReadonlyArray<SelectOption<unknown>> | Promise<ReadonlyArray<SelectOption<unknown>>>;
219
231
  }
220
232
  /** Options for `col.validity({ header, ... })`. Renders the row's overall
221
233
  * validity indicator and (optionally) opens a modal with the validity
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.14",
4
+ "version": "0.37.16",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },