@lavalogic/scoria 0.37.31 → 0.37.33
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 +29 -42
- 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);
|
|
@@ -90,8 +90,6 @@ export function attachDropdownPortal(options) {
|
|
|
90
90
|
/* nothing wired up */
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
-
let originalParent = null;
|
|
94
|
-
let originalNextSibling = null;
|
|
95
93
|
let isPortalled = false;
|
|
96
94
|
const reposition = () => {
|
|
97
95
|
if (!isPortalled) {
|
|
@@ -144,32 +142,31 @@ export function attachDropdownPortal(options) {
|
|
|
144
142
|
if (isPortalled) {
|
|
145
143
|
return;
|
|
146
144
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
145
|
+
// PREVIOUSLY: this helper moved the dropdown to `document.body`
|
|
146
|
+
// (or to the open `<dialog>` ancestor) via `appendChild` so it
|
|
147
|
+
// would escape ancestor `overflow: hidden` / `z-index` clipping.
|
|
148
|
+
// That hop broke Svelte 5's per-component event delegation:
|
|
149
|
+
// inline `onmousedown` / `onclick` handlers in Svelecte's
|
|
150
|
+
// template (specifically the `.sv_dropdown` element) are wired
|
|
151
|
+
// through a delegated dispatch rooted at the component's
|
|
152
|
+
// original mount point, and the dispatch's parent-chain walk
|
|
153
|
+
// never reaches the moved element. The visible symptom: mouse
|
|
154
|
+
// clicks on dropdown options did not select (keyboard Enter
|
|
155
|
+
// did, because that path bypasses the dropdown's click
|
|
156
|
+
// handler).
|
|
157
|
+
//
|
|
158
|
+
// We now leave the dropdown in its original DOM location and
|
|
159
|
+
// only flip it to `position: fixed`. `position: fixed` escapes
|
|
160
|
+
// ancestor `overflow` clipping (the original motivation for
|
|
161
|
+
// portaling) without removing the element from its component
|
|
162
|
+
// tree, so Svelte's delegated dispatch continues to find it.
|
|
163
|
+
// The reposition logic below tracks the trigger via
|
|
164
|
+
// `getBoundingClientRect` exactly as before.
|
|
152
165
|
dropdown.style.display = '';
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
// Without this hop the dropdown's `z-index: 10000` was still painted
|
|
158
|
-
// behind the dialog backdrop and its content (the symptom that
|
|
159
|
-
// surfaced as "select options disappear behind the allocate modal").
|
|
160
|
-
const dialogHost = trigger.closest('dialog');
|
|
161
|
-
const portalTarget = dialogHost && dialogHost.open ? dialogHost : document.body;
|
|
162
|
-
portalTarget.appendChild(dropdown);
|
|
163
|
-
// Tag the teleported dropdown so any "click outside the
|
|
164
|
-
// panel" / "click outside the trigger" handler elsewhere in
|
|
165
|
-
// the codebase can recognise the dropdown as a logical
|
|
166
|
-
// extension of its trigger and ignore clicks landing inside
|
|
167
|
-
// it. The `TableSidebar` side-panel uses this marker (along
|
|
168
|
-
// with `closest('.sv_dropdown')`) so clicking an option in a
|
|
169
|
-
// teleported filter-row dropdown does not close the
|
|
170
|
-
// side-panel under it. Components that want to ignore
|
|
171
|
-
// portal clicks should check
|
|
172
|
-
// `el.closest('[data-scoria-portal-dropdown]')`.
|
|
166
|
+
// Marker preserved for any consumer that still uses it (e.g.
|
|
167
|
+
// the TableSidebar "click-outside" handler). The dropdown is
|
|
168
|
+
// no longer literally portaled, but the marker means "treat
|
|
169
|
+
// clicks landing here as part of the trigger".
|
|
173
170
|
dropdown.setAttribute('data-scoria-portal-dropdown', '');
|
|
174
171
|
isPortalled = true;
|
|
175
172
|
// First-frame measurement: lay out, then reposition again on
|
|
@@ -208,24 +205,14 @@ export function attachDropdownPortal(options) {
|
|
|
208
205
|
// `display: none` (e.g. on certain transition-cancel race paths
|
|
209
206
|
// the open/close class flip and the inline style update arrive
|
|
210
207
|
// out of order), the dropdown would now be `position: static;
|
|
211
|
-
// display: block`
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
208
|
+
// display: block` and contribute its option list's height to
|
|
209
|
+
// the page scroll size, which is what surfaces as a stray
|
|
210
|
+
// page-level vertical scrollbar after toggling a select. Force
|
|
211
|
+
// `display: none` here; Svelecte will restore the inline style
|
|
212
|
+
// on next open via its own class-driven render.
|
|
216
213
|
if (!dropdown.classList.contains('is-open')) {
|
|
217
214
|
dropdown.style.display = 'none';
|
|
218
215
|
}
|
|
219
|
-
if (originalParent != null) {
|
|
220
|
-
if (originalNextSibling != null && originalNextSibling.parentNode === originalParent) {
|
|
221
|
-
originalParent.insertBefore(dropdown, originalNextSibling);
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
originalParent.appendChild(dropdown);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
originalParent = null;
|
|
228
|
-
originalNextSibling = null;
|
|
229
216
|
isPortalled = false;
|
|
230
217
|
};
|
|
231
218
|
// Apply / unapply the portal whenever Svelecte toggles the
|