@lavalogic/scoria 0.37.31 → 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.
- package/dist/Components/SingleSelect.svelte +0 -19
- package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +0 -24
- package/dist/Components/Table/ColumnFactory.svelte.js +10 -0
- package/dist/Components/Table/Types/Columns/Definitions/ColumnDef.svelte.d.ts +18 -0
- package/dist/Components/Table/Types/Columns/Definitions/ColumnDef.svelte.js +8 -0
- package/dist/Components/Table/Types/Context/TableContext.svelte.js +13 -0
- package/dist/Components/Table/Types/Public/ColumnOptions.d.ts +12 -0
- package/dist/Components/Table/createTable.svelte.js +0 -15
- package/dist/Helpers/dropdownPortal.js +32 -0
- package/package.json +1 -1
|
@@ -179,29 +179,10 @@
|
|
|
179
179
|
// svelecte always emits the full option object regardless of
|
|
180
180
|
// `valueAsObject`. Map to the shape the discriminated variant
|
|
181
181
|
// promised: primitive for AsPrimitive, object for AsObject.
|
|
182
|
-
if (typeof window !== 'undefined') {
|
|
183
|
-
// eslint-disable-next-line no-console
|
|
184
|
-
console.debug('%c[scoria/SingleSelect] svelecte onChange fired', 'color:#a0a', {
|
|
185
|
-
inputId,
|
|
186
|
-
name,
|
|
187
|
-
newValue,
|
|
188
|
-
newValueType: typeof newValue,
|
|
189
|
-
valueAsObject,
|
|
190
|
-
valueProp,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
182
|
if (valueAsObject) {
|
|
194
183
|
void (onchange as (v: V | null) => void | Promise<void>)(newValue);
|
|
195
184
|
} else {
|
|
196
185
|
const primitive = newValue == null ? null : newValue[valueProp];
|
|
197
|
-
if (typeof window !== 'undefined') {
|
|
198
|
-
// eslint-disable-next-line no-console
|
|
199
|
-
console.debug(
|
|
200
|
-
'%c[scoria/SingleSelect] calling outer onchange with primitive',
|
|
201
|
-
'color:#a0a',
|
|
202
|
-
{ primitive }
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
186
|
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
206
187
|
void (onchange as (v: V[VP] | null) => void | Promise<void>)(primitive);
|
|
207
188
|
}
|
|
@@ -145,32 +145,8 @@
|
|
|
145
145
|
// V (from `value: T`) clashes with the option type
|
|
146
146
|
// (`SelectOption<T>`); the casts let `V` be inferred
|
|
147
147
|
// from the explicit `onchange` annotation.
|
|
148
|
-
if (dev) {
|
|
149
|
-
console.debug(
|
|
150
|
-
'%c[scoria/TableSelect] onchange fired',
|
|
151
|
-
'color:#0a0',
|
|
152
|
-
{
|
|
153
|
-
columnId: cell.column.columnDef.id,
|
|
154
|
-
newValue,
|
|
155
|
-
newValueType: typeof newValue,
|
|
156
|
-
oldValue: value,
|
|
157
|
-
optional,
|
|
158
|
-
willEdit: newValue !== value && (optional || newValue != null),
|
|
159
|
-
}
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
148
|
if (newValue !== value) {
|
|
163
149
|
if (optional || newValue != null) {
|
|
164
|
-
if (dev) {
|
|
165
|
-
console.debug(
|
|
166
|
-
'%c[scoria/TableSelect] calling tableContext.onEditCell',
|
|
167
|
-
'color:#0a0',
|
|
168
|
-
{
|
|
169
|
-
coords: $state.snapshot(editable.focusDetails.coordinates),
|
|
170
|
-
value: newValue,
|
|
171
|
-
}
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
150
|
tableContext
|
|
175
151
|
.onEditCell(cell.row.original, editable.focusDetails.coordinates, newValue ?? null)
|
|
176
152
|
.catch(devCatch);
|
|
@@ -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. */
|
|
@@ -255,24 +255,9 @@ function makeOnEditCell(editHandlers, onAnyEdit) {
|
|
|
255
255
|
}
|
|
256
256
|
return async (item, coords, value) => {
|
|
257
257
|
const handler = editHandlers.get(coords.id);
|
|
258
|
-
if (typeof window !== 'undefined') {
|
|
259
|
-
// eslint-disable-next-line no-console
|
|
260
|
-
console.debug('%c[scoria/makeOnEditCell] dispatching', 'color:#08f', {
|
|
261
|
-
coordsId: coords.id,
|
|
262
|
-
value,
|
|
263
|
-
handlerFound: !!handler,
|
|
264
|
-
registeredHandlerKeys: Array.from(editHandlers.keys()),
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
258
|
if (handler) {
|
|
268
259
|
try {
|
|
269
260
|
await handler(item, value, coords);
|
|
270
|
-
if (typeof window !== 'undefined') {
|
|
271
|
-
// eslint-disable-next-line no-console
|
|
272
|
-
console.debug('%c[scoria/makeOnEditCell] handler resolved', 'color:#08f', {
|
|
273
|
-
coordsId: coords.id,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
261
|
}
|
|
277
262
|
catch (e) {
|
|
278
263
|
devCatch(e);
|
|
@@ -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
|