@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
- // 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.
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 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".
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 (and so a closed dropdown still occupies
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` (e.g. on certain transition-cancel race paths
206
- // the open/close class flip and the inline style update arrive
207
- // out of order), the dropdown would now be `position: static;
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.
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
+ };
@@ -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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lavalogic/scoria",
3
3
  "description": "Svelte components used for the FloWMS Web Frontend",
4
- "version": "0.37.33",
4
+ "version": "0.37.35",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },