@lavalogic/scoria 0.37.30 → 0.37.32

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.
@@ -376,6 +376,16 @@ export function createColumnFactory() {
376
376
  filterValueType: opts.filterValueType ?? FilterValueType.String,
377
377
  getSortingFn: opts.getSortingFn,
378
378
  getOptions: normalisedOptions,
379
+ // Forward reactiveDependencies onto the def so
380
+ // `makeRow.getValue` can read them synchronously before
381
+ // calling the (possibly async) accessor. Without this,
382
+ // reactive reads that happen only after `await` inside
383
+ // the consumer's `value` callback are not tracked by the
384
+ // cell's `$derived`, and downstream display cells never
385
+ // refresh when a sibling field is edited (visible on the
386
+ // Receipt pod table: Title / UoM did not reload after the
387
+ // SKU select changed `item.receiptLineId`).
388
+ reactiveDependencies: opts.reactiveDependencies,
379
389
  });
380
390
  return makeSpec('display', def);
381
391
  },
@@ -92,6 +92,17 @@ export interface ColumnDefProps<T extends object, in FilterFnType, out OptionsTy
92
92
  * advanced filter panel. See `BaseColumnOptions.filterGroup` for
93
93
  * the consumer-facing description. */
94
94
  filterGroup?: string;
95
+ /**
96
+ * Optional thunk returning a list of additional reactive values the
97
+ * cell renderer should depend on. Read synchronously from
98
+ * `cell.getValue` before the (possibly async) `accessorFn` runs, so
99
+ * Svelte's `$derived` tracking captures these row-level reads even
100
+ * when the user's accessor reads them only after an `await` (where
101
+ * tracking is no longer active). Use when a column's displayed
102
+ * value depends on a sibling field that the accessor reads after a
103
+ * `Promise.then` / `await` boundary.
104
+ */
105
+ reactiveDependencies?: (row: T) => ReadonlyArray<unknown>;
95
106
  }
96
107
  /**
97
108
  * `ColumnDef<T, FilterFnType, OptionsType, TValue>` is the abstract base
@@ -137,6 +148,13 @@ export declare abstract class ColumnDef<T extends object, in FilterFnType = neve
137
148
  readonly isValid: ValidationFn<T> | undefined;
138
149
  /** Reads the row value. Required at the `AccessorDef` subclass layer. */
139
150
  readonly accessorFn: AccessorFn<T, TValue> | undefined;
151
+ /**
152
+ * Optional thunk returning a list of additional reactive values the
153
+ * cell should depend on. Read synchronously by `cell.getValue` so
154
+ * Svelte's `$derived` tracking picks them up even when the user's
155
+ * `accessorFn` accesses them only after an `await` boundary.
156
+ */
157
+ readonly reactiveDependencies: ((row: T) => ReadonlyArray<unknown>) | undefined;
140
158
  /** Filter predicate. Each subclass narrows the generic parameter. */
141
159
  abstract filterFn: FilterFn<T, FilterFnType> | undefined;
142
160
  private _minSize;
@@ -63,6 +63,7 @@ export class ColumnDef {
63
63
  // user function's synchronous portion). The per-Def WeakMap was
64
64
  // an extra correctness-eating layer.
65
65
  this.getDisplayValue = $derived(props.getDisplayValue);
66
+ this.reactiveDependencies = $derived(props.reactiveDependencies);
66
67
  this.debug = $derived(props.debug ?? false);
67
68
  this.getSortingFn = $derived(props.getSortingFn);
68
69
  this.getOptions = $derived(props.getOptions);
@@ -90,6 +91,13 @@ export class ColumnDef {
90
91
  isValid;
91
92
  /** Reads the row value. Required at the `AccessorDef` subclass layer. */
92
93
  accessorFn;
94
+ /**
95
+ * Optional thunk returning a list of additional reactive values the
96
+ * cell should depend on. Read synchronously by `cell.getValue` so
97
+ * Svelte's `$derived` tracking picks them up even when the user's
98
+ * `accessorFn` accesses them only after an `await` boundary.
99
+ */
100
+ reactiveDependencies;
93
101
  _minSize;
94
102
  /** Minimum width in pixels. */
95
103
  get minSize() {
@@ -1833,6 +1833,19 @@ export class TableContext {
1833
1833
  id: `${index}_${def.id}`,
1834
1834
  row: row,
1835
1835
  getValue: () => {
1836
+ // Read declared reactive dependencies synchronously so the
1837
+ // caller's `$derived` registers them even when the
1838
+ // (possibly async) accessor reads them only after an
1839
+ // `await` (where Svelte's tracking context is no longer
1840
+ // active). The returned tuple is read for its side-effect
1841
+ // of touching each entry through the row proxy; the
1842
+ // values themselves are discarded.
1843
+ const deps = def.reactiveDependencies?.(item);
1844
+ if (deps) {
1845
+ for (let i = 0; i < deps.length; i++) {
1846
+ void deps[i];
1847
+ }
1848
+ }
1836
1849
  const gotValue = def.accessorFn?.(item, index);
1837
1850
  if (gotValue !== undefined) {
1838
1851
  return gotValue;
@@ -184,6 +184,18 @@ export interface DisplayColumnOptions<TRow extends object, TValue = unknown> ext
184
184
  * types.
185
185
  */
186
186
  getOptions?: () => ReadonlyArray<SelectOption<TValue>> | Promise<ReadonlyArray<SelectOption<TValue>>>;
187
+ /**
188
+ * Optional thunk returning a list of additional reactive values
189
+ * this column's `value` callback depends on. Read synchronously
190
+ * by the cell renderer before invoking `value` so Svelte's
191
+ * `$derived` tracking captures those row-level reads, even when
192
+ * the `value` callback reads them only after an `await` boundary
193
+ * (where reactive tracking is no longer active). Use when the
194
+ * computed value depends on a sibling field that is read inside
195
+ * the async portion of `value` (e.g. `(row) => [row.fkId]` for a
196
+ * display column that fetches a foreign-key entity by id).
197
+ */
198
+ reactiveDependencies?: (row: TRow) => ReadonlyArray<unknown>;
187
199
  }
188
200
  /** Options for `col.bubble({ header, value, colour, ... })`. Renders one or
189
201
  * more coloured pill bubbles per row. */
@@ -140,6 +140,34 @@ export function attachDropdownPortal(options) {
140
140
  const handleResize = () => {
141
141
  throttled.schedule();
142
142
  };
143
+ /**
144
+ * Direct mousedown listener attached to the portaled dropdown that
145
+ * calls `preventDefault()` to keep focus on the Svelecte input while
146
+ * the user clicks an option.
147
+ *
148
+ * Svelecte already attaches `onmousedown={preventDefault}` to the
149
+ * `.sv_dropdown` element in template, but Svelte 5 wires inline
150
+ * event handlers through its delegated-event chain rooted at the
151
+ * original mount point. Once we `appendChild` the dropdown to
152
+ * `document.body` (or to a `<dialog>` ancestor) the delegated chain
153
+ * no longer finds this element, the inline handler never fires,
154
+ * the browser's default mousedown shifts focus to the option (or to
155
+ * `body`), Svelecte's `on_blur` runs `updateDropdownState(false)`
156
+ * which closes the dropdown, this helper's MutationObserver moves
157
+ * the dropdown back into place, and the `mouseup` / `click` events
158
+ * never reach an option element. The visible symptom: keyboard
159
+ * selection (Enter) works, mouse click does not.
160
+ *
161
+ * Adding a *direct* `addEventListener` on the dropdown bypasses
162
+ * Svelte's delegation entirely. The listener is reset every
163
+ * `portalOut` / `portalIn` cycle.
164
+ */
165
+ const preventFocusShiftOnMousedown = (e) => {
166
+ // Same intent as Svelecte's own `on_mouse_down`. Keep focus on
167
+ // the input so `on_blur` does not collapse the dropdown before
168
+ // the click event reaches its selection handler.
169
+ e.preventDefault();
170
+ };
143
171
  const portalOut = () => {
144
172
  if (isPortalled) {
145
173
  return;
@@ -171,6 +199,9 @@ export function attachDropdownPortal(options) {
171
199
  // portal clicks should check
172
200
  // `el.closest('[data-scoria-portal-dropdown]')`.
173
201
  dropdown.setAttribute('data-scoria-portal-dropdown', '');
202
+ // Restore the focus-preserving mousedown handler that Svelte's
203
+ // delegated event chain no longer reaches after the move.
204
+ dropdown.addEventListener('mousedown', preventFocusShiftOnMousedown);
174
205
  isPortalled = true;
175
206
  // First-frame measurement: lay out, then reposition again on
176
207
  // the next frame so we measure the dropdown's real
@@ -189,6 +220,7 @@ export function attachDropdownPortal(options) {
189
220
  return;
190
221
  }
191
222
  throttled.cancel();
223
+ dropdown.removeEventListener('mousedown', preventFocusShiftOnMousedown);
192
224
  window.removeEventListener('scroll', handleScroll, { capture: true });
193
225
  window.removeEventListener('resize', handleResize);
194
226
  // Strip the inline overrides so Svelecte's next open starts
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.30",
4
+ "version": "0.37.32",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },