@lavalogic/scoria 0.37.43 → 0.37.44
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.
|
@@ -288,23 +288,31 @@
|
|
|
288
288
|
let showFromDateModal = $state(false);
|
|
289
289
|
let showToDateModal = $state(false);
|
|
290
290
|
|
|
291
|
-
function generateOnChangeObject(
|
|
291
|
+
function generateOnChangeObject(_label: string) {
|
|
292
292
|
return (newValue: SelectOption<unknown> | null) => {
|
|
293
293
|
tableContext.paginationRepo?.filterBy(def.id, newValue?.value ?? null);
|
|
294
|
-
|
|
294
|
+
// Cache by `def.id` (which equals `customFilter.id`) so the
|
|
295
|
+
// `$effect` lookup `__queryValues.get(customFilter.id)`
|
|
296
|
+
// finds it on the next reactive pass. Previously we keyed
|
|
297
|
+
// the cache by the human-readable `label`, so the effect's
|
|
298
|
+
// lookup returned `undefined` and the QuerySelect was
|
|
299
|
+
// immediately reset to the placeholder.
|
|
300
|
+
tableContext.dataRepo.__queryValues.set(def.id, newValue);
|
|
295
301
|
assertNotPromise(newValue);
|
|
296
302
|
value = newValue;
|
|
297
303
|
};
|
|
298
304
|
}
|
|
299
305
|
|
|
300
|
-
function generateOnChangePrimitive(
|
|
306
|
+
function generateOnChangePrimitive(_label: string) {
|
|
301
307
|
// Phase 7.6: AsPrimitive QuerySelect now hands us the primitive
|
|
302
308
|
// directly (previously typed as `SelectOption` with a hidden
|
|
303
309
|
// `.value` dig). The discriminated typing makes the flow
|
|
304
310
|
// straightforward.
|
|
305
311
|
return (newValue: unknown) => {
|
|
306
312
|
tableContext.paginationRepo?.filterBy(def.id, newValue ?? null);
|
|
307
|
-
|
|
313
|
+
// See `generateOnChangeObject` - cache key matches the
|
|
314
|
+
// `$effect` lookup above.
|
|
315
|
+
tableContext.dataRepo.__queryValues.set(def.id, newValue ?? null);
|
|
308
316
|
assertNotPromise(newValue);
|
|
309
317
|
value = newValue ?? null;
|
|
310
318
|
};
|
|
@@ -294,22 +294,31 @@
|
|
|
294
294
|
};
|
|
295
295
|
});
|
|
296
296
|
|
|
297
|
-
function generateOnChangeObject(
|
|
297
|
+
function generateOnChangeObject(_label: string) {
|
|
298
298
|
return (newValue: SelectOption<unknown> | null) => {
|
|
299
299
|
tableContext.paginationRepo?.filterBy(def.id, newValue?.value ?? null);
|
|
300
|
-
|
|
300
|
+
// Cache by the column id (which is what the `$effect` above
|
|
301
|
+
// reads back with `__queryValues.get(customFilter.id)`).
|
|
302
|
+
// Previously we cached by the human-readable `label`, so the
|
|
303
|
+
// effect's lookup returned `undefined` and immediately
|
|
304
|
+
// overwrote `value` with `null` - the QuerySelect would
|
|
305
|
+
// briefly flash the chosen option then snap back to the
|
|
306
|
+
// "Please Select an Item" placeholder.
|
|
307
|
+
tableContext.dataRepo.__queryValues.set(def.id, newValue ?? null);
|
|
301
308
|
value = newValue ?? null;
|
|
302
309
|
};
|
|
303
310
|
}
|
|
304
311
|
|
|
305
|
-
function generateOnChangePrimitive(
|
|
312
|
+
function generateOnChangePrimitive(_label: string) {
|
|
306
313
|
// Phase 7.6: AsPrimitive QuerySelect now hands us the primitive
|
|
307
314
|
// directly. The previous code typed this as `SelectOption<unknown>`
|
|
308
315
|
// and read `.value` off it; with the discriminated typing the
|
|
309
316
|
// primitive flows straight through.
|
|
310
317
|
return (newValue: unknown) => {
|
|
311
318
|
tableContext.paginationRepo?.filterBy(def.id, newValue ?? null);
|
|
312
|
-
|
|
319
|
+
// See note in `generateOnChangeObject` - keyed by `def.id`
|
|
320
|
+
// to match the `$effect` lookup.
|
|
321
|
+
tableContext.dataRepo.__queryValues.set(def.id, newValue ?? null);
|
|
313
322
|
value = newValue ?? null;
|
|
314
323
|
};
|
|
315
324
|
}
|
|
@@ -32,14 +32,19 @@
|
|
|
32
32
|
* 3. Listens for capture-phase `scroll` events and for viewport
|
|
33
33
|
* `resize` events so the position tracks the trigger while
|
|
34
34
|
* the dropdown is open. Repositioning is rAF-throttled.
|
|
35
|
-
* 4. Applies a very large `z-index` to the dropdown
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
35
|
+
* 4. Applies a very large `z-index` to the dropdown AND
|
|
36
|
+
* temporarily lifts the dropdown's nearest stacking-context
|
|
37
|
+
* ancestor (the sticky / isolated table strip that contains
|
|
38
|
+
* the trigger) so the dropdown's z-index can actually win
|
|
39
|
+
* against other peer stacking contexts in the same parent.
|
|
40
|
+
* Without the lift the dropdown's z-index was trapped inside
|
|
41
|
+
* its ancestor's own context and could be hidden by an
|
|
42
|
+
* adjacent sibling stacking context with even a small
|
|
43
|
+
* z-index - which is exactly what happened with the table's
|
|
44
|
+
* sticky footer covering open cell dropdowns (or, after the
|
|
45
|
+
* initial fix, the table body covering open footer
|
|
46
|
+
* dropdowns). The lift is ref-counted so two dropdowns that
|
|
47
|
+
* share an ancestor coordinate cleanly.
|
|
43
48
|
*/
|
|
44
49
|
export interface DropdownPortalOptions {
|
|
45
50
|
/** The Svelecte root wrapper `<div class="svelecte">`. Used as the
|
|
@@ -32,14 +32,19 @@
|
|
|
32
32
|
* 3. Listens for capture-phase `scroll` events and for viewport
|
|
33
33
|
* `resize` events so the position tracks the trigger while
|
|
34
34
|
* the dropdown is open. Repositioning is rAF-throttled.
|
|
35
|
-
* 4. Applies a very large `z-index` to the dropdown
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
35
|
+
* 4. Applies a very large `z-index` to the dropdown AND
|
|
36
|
+
* temporarily lifts the dropdown's nearest stacking-context
|
|
37
|
+
* ancestor (the sticky / isolated table strip that contains
|
|
38
|
+
* the trigger) so the dropdown's z-index can actually win
|
|
39
|
+
* against other peer stacking contexts in the same parent.
|
|
40
|
+
* Without the lift the dropdown's z-index was trapped inside
|
|
41
|
+
* its ancestor's own context and could be hidden by an
|
|
42
|
+
* adjacent sibling stacking context with even a small
|
|
43
|
+
* z-index - which is exactly what happened with the table's
|
|
44
|
+
* sticky footer covering open cell dropdowns (or, after the
|
|
45
|
+
* initial fix, the table body covering open footer
|
|
46
|
+
* dropdowns). The lift is ref-counted so two dropdowns that
|
|
47
|
+
* share an ancestor coordinate cleanly.
|
|
43
48
|
*/
|
|
44
49
|
/**
|
|
45
50
|
* Z-index applied to the open dropdown. Picked very high so the
|
|
@@ -48,6 +53,108 @@
|
|
|
48
53
|
* stays below modal overlays (`z 99999` in `Action`-based popovers).
|
|
49
54
|
*/
|
|
50
55
|
const PORTAL_Z_INDEX = 99998;
|
|
56
|
+
/**
|
|
57
|
+
* Z-index applied to the dropdown's nearest stacking-context ancestor
|
|
58
|
+
* while the dropdown is open. One below `PORTAL_Z_INDEX` so the
|
|
59
|
+
* dropdown still wins inside its own stacking context, but high
|
|
60
|
+
* enough that the whole ancestor block paints above its peer stacking
|
|
61
|
+
* contexts (e.g. the table footer's `position: sticky; z-index: 10`
|
|
62
|
+
* row, or the table body's `isolation: isolate; z-index: 20`
|
|
63
|
+
* container). Without this lift, the dropdown's `z-index: 99998` was
|
|
64
|
+
* trapped inside the ancestor's own stacking context and could not
|
|
65
|
+
* paint above a sibling stacking context with even a small z-index.
|
|
66
|
+
*
|
|
67
|
+
* The previous workaround pinned `.datatable-grid` at `z-index: 20`
|
|
68
|
+
* so its cell dropdowns sat above the footer; that fixed the cell
|
|
69
|
+
* case but reversed the failure for the footer's `Records-per-Page`
|
|
70
|
+
* select, whose dropdown opens upwards into the rows. Lifting the
|
|
71
|
+
* dropdown's actual ancestor at open time means both cases work
|
|
72
|
+
* without either side needing a hard-coded z-index bias.
|
|
73
|
+
*/
|
|
74
|
+
const STACKING_LIFT_Z_INDEX = 99997;
|
|
75
|
+
/**
|
|
76
|
+
* Walk up the DOM from `el` and return the first ancestor that
|
|
77
|
+
* establishes a new CSS stacking context. The dropdown's z-index
|
|
78
|
+
* only competes inside this ancestor's context, so lifting this
|
|
79
|
+
* element is what determines whether the dropdown can paint above a
|
|
80
|
+
* sibling sticky / isolated block. Stops at `document.body` (which
|
|
81
|
+
* doesn't form a useful pivot for the lift).
|
|
82
|
+
*/
|
|
83
|
+
function findStackingContextAncestor(el) {
|
|
84
|
+
let current = el.parentElement;
|
|
85
|
+
while (current && current !== document.body) {
|
|
86
|
+
if (createsStackingContext(current)) {
|
|
87
|
+
return current;
|
|
88
|
+
}
|
|
89
|
+
current = current.parentElement;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* True when the computed style of `el` causes the browser to
|
|
95
|
+
* establish a new stacking context. Covers the cases that actually
|
|
96
|
+
* appear in scoria today (sticky / isolated table strips, fixed
|
|
97
|
+
* popover ancestors). Not exhaustive against every CSS feature that
|
|
98
|
+
* triggers a stacking context, but tuned for our tree.
|
|
99
|
+
*/
|
|
100
|
+
function createsStackingContext(el) {
|
|
101
|
+
const cs = getComputedStyle(el);
|
|
102
|
+
if (cs.position === 'fixed' || cs.position === 'sticky') {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if ((cs.position === 'relative' || cs.position === 'absolute') &&
|
|
106
|
+
cs.zIndex !== 'auto') {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (cs.isolation === 'isolate') {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (cs.transform !== 'none') {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (cs.filter !== 'none') {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (cs.perspective !== 'none') {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
if (cs.mixBlendMode !== 'normal') {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (Number.parseFloat(cs.opacity) < 1) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (cs.willChange && cs.willChange !== 'auto') {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const raiseCounts = new WeakMap();
|
|
133
|
+
function liftStackingContext(el) {
|
|
134
|
+
let info = raiseCounts.get(el);
|
|
135
|
+
if (!info) {
|
|
136
|
+
info = { count: 0, originalInlineZIndex: el.style.zIndex };
|
|
137
|
+
raiseCounts.set(el, info);
|
|
138
|
+
el.style.zIndex = String(STACKING_LIFT_Z_INDEX);
|
|
139
|
+
}
|
|
140
|
+
info.count++;
|
|
141
|
+
let released = false;
|
|
142
|
+
return () => {
|
|
143
|
+
if (released) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
released = true;
|
|
147
|
+
const current = raiseCounts.get(el);
|
|
148
|
+
if (!current) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
current.count--;
|
|
152
|
+
if (current.count <= 0) {
|
|
153
|
+
el.style.zIndex = current.originalInlineZIndex;
|
|
154
|
+
raiseCounts.delete(el);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
51
158
|
/**
|
|
52
159
|
* Minimum free vertical space below the trigger before the dropdown
|
|
53
160
|
* is flipped to open upwards. Picked to fit the default Svelecte
|
|
@@ -109,6 +216,7 @@ export function attachDropdownPortal(options) {
|
|
|
109
216
|
};
|
|
110
217
|
}
|
|
111
218
|
let isOpen = false;
|
|
219
|
+
let releaseStackingLift = null;
|
|
112
220
|
const reposition = () => {
|
|
113
221
|
if (!isOpen) {
|
|
114
222
|
return;
|
|
@@ -175,6 +283,18 @@ export function attachDropdownPortal(options) {
|
|
|
175
283
|
return;
|
|
176
284
|
}
|
|
177
285
|
isOpen = true;
|
|
286
|
+
// Lift the dropdown's nearest stacking-context ancestor so the
|
|
287
|
+
// whole block paints above any sibling stacking contexts that
|
|
288
|
+
// would otherwise hide it. Without this, the dropdown's high
|
|
289
|
+
// z-index is trapped inside the ancestor's own stacking context.
|
|
290
|
+
// Resolved from `trigger` rather than `dropdown` because the
|
|
291
|
+
// dropdown's `position: fixed` makes it its own stacking
|
|
292
|
+
// context; we want to lift the surrounding sticky / isolated
|
|
293
|
+
// table strip, not the dropdown itself.
|
|
294
|
+
const stackingAncestor = findStackingContextAncestor(trigger);
|
|
295
|
+
if (stackingAncestor) {
|
|
296
|
+
releaseStackingLift = liftStackingContext(stackingAncestor);
|
|
297
|
+
}
|
|
178
298
|
dropdown.style.display = '';
|
|
179
299
|
dropdown.setAttribute('data-scoria-portal-dropdown', '');
|
|
180
300
|
reposition();
|
|
@@ -189,6 +309,10 @@ export function attachDropdownPortal(options) {
|
|
|
189
309
|
throttled.cancel();
|
|
190
310
|
window.removeEventListener('scroll', handleScroll, { capture: true });
|
|
191
311
|
window.removeEventListener('resize', handleResize);
|
|
312
|
+
if (releaseStackingLift) {
|
|
313
|
+
releaseStackingLift();
|
|
314
|
+
releaseStackingLift = null;
|
|
315
|
+
}
|
|
192
316
|
dropdown.style.position = '';
|
|
193
317
|
dropdown.style.top = '';
|
|
194
318
|
dropdown.style.left = '';
|