@lavalogic/scoria 0.37.33 → 0.37.35
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.
|
@@ -322,10 +322,13 @@
|
|
|
322
322
|
box-sizing: border-box;
|
|
323
323
|
text-align: center;
|
|
324
324
|
--sv-border: none;
|
|
325
|
+
min-width: 0;
|
|
326
|
+
overflow: hidden;
|
|
325
327
|
}
|
|
326
328
|
.select-cell :global(.select-wrapper) {
|
|
327
329
|
--sv-min-height: 0px;
|
|
328
330
|
--sv-general-padding: 0px;
|
|
329
331
|
--sv-item-wrap-padding: 0px 0.75rem;
|
|
330
332
|
--input-height: var(--height);
|
|
333
|
+
min-width: 0;
|
|
331
334
|
}</style>
|
|
@@ -294,10 +294,13 @@
|
|
|
294
294
|
box-sizing: border-box;
|
|
295
295
|
text-align: center;
|
|
296
296
|
--sv-border: none;
|
|
297
|
+
min-width: 0;
|
|
298
|
+
overflow: hidden;
|
|
297
299
|
}
|
|
298
300
|
.select-cell :global(.select-wrapper) {
|
|
299
301
|
--sv-min-height: 0px;
|
|
300
302
|
--sv-general-padding: 0px;
|
|
301
303
|
--sv-item-wrap-padding: 0px 0.75rem;
|
|
302
304
|
--input-height: var(--height);
|
|
305
|
+
min-width: 0;
|
|
303
306
|
}</style>
|
|
@@ -138,35 +138,54 @@ export function attachDropdownPortal(options) {
|
|
|
138
138
|
const handleResize = () => {
|
|
139
139
|
throttled.schedule();
|
|
140
140
|
};
|
|
141
|
+
// Track whether the dropdown has been moved to the portal target
|
|
142
|
+
// at least once. We move it AT MOUNT and leave it there for the
|
|
143
|
+
// lifetime of the SingleSelect, regardless of open / close. This
|
|
144
|
+
// avoids two bugs the older "move on every open, move back on every
|
|
145
|
+
// close" implementation suffered from:
|
|
146
|
+
// 1. Svelte 5's per-component event delegation walks the live
|
|
147
|
+
// DOM parentNode chain at dispatch time; moving the dropdown
|
|
148
|
+
// mid-click left the chain pointing at the old parent and
|
|
149
|
+
// Svelecte's `on_click` selection handler never ran, so mouse
|
|
150
|
+
// clicks did not select an option (keyboard Enter did,
|
|
151
|
+
// because keydown is wired on the input, not the dropdown).
|
|
152
|
+
// 2. The MutationObserver that toggled the move based on the
|
|
153
|
+
// `is-open` class fired between `mousedown` and `click`. The
|
|
154
|
+
// observer's microtask moved the dropdown back to its
|
|
155
|
+
// original parent before the `click` event reached an option,
|
|
156
|
+
// so the click resolved against `body` instead of the
|
|
157
|
+
// dropdown item.
|
|
158
|
+
// Keeping the element in the portal target the whole time also
|
|
159
|
+
// puts the dropdown in the body's stacking context unconditionally,
|
|
160
|
+
// which means a `z-index: 10000` actually wins against the table's
|
|
161
|
+
// own sticky-footer / pinned-column stacking contexts (the
|
|
162
|
+
// `position: fixed`-only variant suffered from that footer being
|
|
163
|
+
// painted ABOVE the open option list).
|
|
164
|
+
let didMoveToPortalTarget = false;
|
|
165
|
+
const moveToPortalTargetOnce = () => {
|
|
166
|
+
if (didMoveToPortalTarget) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// Append to the nearest open `<dialog>` ancestor if one exists
|
|
170
|
+
// (so the dropdown ends up in the dialog's top-layer stacking
|
|
171
|
+
// context and is not painted behind the dialog's backdrop /
|
|
172
|
+
// content), otherwise to `document.body`.
|
|
173
|
+
const dialogHost = trigger.closest('dialog');
|
|
174
|
+
const portalTarget = dialogHost && dialogHost.open ? dialogHost : document.body;
|
|
175
|
+
portalTarget.appendChild(dropdown);
|
|
176
|
+
didMoveToPortalTarget = true;
|
|
177
|
+
};
|
|
141
178
|
const portalOut = () => {
|
|
142
179
|
if (isPortalled) {
|
|
143
180
|
return;
|
|
144
181
|
}
|
|
145
|
-
|
|
146
|
-
//
|
|
147
|
-
//
|
|
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.
|
|
182
|
+
moveToPortalTargetOnce();
|
|
183
|
+
// Clear the defensive `display: none` that `portalIn` may have
|
|
184
|
+
// applied; the dropdown is opening again now.
|
|
165
185
|
dropdown.style.display = '';
|
|
166
|
-
// Marker
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
// clicks landing here as part of the trigger".
|
|
186
|
+
// Marker so consumers (e.g. the TableSidebar "click-outside"
|
|
187
|
+
// handler) can recognise clicks landing inside the dropdown as
|
|
188
|
+
// part of the trigger, not as outside-clicks.
|
|
170
189
|
dropdown.setAttribute('data-scoria-portal-dropdown', '');
|
|
171
190
|
isPortalled = true;
|
|
172
191
|
// First-frame measurement: lay out, then reposition again on
|
|
@@ -189,8 +208,7 @@ export function attachDropdownPortal(options) {
|
|
|
189
208
|
window.removeEventListener('scroll', handleScroll, { capture: true });
|
|
190
209
|
window.removeEventListener('resize', handleResize);
|
|
191
210
|
// Strip the inline overrides so Svelecte's next open starts
|
|
192
|
-
// from a clean slate
|
|
193
|
-
// its Svelecte-managed `display: none` slot).
|
|
211
|
+
// from a clean slate.
|
|
194
212
|
dropdown.style.position = '';
|
|
195
213
|
dropdown.style.top = '';
|
|
196
214
|
dropdown.style.left = '';
|
|
@@ -202,17 +220,20 @@ export function attachDropdownPortal(options) {
|
|
|
202
220
|
dropdown.style.zIndex = '';
|
|
203
221
|
dropdown.removeAttribute('data-scoria-portal-dropdown');
|
|
204
222
|
// Defensive: if Svelecte left the dropdown without its own
|
|
205
|
-
// `display: none` (
|
|
206
|
-
//
|
|
207
|
-
// out of order), the dropdown would
|
|
208
|
-
// display: block` and contribute
|
|
209
|
-
// the
|
|
210
|
-
// page-level
|
|
211
|
-
// `display: none` here; Svelecte will restore the inline style
|
|
212
|
-
// on next open via its own class-driven render.
|
|
223
|
+
// `display: none` (transition-cancel race paths where the
|
|
224
|
+
// open/close class flip and the inline style update arrive
|
|
225
|
+
// out of order), the dropdown would otherwise stay `position:
|
|
226
|
+
// static; display: block` in `document.body` and contribute
|
|
227
|
+
// its option-list height to the body scroll size, surfacing
|
|
228
|
+
// as a stray page-level scrollbar after toggling a select.
|
|
213
229
|
if (!dropdown.classList.contains('is-open')) {
|
|
214
230
|
dropdown.style.display = 'none';
|
|
215
231
|
}
|
|
232
|
+
// Note: we do NOT move the dropdown back to its original
|
|
233
|
+
// parent. Moving mid-click races with the click event (see the
|
|
234
|
+
// `moveToPortalTargetOnce` comment above). The dropdown stays
|
|
235
|
+
// in the portal target for the rest of its lifetime and is
|
|
236
|
+
// removed on component unmount.
|
|
216
237
|
isPortalled = false;
|
|
217
238
|
};
|
|
218
239
|
// Apply / unapply the portal whenever Svelecte toggles the
|
|
@@ -240,5 +261,46 @@ export function attachDropdownPortal(options) {
|
|
|
240
261
|
return () => {
|
|
241
262
|
observer.disconnect();
|
|
242
263
|
portalIn();
|
|
264
|
+
// Final cleanup: the dropdown was moved to the portal target
|
|
265
|
+
// at first open and stayed there for the component's lifetime
|
|
266
|
+
// (see `moveToPortalTargetOnce`). When the SingleSelect
|
|
267
|
+
// unmounts, take the dropdown out of the portal target so it
|
|
268
|
+
// is not orphaned in the document.
|
|
269
|
+
if (didMoveToPortalTarget && dropdown.parentNode) {
|
|
270
|
+
dropdown.parentNode.removeChild(dropdown);
|
|
271
|
+
}
|
|
243
272
|
};
|
|
244
273
|
}
|
|
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
|
+
};
|
package/dist/scss/_mixins.scss
CHANGED
|
@@ -1745,12 +1745,33 @@
|
|
|
1745
1745
|
box-sizing: border-box;
|
|
1746
1746
|
text-align: center;
|
|
1747
1747
|
--sv-border: none;
|
|
1748
|
+
// Suppress the cell's content-driven `min-width: auto`. Without
|
|
1749
|
+
// this, an open select-cell whose Svelecte input is rendering a
|
|
1750
|
+
// long selected-value label (e.g. `TEST-SA1-01 (line ID
|
|
1751
|
+
// 525504)` plus the clear-x icon) reports its `min-content` as
|
|
1752
|
+
// the full label width. With the column grid track sized
|
|
1753
|
+
// `minmax(W, 1fr)`, the track minimum becomes `max(W,
|
|
1754
|
+
// min-content)` and grows past the declared `W`, which forces
|
|
1755
|
+
// every other `1fr` column to shrink in turn (the visible
|
|
1756
|
+
// "all columns resize when the SKU dropdown is opened on a
|
|
1757
|
+
// row that already has a value" symptom). `min-width: 0` makes
|
|
1758
|
+
// the cell honour the column's declared minimum instead.
|
|
1759
|
+
min-width: 0;
|
|
1760
|
+
// Pair with `overflow: hidden` so the input inside cannot
|
|
1761
|
+
// visually overflow the cell once it is forced narrower than
|
|
1762
|
+
// its content - the label clips with ellipsis instead.
|
|
1763
|
+
overflow: hidden;
|
|
1748
1764
|
|
|
1749
1765
|
:global(.select-wrapper) {
|
|
1750
1766
|
--sv-min-height: 0px;
|
|
1751
1767
|
--sv-general-padding: 0px;
|
|
1752
1768
|
--sv-item-wrap-padding: 0px 0.75rem;
|
|
1753
1769
|
--input-height: var(--height);
|
|
1770
|
+
// `min-width: 0` here too: `.select-wrapper` is a flex
|
|
1771
|
+
// container by default in the regular SingleSelect, but
|
|
1772
|
+
// inside the table cell it should likewise relinquish
|
|
1773
|
+
// its content-driven minimum so the cell shrink works.
|
|
1774
|
+
min-width: 0;
|
|
1754
1775
|
}
|
|
1755
1776
|
}
|
|
1756
1777
|
}
|