@sveltia/ui 0.70.2 → 0.70.3

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.
@@ -36,7 +36,8 @@
36
36
  disabled = false,
37
37
  label = '',
38
38
  labelDir = undefined,
39
- popupPosition = 'right-top', // @todo Make this auto detect
39
+ // The popup flips to the other side by itself when the submenu wouldn’t fit on this one
40
+ popupPosition = 'right-top',
40
41
  children: _children,
41
42
  startIcon: _startIcon,
42
43
  endIcon: _endIcon,
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
3
3
  */
4
- export const UI_VERSION: "0.70.2";
4
+ export const UI_VERSION: "0.70.3";
@@ -3,4 +3,4 @@
3
3
  /**
4
4
  * Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
5
5
  */
6
- export const UI_VERSION = '0.70.2';
6
+ export const UI_VERSION = '0.70.3';
@@ -325,6 +325,13 @@ class Popup {
325
325
 
326
326
  const topMargin = intersectionRect.top - 8;
327
327
  const bottomMargin = rootBounds.height - intersectionRect.bottom - 8;
328
+ // A popup that opens beside its anchor is aligned with one of the anchor’s edges and extends
329
+ // across it, so its room is measured from that edge, not from the opposite one a dropdown
330
+ // hangs off
331
+ const leftMargin = intersectionRect.left - 8;
332
+ const rightMargin = rootBounds.width - intersectionRect.right - 8;
333
+ const downwardMargin = rootBounds.height - intersectionRect.top - 8;
334
+ const upwardMargin = intersectionRect.bottom - 8;
328
335
  let { position } = this;
329
336
  let height;
330
337
 
@@ -345,7 +352,6 @@ class Popup {
345
352
  }
346
353
 
347
354
  // Alter the position if the space is limited
348
- // @todo Handle more overflow cases
349
355
  if (position.startsWith('bottom-')) {
350
356
  if (contentHeight > bottomMargin) {
351
357
  if (topMargin > bottomMargin) {
@@ -357,6 +363,31 @@ class Popup {
357
363
  }
358
364
  }
359
365
 
366
+ // Only a popup that opens beside its anchor carries a `-top`/`-bottom` suffix, and it grows
367
+ // away from the edge it’s aligned with: down from the anchor’s top, or up from its bottom. It
368
+ // gets the same treatment as the dropdown above — align with the other edge when the content
369
+ // doesn’t fit and that edge has more room, and cap it either way, so a long submenu scrolls
370
+ // instead of running past the viewport
371
+ if (position.endsWith('-top')) {
372
+ if (contentHeight > downwardMargin) {
373
+ if (upwardMargin > downwardMargin) {
374
+ position = /** @type {PopupPosition} */ (position.replace('-top', '-bottom'));
375
+ height = upwardMargin;
376
+ } else {
377
+ height = downwardMargin;
378
+ }
379
+ }
380
+ } else if (position.endsWith('-bottom')) {
381
+ if (contentHeight > upwardMargin) {
382
+ if (downwardMargin > upwardMargin) {
383
+ position = /** @type {PopupPosition} */ (position.replace('-bottom', '-top'));
384
+ height = downwardMargin;
385
+ } else {
386
+ height = upwardMargin;
387
+ }
388
+ }
389
+ }
390
+
360
391
  // If the popup overflows the viewport, change the position
361
392
  if (position.endsWith('-left')) {
362
393
  if (intersectionRect.left + contentWidth > rootBounds.width - 8) {
@@ -370,6 +401,20 @@ class Popup {
370
401
  }
371
402
  }
372
403
 
404
+ // The two checks above align a dropdown that opens below or above its anchor, so neither
405
+ // covers a popup that opens beside one — a submenu — running off the edge it opens towards.
406
+ // That gets the same treatment as the vertical flip above: switch to the other side, but only
407
+ // when it has more room, so a submenu with nowhere to go stays where the caller put it
408
+ if (position.startsWith('right-')) {
409
+ if (contentWidth > rightMargin && leftMargin > rightMargin) {
410
+ position = /** @type {PopupPosition} */ (position.replace('right-', 'left-'));
411
+ }
412
+ } else if (position.startsWith('left-')) {
413
+ if (contentWidth > leftMargin && rightMargin > leftMargin) {
414
+ position = /** @type {PopupPosition} */ (position.replace('left-', 'right-'));
415
+ }
416
+ }
417
+
373
418
  const top = position.startsWith('bottom-')
374
419
  ? `${Math.round(intersectionRect.bottom)}px`
375
420
  : position.endsWith('-top')
@@ -398,9 +443,15 @@ class Popup {
398
443
  inset: [top, right, bottom, left].join(' '),
399
444
  zIndex: 1000,
400
445
  minWidth: `${Math.round(intersectionRect.width)}px`,
401
- maxWidth: position.endsWith('-left')
402
- ? `${Math.round(rootBounds.width - intersectionRect.left - 8)}px`
403
- : `${Math.round(intersectionRect.right - 8)}px`,
446
+ // A popup opening beside the anchor is capped by the room on that side, not by the width the
447
+ // anchor’s own edges leave, which is what the two dropdown cases below measure
448
+ maxWidth: position.startsWith('right-')
449
+ ? `${Math.round(rightMargin)}px`
450
+ : position.startsWith('left-')
451
+ ? `${Math.round(leftMargin)}px`
452
+ : position.endsWith('-left')
453
+ ? `${Math.round(rootBounds.width - intersectionRect.left - 8)}px`
454
+ : `${Math.round(intersectionRect.right - 8)}px`,
404
455
  // `undefined` removes the `max-height` the popup is given, letting it take its natural size
405
456
  // again. `auto` would look equivalent but isn’t a valid `max-height`, so the browser would
406
457
  // drop it and leave whatever limit was applied last in place.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltia/ui",
3
- "version": "0.70.2",
3
+ "version": "0.70.3",
4
4
  "description": "A collection of Svelte components and utilities for building user interfaces.",
5
5
  "repository": {
6
6
  "type": "git",