@lavalogic/scoria 0.37.36 → 0.37.37
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.
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
*
|
|
11
11
|
* This helper sidesteps all of that:
|
|
12
12
|
*
|
|
13
|
-
* 1. On open, it reparents the dropdown to `document.body` (
|
|
14
|
-
* the
|
|
15
|
-
*
|
|
13
|
+
* 1. On first open, it reparents the dropdown to `document.body` (or
|
|
14
|
+
* to the nearest open `<dialog>` ancestor) ONCE and leaves it
|
|
15
|
+
* there for the lifetime of the SingleSelect. Moving the element
|
|
16
|
+
* on every open / close raced with the user's mouse click and
|
|
17
|
+
* broke Svelte 5's per-component event delegation, so options
|
|
18
|
+
* could only be selected with the keyboard.
|
|
16
19
|
* 2. It switches the dropdown to `position: fixed` and sets
|
|
17
20
|
* `top` / `left` / `width` / `bottom` from the trigger's
|
|
18
21
|
* `getBoundingClientRect()`.
|
|
@@ -23,9 +26,10 @@
|
|
|
23
26
|
* `resize` events so the position tracks the trigger while the
|
|
24
27
|
* dropdown is open. Repositioning is rAF-throttled to keep the
|
|
25
28
|
* cost off the scroll-event hot path.
|
|
26
|
-
* 5. On close, the
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
+
* 5. On close, the inline positioning styles are cleared and the
|
|
30
|
+
* dropdown is hidden with `display: none` (Svelecte will restore
|
|
31
|
+
* its own display rule on the next open via the class flip). The
|
|
32
|
+
* dropdown is NOT moved back to its original parent.
|
|
29
33
|
*
|
|
30
34
|
* The helper deliberately does NOT add the `is-open` class or fight
|
|
31
35
|
* Svelecte's own state machine. Svelecte still owns open / closed
|
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
*
|
|
11
11
|
* This helper sidesteps all of that:
|
|
12
12
|
*
|
|
13
|
-
* 1. On open, it reparents the dropdown to `document.body` (
|
|
14
|
-
* the
|
|
15
|
-
*
|
|
13
|
+
* 1. On first open, it reparents the dropdown to `document.body` (or
|
|
14
|
+
* to the nearest open `<dialog>` ancestor) ONCE and leaves it
|
|
15
|
+
* there for the lifetime of the SingleSelect. Moving the element
|
|
16
|
+
* on every open / close raced with the user's mouse click and
|
|
17
|
+
* broke Svelte 5's per-component event delegation, so options
|
|
18
|
+
* could only be selected with the keyboard.
|
|
16
19
|
* 2. It switches the dropdown to `position: fixed` and sets
|
|
17
20
|
* `top` / `left` / `width` / `bottom` from the trigger's
|
|
18
21
|
* `getBoundingClientRect()`.
|
|
@@ -23,9 +26,10 @@
|
|
|
23
26
|
* `resize` events so the position tracks the trigger while the
|
|
24
27
|
* dropdown is open. Repositioning is rAF-throttled to keep the
|
|
25
28
|
* cost off the scroll-event hot path.
|
|
26
|
-
* 5. On close, the
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
+
* 5. On close, the inline positioning styles are cleared and the
|
|
30
|
+
* dropdown is hidden with `display: none` (Svelecte will restore
|
|
31
|
+
* its own display rule on the next open via the class flip). The
|
|
32
|
+
* dropdown is NOT moved back to its original parent.
|
|
29
33
|
*
|
|
30
34
|
* The helper deliberately does NOT add the `is-open` class or fight
|
|
31
35
|
* Svelecte's own state machine. Svelecte still owns open / closed
|
|
@@ -139,28 +143,26 @@ export function attachDropdownPortal(options) {
|
|
|
139
143
|
throttled.schedule();
|
|
140
144
|
};
|
|
141
145
|
// Track whether the dropdown has been moved to the portal target
|
|
142
|
-
// at least once. We move it AT
|
|
143
|
-
// lifetime of the SingleSelect, regardless of open / close.
|
|
144
|
-
// avoids two bugs the older "move on every open, move back on
|
|
145
|
-
// close" implementation suffered from:
|
|
146
|
+
// at least once. We move it AT FIRST OPEN and leave it there for
|
|
147
|
+
// the lifetime of the SingleSelect, regardless of open / close.
|
|
148
|
+
// This avoids two bugs the older "move on every open, move back on
|
|
149
|
+
// every close" implementation suffered from:
|
|
146
150
|
// 1. Svelte 5's per-component event delegation walks the live
|
|
147
151
|
// DOM parentNode chain at dispatch time; moving the dropdown
|
|
148
152
|
// mid-click left the chain pointing at the old parent and
|
|
149
|
-
// Svelecte's `on_click` selection handler never ran, so
|
|
150
|
-
// clicks did not select an option (keyboard Enter did,
|
|
153
|
+
// Svelecte's `on_click` selection handler never ran, so
|
|
154
|
+
// mouse clicks did not select an option (keyboard Enter did,
|
|
151
155
|
// because keydown is wired on the input, not the dropdown).
|
|
152
156
|
// 2. The MutationObserver that toggled the move based on the
|
|
153
157
|
// `is-open` class fired between `mousedown` and `click`. The
|
|
154
158
|
// observer's microtask moved the dropdown back to its
|
|
155
|
-
// original parent before the `click` event reached an
|
|
156
|
-
// so the click resolved against `body` instead of
|
|
157
|
-
// dropdown item.
|
|
159
|
+
// original parent before the `click` event reached an
|
|
160
|
+
// option, so the click resolved against `body` instead of
|
|
161
|
+
// the dropdown item.
|
|
158
162
|
// Keeping the element in the portal target the whole time also
|
|
159
163
|
// puts the dropdown in the body's stacking context unconditionally,
|
|
160
164
|
// which means a `z-index: 10000` actually wins against the table's
|
|
161
|
-
// own sticky-footer / pinned-column stacking contexts
|
|
162
|
-
// `position: fixed`-only variant suffered from that footer being
|
|
163
|
-
// painted ABOVE the open option list).
|
|
165
|
+
// own sticky-footer / pinned-column stacking contexts.
|
|
164
166
|
let didMoveToPortalTarget = false;
|
|
165
167
|
const moveToPortalTargetOnce = () => {
|
|
166
168
|
if (didMoveToPortalTarget) {
|
|
@@ -222,18 +224,19 @@ export function attachDropdownPortal(options) {
|
|
|
222
224
|
// Defensive: if Svelecte left the dropdown without its own
|
|
223
225
|
// `display: none` (transition-cancel race paths where the
|
|
224
226
|
// open/close class flip and the inline style update arrive
|
|
225
|
-
// out of order), the dropdown would otherwise stay
|
|
226
|
-
// static; display: block` in
|
|
227
|
-
// its option-list height to the
|
|
228
|
-
// as a stray page-level scrollbar after
|
|
227
|
+
// out of order), the dropdown would otherwise stay
|
|
228
|
+
// `position: static; display: block` in the portal target
|
|
229
|
+
// and contribute its option-list height to the page scroll
|
|
230
|
+
// size, surfacing as a stray page-level scrollbar after
|
|
231
|
+
// toggling a select.
|
|
229
232
|
if (!dropdown.classList.contains('is-open')) {
|
|
230
233
|
dropdown.style.display = 'none';
|
|
231
234
|
}
|
|
232
235
|
// Note: we do NOT move the dropdown back to its original
|
|
233
|
-
// parent. Moving mid-click races with the click event (see
|
|
234
|
-
// `moveToPortalTargetOnce` comment above). The dropdown
|
|
235
|
-
// in the portal target for the rest of its lifetime
|
|
236
|
-
// removed on component unmount.
|
|
236
|
+
// parent. Moving mid-click races with the click event (see
|
|
237
|
+
// the `moveToPortalTargetOnce` comment above). The dropdown
|
|
238
|
+
// stays in the portal target for the rest of its lifetime
|
|
239
|
+
// and is removed on component unmount.
|
|
237
240
|
isPortalled = false;
|
|
238
241
|
};
|
|
239
242
|
// Apply / unapply the portal whenever Svelecte toggles the
|
|
@@ -262,45 +265,12 @@ export function attachDropdownPortal(options) {
|
|
|
262
265
|
observer.disconnect();
|
|
263
266
|
portalIn();
|
|
264
267
|
// Final cleanup: the dropdown was moved to the portal target
|
|
265
|
-
// at first open and stayed there for the component's
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
268
|
+
// at first open and stayed there for the component's
|
|
269
|
+
// lifetime. When the SingleSelect unmounts, take the dropdown
|
|
270
|
+
// out of the portal target so it is not orphaned in the
|
|
271
|
+
// document.
|
|
269
272
|
if (didMoveToPortalTarget && dropdown.parentNode) {
|
|
270
273
|
dropdown.parentNode.removeChild(dropdown);
|
|
271
274
|
}
|
|
272
275
|
};
|
|
273
276
|
}
|
|
274
|
-
tion;
|
|
275
|
-
of;
|
|
276
|
-
mutations;
|
|
277
|
-
{
|
|
278
|
-
if (mutation.attributeName !== 'class') {
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
if (dropdown.classList.contains('is-open')) {
|
|
282
|
-
portalOut();
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
portalIn();
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
;
|
|
289
|
-
observer.observe(dropdown, { attributes: true, attributeFilter: ['class'] });
|
|
290
|
-
// Honour the dropdown's initial state in case it mounted already
|
|
291
|
-
// open (e.g. `focusOnLoad`).
|
|
292
|
-
if (dropdown.classList.contains('is-open')) {
|
|
293
|
-
portalOut();
|
|
294
|
-
}
|
|
295
|
-
return () => {
|
|
296
|
-
observer.disconnect();
|
|
297
|
-
portalIn();
|
|
298
|
-
// Final cleanup: the dropdown was moved to the portal target
|
|
299
|
-
// at first open and stayed there for the component's lifetime
|
|
300
|
-
// (see `moveToPortalTargetOnce`). When the SingleSelect
|
|
301
|
-
// unmounts, take the dropdown out of the portal target so it
|
|
302
|
-
// is not orphaned in the document.
|
|
303
|
-
if (didMoveToPortalTarget && dropdown.parentNode) {
|
|
304
|
-
dropdown.parentNode.removeChild(dropdown);
|
|
305
|
-
}
|
|
306
|
-
};
|