@lavalogic/scoria 0.37.32 → 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>
|
|
@@ -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) {
|
|
@@ -140,68 +138,55 @@ export function attachDropdownPortal(options) {
|
|
|
140
138
|
const handleResize = () => {
|
|
141
139
|
throttled.schedule();
|
|
142
140
|
};
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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;
|
|
170
177
|
};
|
|
171
178
|
const portalOut = () => {
|
|
172
179
|
if (isPortalled) {
|
|
173
180
|
return;
|
|
174
181
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
// (see the defensive hide there) so the dropdown is visible
|
|
179
|
-
// once teleported into the new host.
|
|
182
|
+
moveToPortalTargetOnce();
|
|
183
|
+
// Clear the defensive `display: none` that `portalIn` may have
|
|
184
|
+
// applied; the dropdown is opening again now.
|
|
180
185
|
dropdown.style.display = '';
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
// above every z-indexed element in the regular stacking context.
|
|
185
|
-
// Without this hop the dropdown's `z-index: 10000` was still painted
|
|
186
|
-
// behind the dialog backdrop and its content (the symptom that
|
|
187
|
-
// surfaced as "select options disappear behind the allocate modal").
|
|
188
|
-
const dialogHost = trigger.closest('dialog');
|
|
189
|
-
const portalTarget = dialogHost && dialogHost.open ? dialogHost : document.body;
|
|
190
|
-
portalTarget.appendChild(dropdown);
|
|
191
|
-
// Tag the teleported dropdown so any "click outside the
|
|
192
|
-
// panel" / "click outside the trigger" handler elsewhere in
|
|
193
|
-
// the codebase can recognise the dropdown as a logical
|
|
194
|
-
// extension of its trigger and ignore clicks landing inside
|
|
195
|
-
// it. The `TableSidebar` side-panel uses this marker (along
|
|
196
|
-
// with `closest('.sv_dropdown')`) so clicking an option in a
|
|
197
|
-
// teleported filter-row dropdown does not close the
|
|
198
|
-
// side-panel under it. Components that want to ignore
|
|
199
|
-
// portal clicks should check
|
|
200
|
-
// `el.closest('[data-scoria-portal-dropdown]')`.
|
|
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.
|
|
201
189
|
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);
|
|
205
190
|
isPortalled = true;
|
|
206
191
|
// First-frame measurement: lay out, then reposition again on
|
|
207
192
|
// the next frame so we measure the dropdown's real
|
|
@@ -220,12 +205,10 @@ export function attachDropdownPortal(options) {
|
|
|
220
205
|
return;
|
|
221
206
|
}
|
|
222
207
|
throttled.cancel();
|
|
223
|
-
dropdown.removeEventListener('mousedown', preventFocusShiftOnMousedown);
|
|
224
208
|
window.removeEventListener('scroll', handleScroll, { capture: true });
|
|
225
209
|
window.removeEventListener('resize', handleResize);
|
|
226
210
|
// Strip the inline overrides so Svelecte's next open starts
|
|
227
|
-
// from a clean slate
|
|
228
|
-
// its Svelecte-managed `display: none` slot).
|
|
211
|
+
// from a clean slate.
|
|
229
212
|
dropdown.style.position = '';
|
|
230
213
|
dropdown.style.top = '';
|
|
231
214
|
dropdown.style.left = '';
|
|
@@ -237,27 +220,20 @@ export function attachDropdownPortal(options) {
|
|
|
237
220
|
dropdown.style.zIndex = '';
|
|
238
221
|
dropdown.removeAttribute('data-scoria-portal-dropdown');
|
|
239
222
|
// Defensive: if Svelecte left the dropdown without its own
|
|
240
|
-
// `display: none` (
|
|
241
|
-
//
|
|
242
|
-
// out of order), the dropdown would
|
|
243
|
-
// display: block` in `document.body` and contribute
|
|
244
|
-
// list
|
|
245
|
-
// as a stray page-level
|
|
246
|
-
// select. Force `display: none` here; Svelecte will restore the
|
|
247
|
-
// inline style 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.
|
|
248
229
|
if (!dropdown.classList.contains('is-open')) {
|
|
249
230
|
dropdown.style.display = 'none';
|
|
250
231
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
originalParent.appendChild(dropdown);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
originalParent = null;
|
|
260
|
-
originalNextSibling = null;
|
|
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.
|
|
261
237
|
isPortalled = false;
|
|
262
238
|
};
|
|
263
239
|
// Apply / unapply the portal whenever Svelecte toggles the
|
|
@@ -285,5 +261,46 @@ export function attachDropdownPortal(options) {
|
|
|
285
261
|
return () => {
|
|
286
262
|
observer.disconnect();
|
|
287
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
|
+
}
|
|
288
272
|
};
|
|
289
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
|
}
|