@ixfx/components 0.14.3 → 0.14.4

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.
package/dist/index.d.ts CHANGED
@@ -2571,6 +2571,23 @@ interface PillRendererOptions {
2571
2571
  queueDisplay?: PillQueueDisplay;
2572
2572
  /** Extra margin (px) added to the default spacing on each side, letting pills avoid eg. a fixed header/footer/notch. */
2573
2573
  safeArea?: PillSafeArea;
2574
+ /**
2575
+ * Render the toasts in the browser's top layer (via the Popover API), so they
2576
+ * appear above modal `<dialog>` backdrops and other popovers regardless of any
2577
+ * `z-index`. Each time a new toast arrives the container is re-promoted, so a
2578
+ * dialog opened after earlier toasts doesn't keep covering later ones.
2579
+ * Browsers without the Popover API silently fall back to normal stacking.
2580
+ *
2581
+ * Caveat: a modal dialog makes the rest of the page inert, including toasts, so
2582
+ * they show above it but can't be clicked until it closes (timed dismissal is
2583
+ * unaffected). Default `true`. Set `false` to manage stacking yourself with `zIndex`.
2584
+ */
2585
+ topLayer?: boolean;
2586
+ /**
2587
+ * `z-index` for the toast layer when `topLayer` is `false`. Ignored otherwise.
2588
+ * Leave unset to take no part in z-ordering.
2589
+ */
2590
+ zIndex?: number;
2574
2591
  }
2575
2592
  declare function createPillRenderer(options?: PillRendererOptions): NotificationRenderer;
2576
2593
  //#endregion
package/dist/index.js CHANGED
@@ -10572,11 +10572,15 @@ function restingTransform(edges, dx, dy) {
10572
10572
  if (edges.horizontal === `middle`) return `translateX(-50%) translate(${dx}px, ${dy}px)`;
10573
10573
  return `translate(${dx}px, ${dy}px)`;
10574
10574
  }
10575
+ function supportsPopover(el) {
10576
+ return typeof el.showPopover === `function` && typeof el.hidePopover === `function`;
10577
+ }
10575
10578
  function createPillRenderer(options = {}) {
10576
10579
  const position = options.position ?? `top-right`;
10577
10580
  const queueDisplay = options.queueDisplay ?? `list`;
10578
10581
  const safeArea = options.safeArea ?? {};
10579
10582
  const edges = edgesOf(position);
10583
+ const useTopLayer = options.topLayer ?? true;
10580
10584
  let container = null;
10581
10585
  let hooks = null;
10582
10586
  const pills = /* @__PURE__ */ new Map();
@@ -10588,9 +10592,20 @@ function createPillRenderer(options = {}) {
10588
10592
  container.className = `ixfx-notification-container`;
10589
10593
  container.setAttribute(`role`, `region`);
10590
10594
  container.setAttribute(`aria-label`, `Notifications`);
10595
+ container.style.cssText = `pointer-events: none;`;
10596
+ if (useTopLayer && supportsPopover(container)) {
10597
+ container.setAttribute(`popover`, `manual`);
10598
+ container.style.cssText += `position: fixed; inset: 0 auto auto 0; width: 0; height: 0; margin: 0; padding: 0; border: 0; overflow: visible; background: transparent; color: inherit;`;
10599
+ } else if (!useTopLayer && options.zIndex !== void 0) container.style.cssText += `position: fixed; inset: 0 auto auto 0; width: 0; height: 0; z-index: ${options.zIndex};`;
10591
10600
  document.body.appendChild(container);
10592
10601
  return container;
10593
10602
  }
10603
+ /** Put the container on top of the top-layer stack (above anything opened since it was last shown). */
10604
+ function promote(cont) {
10605
+ if (!cont.hasAttribute(`popover`)) return;
10606
+ if (cont.matches(`:popover-open`)) cont.hidePopover();
10607
+ cont.showPopover();
10608
+ }
10594
10609
  function updateAttributes(el, notification) {
10595
10610
  el.setAttribute(`notification-type`, notification.type);
10596
10611
  el.setAttribute(`message`, notification.message);
@@ -10675,6 +10690,7 @@ function createPillRenderer(options = {}) {
10675
10690
  }
10676
10691
  function setNotifications(notifications, _queueSize) {
10677
10692
  const cont = ensureContainer();
10693
+ let added = false;
10678
10694
  const idsToKeep = new Set(notifications.map((n) => n.id));
10679
10695
  for (const notification of notifications) {
10680
10696
  const existing = pills.get(notification.id);
@@ -10689,8 +10705,10 @@ function createPillRenderer(options = {}) {
10689
10705
  notification
10690
10706
  });
10691
10707
  cont.appendChild(el);
10708
+ added = true;
10692
10709
  measureRowHeight(el);
10693
10710
  }
10711
+ if (added) promote(cont);
10694
10712
  for (const [id] of pills) if (!idsToKeep.has(id)) dismiss(id);
10695
10713
  requestAnimationFrame(() => applyLayout(notifications));
10696
10714
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
- "version": "0.14.3",
4
- "generated": "2026-10-16T15:45:06.852Z",
3
+ "version": "0.14.4",
4
+ "generated": "2026-10-16T16:32:35.695Z",
5
5
  "components": [
6
6
  {
7
7
  "name": "ac-text",
@@ -97,6 +97,22 @@ NotificationManager.configure({
97
97
  | `position` | `'top-left' \| 'top-middle' \| 'top-right' \| 'bottom-left' \| 'bottom-middle' \| 'bottom-right'` | `'top-right'` | Which corner/edge pills anchor to, and the direction they slide in/out from. |
98
98
  | `queueDisplay` | `'list' \| 'stack'` | `'list'` | How queued (non-front) messages are shown — see below. |
99
99
  | `safeArea` | `{ top?, right?, bottom?, left? }` (px) | `{}` | Extra margin added on top of the default spacing, e.g. to avoid a fixed header/footer/notch. |
100
+ | `topLayer` | `boolean` | `true` | Show toasts in the browser's top layer so they sit above modal dialogs and popovers. See [Stacking with dialogs](#stacking-with-dialogs-and-popovers). |
101
+ | `zIndex` | `number` | — | Only used when `topLayer` is `false`: the `z-index` of the toast layer, for apps that manage stacking themselves. |
102
+
103
+ ### Stacking with dialogs and popovers
104
+
105
+ A modal `<dialog>` (opened with `showModal()`) and its backdrop live in the browser's *top layer*, which no `z-index` can beat. So by default the pill renderer puts its container in the top layer too, using the [Popover API](https://developer.mozilla.org/docs/Web/API/Popover_API) (`popover="manual"`), and toasts appear above the backdrop.
106
+
107
+ - **Order follows time.** Top-layer elements stack in the order they were shown. Each time a *new* toast arrives the container is re-shown, so it ends up above a dialog opened after earlier toasts. An already-visible toast is not re-promoted if a dialog opens later; the next toast will be.
108
+ - **Modal dialogs make the page inert.** While a modal dialog is open, everything outside it, including toasts in the top layer, cannot be clicked or focused. Toasts are visible and still time out, but tap-to-dismiss only works once the dialog closes. If toasts must be clickable over a modal, open that dialog non-modally (`show()`), or render toasts inside the dialog.
109
+ - **Manual control.** Pass `topLayer: false` to opt out. Toasts then stack normally; give them a `zIndex` above your own layers if needed:
110
+
111
+ ```typescript
112
+ createPillRenderer({ topLayer: false, zIndex: 1000 });
113
+ ```
114
+
115
+ - **Older browsers** without the Popover API fall back to normal stacking automatically.
100
116
 
101
117
  ### `queueDisplay: 'list'`
102
118
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
3
  "type": "module",
4
- "version": "0.14.3",
4
+ "version": "0.14.4",
5
5
  "packageManager": "pnpm@10.0.0",
6
6
  "description": "Web components for ixfx — Lit-based UI toolkit",
7
7
  "author": "Clint Heyer",