@vectoriox/iox-ui 5.85.0 → 5.86.0

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.
@@ -13,7 +13,7 @@ import { SLIDER_WORDS, sliderPartElement, SliderRole, sliderWords, attachCarouse
13
13
  import { SceneAttribute, SCENE_WAKE_EVENT, SCENE_REVEAL_DEFAULT, SceneRevealFrom, revealClipPath, TextRevealBy, playTextReveal } from '@vectoriox/iox-ui/scene-core';
14
14
  import { getByPath } from '@vectoriox/iox-ui/data-core';
15
15
  export * from '@vectoriox/iox-ui/data-core';
16
- import { dialogOpenerAttributes, dialogDomId } from '@vectoriox/iox-ui/actions-core';
16
+ import { runExitMotion, isExiting, popoverOpenersSelector, OpenerAttribute, cancelExit, POPOVER_NODE_TYPE, dialogOpenerAttributes, popoverOpenerAttributes, popoverDomId, dialogDomId } from '@vectoriox/iox-ui/actions-core';
17
17
  import { VideoKind, planVideo, videoRootAttributes, videoInnerHtml, videoWords, VideoAttribute, attachVideoAt, VideoClass } from '@vectoriox/iox-ui/media-core';
18
18
  import * as i1$1 from '@angular/platform-browser';
19
19
 
@@ -1811,9 +1811,20 @@ var A11yTrait;
1811
1811
  A11yTrait["AccessibleName"] = "ariaLabel";
1812
1812
  /** A Dialog's "Close when wider than": a breakpoint id, or empty = never. */
1813
1813
  A11yTrait["CloseAbove"] = "closeAbove";
1814
+ /** "Tooltip ▸ Words" (premium M5): words for a control that has none of its own. */
1815
+ A11yTrait["Tooltip"] = "tooltip";
1816
+ /** "Tooltip ▸ Side": above (default) · below · start · end. */
1817
+ A11yTrait["TooltipPlacement"] = "tooltipPlacement";
1814
1818
  })(A11yTrait || (A11yTrait = {}));
1815
1819
  /** The types that carry an Accessible name trait. */
1816
- const ACCESSIBLE_NAME_TYPES = ['ButtonBlock', 'LinkedContainer', 'Link', 'Dialog', 'SliderContainer'];
1820
+ const ACCESSIBLE_NAME_TYPES = ['ButtonBlock', 'LinkedContainer', 'Link', 'Dialog', 'Popover', 'SliderContainer'];
1821
+ /** The types that carry a Tooltip (premium M5): the controls — a button, a linked box, a link (a gallery's arrows are
1822
+ * ButtonBlocks). Not a Text or an Image: a tooltip on something that takes no focus is invisible to a keyboard. */
1823
+ const TOOLTIP_TYPES = ['ButtonBlock', 'LinkedContainer', 'Link'];
1824
+ /** The attribute a tooltip's words travel on (the raw SSR carries it; the tooltip runtime reads it). */
1825
+ const TOOLTIP_ATTRIBUTE = 'data-iox-tooltip';
1826
+ /** The side it asks for, when not the default (above). */
1827
+ const TOOLTIP_PLACEMENT_ATTRIBUTE = 'data-iox-tooltip-placement';
1817
1828
  /** The types that link to a page and can therefore be the current page's link. */
1818
1829
  const CURRENT_LINK_TYPES = ['Link', 'LinkedContainer', 'ButtonBlock'];
1819
1830
 
@@ -1895,6 +1906,12 @@ const NOOP_INITIAL_VALUES = {
1895
1906
  * `lineHeight: 1.5` followed by the forced `line-height: 1`), so callers strip them first via
1896
1907
  * {@link stripSuppressedProps} and let renderDefaultDeclarations supply the real value.
1897
1908
  */
1909
+ /** The positioning props an overlay's renderer owns (a Dialog's, a Popover's). */
1910
+ const PLACED_BY_RENDERER = [
1911
+ 'position', 'inset', 'top', 'right', 'bottom', 'left',
1912
+ 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
1913
+ 'marginBlock', 'marginBlockStart', 'marginBlockEnd', 'marginInline', 'marginInlineStart', 'marginInlineEnd',
1914
+ ];
1898
1915
  const SUPPRESSED_STYLE_PROPS_BY_TYPE = {
1899
1916
  // An icon's line box is structurally forced to 1 — see the Icon branch of renderDefaultDeclarations.
1900
1917
  Icon: ['lineHeight'],
@@ -1903,11 +1920,10 @@ const SUPPRESSED_STYLE_PROPS_BY_TYPE = {
1903
1920
  // the canvas twin a grid stage. Every node serialises the Spacing/Position traits, so a stored
1904
1921
  // `margin: 0px` (0,1,0) beat the reset and every dialog — the consent banner too — opened in the
1905
1922
  // top-left corner of the live site (DS2 verification). Placement is the `placement` trait's job.
1906
- Dialog: [
1907
- 'position', 'inset', 'top', 'right', 'bottom', 'left',
1908
- 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
1909
- 'marginBlock', 'marginBlockStart', 'marginBlockEnd', 'marginInline', 'marginInlineStart', 'marginInlineEnd',
1910
- ],
1923
+ Dialog: PLACED_BY_RENDERER,
1924
+ // A Popover (premium M5) likewise: the live twin is a top-layer panel placed beside its opener by `placeFloating`
1925
+ // (inline top / left), the canvas twin a pin in its stage — a stored margin or position would move it off its opener.
1926
+ Popover: PLACED_BY_RENDERER,
1911
1927
  };
1912
1928
  /** Remove the style props whose value a type's render-default always overrides. Returns the map
1913
1929
  * unchanged (same reference) when the type suppresses nothing. */
@@ -5007,10 +5023,33 @@ var FormRenderState;
5007
5023
  /** The attribute every renderer writes a form's state on (the canvas switcher, both live paths). */
5008
5024
  const FORM_STATE_ATTRIBUTE = 'data-iox-state';
5009
5025
  /**
5010
- * The server's answer → what the visitor sees. Only a confirmed success is Success; per-field verdicts
5011
- * land on the fields; anything else (a dropped network included) is the designed Error block.
5012
- */
5013
- function submitOutcome(response) {
5026
+ * What a form shows once it has sent (premium M5, N3) — the Form's "After sending" choice:
5027
+ * - `message`: the success in place (M3): the fields leave, the Success block shows, Send reads "Sent" and stays so;
5028
+ * - `toast`: the fields STAY, cleared for the next answer, and a toast says the form's Sent words — a newsletter in a
5029
+ * footer, a form someone may send again.
5030
+ */
5031
+ var FormAfterSend;
5032
+ (function (FormAfterSend) {
5033
+ FormAfterSend["Message"] = "message";
5034
+ FormAfterSend["Toast"] = "toast";
5035
+ })(FormAfterSend || (FormAfterSend = {}));
5036
+ /** The Form trait holding the choice. */
5037
+ const FORM_AFTER_SEND_TRAIT = 'afterSend';
5038
+ /** The attribute every renderer writes it on (only when it is `toast` — `message` is what a form always did). */
5039
+ const FORM_AFTER_SEND_ATTRIBUTE = 'data-iox-after-send';
5040
+ /** The choice a stored value names (`message` unless it says `toast`). */
5041
+ function formAfterSend(value) {
5042
+ return value === FormAfterSend.Toast ? FormAfterSend.Toast : FormAfterSend.Message;
5043
+ }
5044
+ /**
5045
+ * The server's answer → what the visitor sees. Only a confirmed success is Success (or, for a form that keeps its
5046
+ * fields, Idle again with a toast); per-field verdicts land on the fields; anything else (a dropped network included)
5047
+ * is the designed Error block.
5048
+ */
5049
+ function submitOutcome(response, afterSend = FormAfterSend.Message) {
5050
+ if (response?.ok && formAfterSend(afterSend) === FormAfterSend.Toast) {
5051
+ return { state: FormRenderState.Idle, reset: true, toast: true, ...(response.redirect ? { redirect: response.redirect } : {}) };
5052
+ }
5014
5053
  if (response?.ok)
5015
5054
  return { state: FormRenderState.Success, reset: true, ...(response.redirect ? { redirect: response.redirect } : {}) };
5016
5055
  if (response?.errors && Object.keys(response.errors).length)
@@ -5512,6 +5551,562 @@ function dateHtml(p, options, describedBy) {
5512
5551
  * disagree. See iox-ai-guidance `architecture/builder/forms-and-inputs-plan.md` §5.
5513
5552
  */
5514
5553
 
5554
+ /**
5555
+ * placeFloating — where a panel anchored to a control goes (premium components N8; M5's tooltip and popover).
5556
+ *
5557
+ * ONE pure decision for every floating panel: on the side the caller prefers (under its anchor by default; a tooltip
5558
+ * above), flipped to the opposite side when the preferred one has no room and the other has more; lined up with the
5559
+ * anchor's reading start (the left edge in LTR, the right in RTL) or centred on it; held inside the viewport with a
5560
+ * margin; and the transform-origin AT the anchor, so the panel grows from the control that opened it (Emil 47) — even
5561
+ * when the clamp moved it sideways.
5562
+ *
5563
+ * Lives in the main entry: nothing server-side places a panel; the date picker, the tooltip and the popover are DOM
5564
+ * runtimes of the main entry. Pure — no DOM read here: the caller measures, this decides, the caller writes.
5565
+ */
5566
+ /** The side of the anchor the panel sits on. Start / End follow the reading direction (Start is the left in LTR). */
5567
+ var FloatingSide;
5568
+ (function (FloatingSide) {
5569
+ FloatingSide["Below"] = "below";
5570
+ FloatingSide["Above"] = "above";
5571
+ FloatingSide["Start"] = "start";
5572
+ FloatingSide["End"] = "end";
5573
+ })(FloatingSide || (FloatingSide = {}));
5574
+ /** How the panel lines up with its anchor across the side: at the anchor's reading start (top, for a Start / End
5575
+ * panel), or centred on it (a tooltip). */
5576
+ var FloatingAlign;
5577
+ (function (FloatingAlign) {
5578
+ FloatingAlign["Start"] = "start";
5579
+ FloatingAlign["Center"] = "center";
5580
+ })(FloatingAlign || (FloatingAlign = {}));
5581
+ /** Emil's popover spacing: close enough to read as the control's, clear of its focus ring. */
5582
+ const FLOATING_GAP = 8;
5583
+ /** The viewport margin — a panel never touches the screen's edge. */
5584
+ const FLOATING_MARGIN = 12;
5585
+ /**
5586
+ * Where a floating panel stops being a panel and becomes a bottom sheet (the date picker's calendar, a popover): a
5587
+ * phone. ONE decision, the width the shared CSS keys its sheets on.
5588
+ */
5589
+ const FLOATING_SHEET_QUERY = '(max-width: 767px)';
5590
+ const OPPOSITE = {
5591
+ [FloatingSide.Below]: FloatingSide.Above,
5592
+ [FloatingSide.Above]: FloatingSide.Below,
5593
+ [FloatingSide.Start]: FloatingSide.End,
5594
+ [FloatingSide.End]: FloatingSide.Start,
5595
+ };
5596
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), Math.max(min, max));
5597
+ const isVertical = (side) => side === FloatingSide.Below || side === FloatingSide.Above;
5598
+ /** True when the side is the anchor's LEFT on screen (Start in LTR, End in RTL). */
5599
+ const isLeftOf = (side, rtl) => (side === FloatingSide.Start) !== rtl;
5600
+ /** Room on one side of the anchor, along its axis, once the gap and the margin are kept. */
5601
+ function roomOn(side, anchor, viewport, gap, margin, rtl) {
5602
+ if (side === FloatingSide.Below)
5603
+ return viewport.height - (anchor.top + anchor.height) - gap - margin;
5604
+ if (side === FloatingSide.Above)
5605
+ return anchor.top - gap - margin;
5606
+ return isLeftOf(side, rtl) ? anchor.left - gap - margin : viewport.width - (anchor.left + anchor.width) - gap - margin;
5607
+ }
5608
+ /** The preferred side if the panel fits there, or if it is at least as roomy as the opposite; else the opposite. */
5609
+ function chooseSide(prefer, anchor, panel, viewport, gap, margin, rtl) {
5610
+ const need = isVertical(prefer) ? panel.height : panel.width;
5611
+ const here = roomOn(prefer, anchor, viewport, gap, margin, rtl);
5612
+ const there = roomOn(OPPOSITE[prefer], anchor, viewport, gap, margin, rtl);
5613
+ return here >= need || here >= there ? prefer : OPPOSITE[prefer];
5614
+ }
5615
+ /** Where `panel` goes beside `anchor` inside `viewport`. */
5616
+ function placeFloating(anchor, panel, viewport, options = {}) {
5617
+ const gap = options.gap ?? FLOATING_GAP;
5618
+ const margin = options.margin ?? FLOATING_MARGIN;
5619
+ const rtl = !!options.rtl;
5620
+ const centred = options.align === FloatingAlign.Center;
5621
+ const side = chooseSide(options.side ?? FloatingSide.Below, anchor, panel, viewport, gap, margin, rtl);
5622
+ const maxTop = viewport.height - margin - panel.height;
5623
+ const maxLeft = viewport.width - margin - panel.width;
5624
+ if (isVertical(side)) {
5625
+ const wantTop = side === FloatingSide.Below ? anchor.top + anchor.height + gap : anchor.top - gap - panel.height;
5626
+ const top = clamp(wantTop, margin, maxTop);
5627
+ // The point on the anchor the panel answers to: its reading-start edge, or its middle.
5628
+ const refX = centred ? anchor.left + anchor.width / 2 : (rtl ? anchor.left + anchor.width : anchor.left);
5629
+ const wantLeft = centred ? refX - panel.width / 2 : (rtl ? refX - panel.width : refX);
5630
+ const left = clamp(wantLeft, margin, maxLeft);
5631
+ const originX = Math.round(clamp(refX - left, 0, panel.width));
5632
+ const originY = side === FloatingSide.Below ? 0 : Math.round(panel.height);
5633
+ return { top: Math.round(top), left: Math.round(left), side, origin: `${originX}px ${originY}px` };
5634
+ }
5635
+ const leftOf = isLeftOf(side, rtl);
5636
+ const wantLeft = leftOf ? anchor.left - gap - panel.width : anchor.left + anchor.width + gap;
5637
+ const left = clamp(wantLeft, margin, maxLeft);
5638
+ const refY = centred ? anchor.top + anchor.height / 2 : anchor.top;
5639
+ const wantTop = centred ? refY - panel.height / 2 : refY;
5640
+ const top = clamp(wantTop, margin, maxTop);
5641
+ const originX = leftOf ? Math.round(panel.width) : 0;
5642
+ const originY = Math.round(clamp(refY - top, 0, panel.height));
5643
+ return { top: Math.round(top), left: Math.round(left), side, origin: `${originX}px ${originY}px` };
5644
+ }
5645
+ /**
5646
+ * A screen rect in the coordinates of a LAYER — a box that fills the viewport on the live site (scale 1, at 0,0) and
5647
+ * the page frame on the builder canvas (offset, zoomed). The floating runtimes place inside a layer, so one placement
5648
+ * serves both; `layerRect` is the layer's screen rect and `scale` its zoom (its screen width over its layout width).
5649
+ */
5650
+ function toLayerRect(rect, layerRect, scale = 1) {
5651
+ const s = scale > 0 && Number.isFinite(scale) ? scale : 1;
5652
+ return {
5653
+ top: (rect.top - layerRect.top) / s,
5654
+ left: (rect.left - layerRect.left) / s,
5655
+ width: rect.width / s,
5656
+ height: rect.height / s,
5657
+ };
5658
+ }
5659
+
5660
+ /**
5661
+ * THE FLOATING LAYER (premium components M5) — the one box tooltips and toasts are placed in.
5662
+ *
5663
+ * On the live site it is a `popover="manual"` element in the TOP LAYER, filling the viewport: above a fixed header, a
5664
+ * slider's `overflow: hidden`, a nav's `backdrop-filter` (which would otherwise contain anything fixed inside it).
5665
+ * Each SCOPE has its own layer — the document's body, or an open modal `<dialog>` (a tooltip on a lightbox's arrow, a
5666
+ * toast raised from a drawer's form): a layer inside the dialog is shown after it, so it sits above it in the top
5667
+ * layer, it is not inert behind it, and it inherits the dialog's tokens and scheme through the DOM.
5668
+ * On the builder canvas it is a plain `position: fixed` box inside the transformed page frame, which CONTAINS it — so
5669
+ * it fills the frame and zooms with it; nothing is drawn over the builder's own panels.
5670
+ *
5671
+ * Every placement is made in the layer's own px (`layerFrame` + `toLayerRect`), so the site and the canvas share it.
5672
+ */
5673
+ const FLOATING_LAYER_CLASS = 'iox-float-layer';
5674
+ /** The selector of an open modal dialog — a floating element raised from inside one belongs to its layer. */
5675
+ const OPEN_MODAL = 'dialog[open]';
5676
+ /** True when the browser has the Popover API (jsdom and old engines do not — they open by `hidden`). */
5677
+ function canPopover(el) {
5678
+ return typeof el.showPopover === 'function';
5679
+ }
5680
+ /** True while a `popover` element is shown in the top layer. */
5681
+ function isOpenPopover(el) {
5682
+ try {
5683
+ return el.matches(':popover-open');
5684
+ }
5685
+ catch {
5686
+ return false;
5687
+ }
5688
+ }
5689
+ /** Show a `popover` element in the top layer; an element without the API is un-hidden instead. */
5690
+ function showInTopLayer(el) {
5691
+ if (!el.hasAttribute('popover') || !canPopover(el)) {
5692
+ el.hidden = false;
5693
+ return;
5694
+ }
5695
+ try {
5696
+ if (!isOpenPopover(el))
5697
+ el.showPopover();
5698
+ }
5699
+ catch { /* not connected */ }
5700
+ }
5701
+ /** Take a `popover` element out of the top layer (or hide it). */
5702
+ function hideFromTopLayer(el) {
5703
+ if (!el.hasAttribute('popover') || !canPopover(el)) {
5704
+ el.hidden = true;
5705
+ return;
5706
+ }
5707
+ try {
5708
+ if (isOpenPopover(el))
5709
+ el.hidePopover();
5710
+ }
5711
+ catch { /* already hidden */ }
5712
+ }
5713
+ /** The scope a floating element raised from `from` belongs to: its closest open modal dialog, else the container. */
5714
+ function floatingScope(from, options = {}) {
5715
+ const dialog = options.topLayer !== false ? from?.closest?.(OPEN_MODAL) ?? null : null;
5716
+ if (dialog)
5717
+ return dialog;
5718
+ const container = typeof options.container === 'function' ? options.container(from ?? null) : options.container;
5719
+ return container ?? from?.ownerDocument?.body ?? null;
5720
+ }
5721
+ /** The layer of a scope — created on first use, raised into the top layer when the site asks for it. */
5722
+ function floatingLayer(scope, options = {}) {
5723
+ const doc = scope.ownerDocument;
5724
+ let layer = Array.from(scope.children).find(c => c.classList.contains(FLOATING_LAYER_CLASS));
5725
+ if (!layer) {
5726
+ layer = doc.createElement('div');
5727
+ layer.className = FLOATING_LAYER_CLASS;
5728
+ // Display only — the tooltip names what it describes through aria-describedby, the toasts speak through their
5729
+ // own live region; the layer itself is not a landmark.
5730
+ layer.setAttribute('data-iox-chrome', '');
5731
+ if (options.topLayer !== false && canPopover(layer))
5732
+ layer.setAttribute('popover', 'manual');
5733
+ scope.appendChild(layer);
5734
+ }
5735
+ raiseFloatingLayer(layer);
5736
+ return layer;
5737
+ }
5738
+ /** Show a top-layer layer (again): a layer closed by a scope change, or one a later modal dialog now covers. */
5739
+ function raiseFloatingLayer(layer) {
5740
+ if (!layer.hasAttribute('popover') || !canPopover(layer))
5741
+ return;
5742
+ showInTopLayer(layer);
5743
+ }
5744
+ /** The layer's frame. On the site: (0, 0), scale 1, the viewport. On the canvas: the frame's corner, its zoom, its px. */
5745
+ function layerFrame(layer) {
5746
+ const r = layer.getBoundingClientRect();
5747
+ const width = layer.offsetWidth || r.width;
5748
+ const height = layer.offsetHeight || r.height;
5749
+ const scale = width > 0 && r.width > 0 ? r.width / width : 1;
5750
+ return { rect: { top: r.top, left: r.left }, scale, viewport: { width, height } };
5751
+ }
5752
+ /** An element's screen rect in the layer's px. */
5753
+ function rectInLayer(el, frame) {
5754
+ const r = el.getBoundingClientRect();
5755
+ return toLayerRect({ top: r.top, left: r.left, width: r.width, height: r.height }, frame.rect, frame.scale);
5756
+ }
5757
+
5758
+ /**
5759
+ * THE TOAST (premium components M5, N3) — a non-modal notice at an edge of the screen. ONE DOM runtime for the live site
5760
+ * (the `showToast` action on both engine paths, and a form that keeps its fields after sending) and the builder canvas
5761
+ * (the same action RUNS there; the design-time "Showing on canvas" state holds one up to style it).
5762
+ *
5763
+ * - Speaks through a polite live region that exists BEFORE the first notice lands (a region inserted with its words is
5764
+ * not announced); never takes focus.
5765
+ * - Enters from its edge and leaves to the SAME edge (Emil 129–130), on the look's toast tokens (400ms `ease` in, a
5766
+ * faster exit); reduced motion keeps the fade and drops the slide (the shared CSS).
5767
+ * - Its timer pauses while the tab is hidden, the pointer is on the stack or focus is inside it (Emil 132; WCAG 2.2.1),
5768
+ * and it has a close control.
5769
+ * - At most `TOAST_MAX_VISIBLE` at once — the oldest leaves for a new one; the rest slide to their new places (FLIP on
5770
+ * `translate`, which composes with the entrance's `transform`), so nothing jumps.
5771
+ * - Above the home indicator (`env(safe-area-inset-*)`) and above a bottom tab bar (the shared CSS).
5772
+ */
5773
+ var ToastEdge;
5774
+ (function (ToastEdge) {
5775
+ ToastEdge["Bottom"] = "bottom";
5776
+ ToastEdge["Top"] = "top";
5777
+ })(ToastEdge || (ToastEdge = {}));
5778
+ var ToastClass;
5779
+ (function (ToastClass) {
5780
+ ToastClass["Stack"] = "iox-toasts";
5781
+ ToastClass["Toast"] = "iox-toast";
5782
+ ToastClass["Text"] = "iox-toast__text";
5783
+ ToastClass["Close"] = "iox-toast__close";
5784
+ /** The element style's marker — set when the element it was raised from wears the design system. */
5785
+ ToastClass["Marker"] = "iox-es-toast";
5786
+ })(ToastClass || (ToastClass = {}));
5787
+ const TOAST_EDGE_ATTRIBUTE = 'data-iox-edge';
5788
+ /** On the toast the builder canvas holds up for styling (no timer, never leaves on its own). */
5789
+ const TOAST_DESIGN_ATTRIBUTE = 'data-iox-toast-design';
5790
+ /** A stack created for a toast waits this long before the words land: a live region must exist before it changes. */
5791
+ const TOAST_FIRST_LANDS_MS = 80;
5792
+ /** The most toasts on screen at once (Emil 131: a short stack, never a wall). */
5793
+ const TOAST_MAX_VISIBLE = 3;
5794
+ /** A toast stays at least this long … */
5795
+ const TOAST_MIN_MS = 4000;
5796
+ /** … at most this long … */
5797
+ const TOAST_MAX_MS = 10000;
5798
+ /** … and this much longer per character it says (a reader's pace). */
5799
+ const TOAST_MS_PER_CHAR = 60;
5800
+ const DESIGN_MARKER$1 = '.iox-es';
5801
+ const SVG_NS = 'http://www.w3.org/2000/svg';
5802
+ const CLOSE_PATH = 'M6 6l12 12M18 6L6 18';
5803
+ /** The close control's words in the page's language. */
5804
+ const TOAST_CLOSE_WORDS = Object.freeze({ en: 'Close', he: 'סגירה' });
5805
+ /** "Close" in `lang` (its base language), else English. */
5806
+ function toastCloseWord(lang) {
5807
+ const base = String(lang ?? '').toLowerCase().split(/[-_]/)[0];
5808
+ return TOAST_CLOSE_WORDS[base] ?? TOAST_CLOSE_WORDS['en'];
5809
+ }
5810
+ /** How long a toast stays: long enough to read, within bounds. Pure. */
5811
+ function toastDuration(text, override) {
5812
+ if (typeof override === 'number' && Number.isFinite(override) && override > 0)
5813
+ return override;
5814
+ const want = TOAST_MIN_MS + Math.max(0, String(text ?? '').trim().length - 20) * TOAST_MS_PER_CHAR;
5815
+ return Math.min(TOAST_MAX_MS, Math.max(TOAST_MIN_MS, want));
5816
+ }
5817
+ /** Which toasts leave so a new one fits: the oldest first, keeping `max - 1` beside the newcomer. Pure. */
5818
+ function toastsToEvict(shown, max = TOAST_MAX_VISIBLE) {
5819
+ const over = shown.length - (max - 1);
5820
+ return over > 0 ? shown.slice(0, over) : [];
5821
+ }
5822
+ /** The edge a value names (Bottom unless it says Top). */
5823
+ function toastEdge(value) {
5824
+ return value === ToastEdge.Top ? ToastEdge.Top : ToastEdge.Bottom;
5825
+ }
5826
+ var PauseReason;
5827
+ (function (PauseReason) {
5828
+ PauseReason["Hidden"] = "hidden";
5829
+ PauseReason["Hover"] = "hover";
5830
+ PauseReason["Focus"] = "focus";
5831
+ })(PauseReason || (PauseReason = {}));
5832
+ /**
5833
+ * A countdown that stops while any reason holds it (the tab hidden, the pointer on the stack, focus inside) and runs
5834
+ * on from what was left. Its clock and scheduler are injectable for a spec.
5835
+ */
5836
+ class PausableTimer {
5837
+ constructor(ms, onDone, now = () => Date.now()) {
5838
+ this.onDone = onDone;
5839
+ this.now = now;
5840
+ this.startedAt = 0;
5841
+ this.handle = null;
5842
+ this.reasons = new Set();
5843
+ this.done = false;
5844
+ this.remaining = Math.max(0, ms);
5845
+ this.run();
5846
+ }
5847
+ get paused() { return this.reasons.size > 0; }
5848
+ /** What is left (ms), counting the time run since the last start. */
5849
+ left() {
5850
+ return this.handle ? Math.max(0, this.remaining - (this.now() - this.startedAt)) : this.remaining;
5851
+ }
5852
+ pause(reason) {
5853
+ if (this.done)
5854
+ return;
5855
+ this.reasons.add(reason);
5856
+ if (!this.handle)
5857
+ return;
5858
+ this.remaining = this.left();
5859
+ clearTimeout(this.handle);
5860
+ this.handle = null;
5861
+ }
5862
+ resume(reason) {
5863
+ if (this.done)
5864
+ return;
5865
+ this.reasons.delete(reason);
5866
+ if (!this.reasons.size && !this.handle)
5867
+ this.run();
5868
+ }
5869
+ clear() {
5870
+ this.done = true;
5871
+ if (this.handle)
5872
+ clearTimeout(this.handle);
5873
+ this.handle = null;
5874
+ }
5875
+ run() {
5876
+ this.startedAt = this.now();
5877
+ this.handle = setTimeout(() => {
5878
+ this.handle = null;
5879
+ if (this.done)
5880
+ return;
5881
+ this.done = true;
5882
+ this.onDone();
5883
+ }, this.remaining);
5884
+ }
5885
+ }
5886
+ const STACK = '__ioxToastStack';
5887
+ /** The live stack of one edge in one layer, with its listeners — created with the first toast at that edge. */
5888
+ class ToastStack {
5889
+ constructor(layer, edge) {
5890
+ this.toasts = [];
5891
+ this.cleanups = [];
5892
+ const doc = layer.ownerDocument;
5893
+ this.el = doc.createElement('div');
5894
+ this.el.className = ToastClass.Stack;
5895
+ this.el.setAttribute(TOAST_EDGE_ATTRIBUTE, edge);
5896
+ this.el.setAttribute('role', 'status');
5897
+ this.el.setAttribute('aria-live', 'polite');
5898
+ this.el.setAttribute('aria-atomic', 'false');
5899
+ this.el.setAttribute('aria-relevant', 'additions text');
5900
+ layer.appendChild(this.el);
5901
+ const on = (target, type, fn) => {
5902
+ target.addEventListener(type, fn);
5903
+ this.cleanups.push(() => target.removeEventListener(type, fn));
5904
+ };
5905
+ on(this.el, 'pointerenter', () => this.each(t => t.timer?.pause(PauseReason.Hover)));
5906
+ on(this.el, 'pointerleave', () => this.each(t => t.timer?.resume(PauseReason.Hover)));
5907
+ on(this.el, 'focusin', () => this.each(t => t.timer?.pause(PauseReason.Focus)));
5908
+ on(this.el, 'focusout', () => { if (!this.el.contains(doc.activeElement))
5909
+ this.each(t => t.timer?.resume(PauseReason.Focus)); });
5910
+ on(doc, 'visibilitychange', () => this.each(t => (doc.hidden ? t.timer?.pause(PauseReason.Hidden) : t.timer?.resume(PauseReason.Hidden))));
5911
+ this.el[STACK] = this;
5912
+ }
5913
+ /** Add a toast: the oldest beyond the limit leave, the others slide up (or down) to make room. A stack that was
5914
+ * just created waits `TOAST_FIRST_LANDS_MS` before its first words land, so the live region is heard. */
5915
+ add(text, options, design, fresh = false) {
5916
+ const doc = this.el.ownerDocument;
5917
+ for (const old of toastsToEvict(this.toasts.filter(t => !t.closing)))
5918
+ this.close(old);
5919
+ const el = doc.createElement('div');
5920
+ el.className = ToastClass.Toast;
5921
+ el.classList.toggle(ToastClass.Marker, !!options.from?.closest?.(DESIGN_MARKER$1));
5922
+ if (design)
5923
+ el.setAttribute(TOAST_DESIGN_ATTRIBUTE, '');
5924
+ const p = doc.createElement('p');
5925
+ p.className = ToastClass.Text;
5926
+ p.textContent = text;
5927
+ const close = doc.createElement('button');
5928
+ close.type = 'button';
5929
+ close.className = ToastClass.Close;
5930
+ const name = toastCloseWord(options.lang ?? options.from?.closest?.('[lang]')?.getAttribute('lang') ?? doc.documentElement.lang);
5931
+ close.setAttribute('aria-label', name);
5932
+ close.title = name;
5933
+ close.appendChild(closeIcon(doc));
5934
+ el.append(p, close);
5935
+ const live = { el, timer: null, closing: false };
5936
+ close.addEventListener('click', () => this.close(live));
5937
+ this.toasts.push(live);
5938
+ const land = () => {
5939
+ if (live.closing)
5940
+ return;
5941
+ this.withFlip(() => this.el.appendChild(el));
5942
+ if (design)
5943
+ return;
5944
+ live.timer = new PausableTimer(toastDuration(text, options.durationMs), () => this.close(live), () => doc.defaultView?.performance?.now() ?? Date.now());
5945
+ if (doc.hidden)
5946
+ live.timer.pause(PauseReason.Hidden);
5947
+ if (this.el.matches(':hover'))
5948
+ live.timer.pause(PauseReason.Hover);
5949
+ if (this.el.contains(doc.activeElement))
5950
+ live.timer.pause(PauseReason.Focus);
5951
+ };
5952
+ if (fresh)
5953
+ setTimeout(land, TOAST_FIRST_LANDS_MS);
5954
+ else
5955
+ land();
5956
+ return { element: el, close: () => this.close(live) };
5957
+ }
5958
+ /** Leave to its edge (the shared CSS's exit), then the rest close the gap. */
5959
+ close(t) {
5960
+ if (t.closing)
5961
+ return;
5962
+ t.closing = true;
5963
+ t.timer?.clear();
5964
+ const animate = motionAllowed(this.el.ownerDocument.defaultView);
5965
+ void runExitMotion(t.el, { animate }).then(() => {
5966
+ const i = this.toasts.indexOf(t);
5967
+ if (i >= 0)
5968
+ this.toasts.splice(i, 1);
5969
+ this.withFlip(() => t.el.remove());
5970
+ });
5971
+ }
5972
+ /** Take the stack down (its layer is going — a spec, a page torn down). An empty stack otherwise stays, so its live
5973
+ * region is already there for the next toast. */
5974
+ destroy() {
5975
+ for (const t of this.toasts)
5976
+ t.timer?.clear();
5977
+ this.toasts.length = 0;
5978
+ for (const c of this.cleanups)
5979
+ c();
5980
+ this.cleanups.length = 0;
5981
+ this.el.remove();
5982
+ }
5983
+ each(fn) {
5984
+ for (const t of this.toasts)
5985
+ fn(t);
5986
+ }
5987
+ /** Move the stack with no jump: measure, change, then play each staying toast from where it was (FLIP). */
5988
+ withFlip(change) {
5989
+ const view = this.el.ownerDocument.defaultView;
5990
+ const staying = this.toasts.filter(t => !t.closing && t.el.isConnected);
5991
+ const before = new Map(staying.map(t => [t.el, t.el.getBoundingClientRect().top]));
5992
+ change();
5993
+ if (!motionAllowed(view))
5994
+ return;
5995
+ const duration = parseDuration(view?.getComputedStyle(this.el).getPropertyValue('--iox-toast-dur-in') ?? '');
5996
+ if (!duration)
5997
+ return;
5998
+ for (const t of staying) {
5999
+ const from = before.get(t.el);
6000
+ if (from === undefined || !t.el.isConnected)
6001
+ continue;
6002
+ const dy = from - t.el.getBoundingClientRect().top;
6003
+ if (!dy || typeof t.el.animate !== 'function')
6004
+ continue;
6005
+ t.el.animate([{ translate: `0 ${dy}px` }, { translate: '0 0' }], { duration, easing: 'ease' });
6006
+ }
6007
+ }
6008
+ }
6009
+ function closeIcon(doc) {
6010
+ const svg = doc.createElementNS(SVG_NS, 'svg');
6011
+ svg.setAttribute('width', '16');
6012
+ svg.setAttribute('height', '16');
6013
+ svg.setAttribute('viewBox', '0 0 24 24');
6014
+ svg.setAttribute('aria-hidden', 'true');
6015
+ svg.setAttribute('focusable', 'false');
6016
+ const path = doc.createElementNS(SVG_NS, 'path');
6017
+ path.setAttribute('d', CLOSE_PATH);
6018
+ path.setAttribute('fill', 'none');
6019
+ path.setAttribute('stroke', 'currentColor');
6020
+ path.setAttribute('stroke-width', '2');
6021
+ path.setAttribute('stroke-linecap', 'round');
6022
+ svg.appendChild(path);
6023
+ return svg;
6024
+ }
6025
+ /** `400ms` / `0.4s` → 400; anything else → 0. */
6026
+ function parseDuration(value) {
6027
+ const v = value.trim();
6028
+ if (!v)
6029
+ return 0;
6030
+ const n = parseFloat(v);
6031
+ if (!Number.isFinite(n))
6032
+ return 0;
6033
+ return v.endsWith('ms') ? n : n * 1000;
6034
+ }
6035
+ /** The stack at `edge` in a layer, created on first use (`fresh`). */
6036
+ function stackOf(layer, edge) {
6037
+ const el = layer.querySelector(`:scope > .${ToastClass.Stack}[${TOAST_EDGE_ATTRIBUTE}="${edge}"]`);
6038
+ const found = el?.[STACK];
6039
+ return found ? { stack: found, fresh: false } : { stack: new ToastStack(layer, edge), fresh: true };
6040
+ }
6041
+ /** Take down every toast stack under `root` (a page torn down, a spec). */
6042
+ function clearToasts(root) {
6043
+ root?.querySelectorAll?.(`.${ToastClass.Stack}`).forEach(el => el[STACK]?.destroy());
6044
+ }
6045
+ /** The layer a toast raised from `options.from` goes to: its open dialog's, else the container's (the body). */
6046
+ function layerFor(doc, options) {
6047
+ const scope = floatingScope(options.from ?? null, options) ?? doc.body;
6048
+ return scope ? floatingLayer(scope, options) : null;
6049
+ }
6050
+ /**
6051
+ * Raise a toast. Null when there is nothing to say or nowhere to say it (no document).
6052
+ * A first toast creates its live region and lands a frame later, so assistive technology hears it.
6053
+ */
6054
+ function showToast(doc, text, options = {}) {
6055
+ const words = String(text ?? '').trim();
6056
+ if (!doc || !words)
6057
+ return null;
6058
+ const layer = layerFor(doc, options);
6059
+ if (!layer)
6060
+ return null;
6061
+ const { stack, fresh } = stackOf(layer, toastEdge(options.edge));
6062
+ return stack.add(words, options, false, fresh);
6063
+ }
6064
+ /**
6065
+ * The builder canvas's design-time state: hold a toast up (no timer) so the author styles it, or take it down. One per
6066
+ * layer; showing it again only changes its words.
6067
+ */
6068
+ function showToastDesign(doc, text, show, options = {}) {
6069
+ if (!doc)
6070
+ return;
6071
+ const layer = layerFor(doc, options);
6072
+ if (!layer)
6073
+ return;
6074
+ const existing = layer.querySelector(`.${ToastClass.Toast}[${TOAST_DESIGN_ATTRIBUTE}]`);
6075
+ if (!show) {
6076
+ existing?.querySelector(`.${ToastClass.Close}`)?.click();
6077
+ return;
6078
+ }
6079
+ if (existing) {
6080
+ const p = existing.querySelector(`.${ToastClass.Text}`);
6081
+ if (p)
6082
+ p.textContent = text;
6083
+ return;
6084
+ }
6085
+ stackOf(layer, toastEdge(options.edge)).stack.add(String(text ?? '').trim() || ' ', options, true);
6086
+ }
6087
+
6088
+ /**
6089
+ * A form that KEEPS its fields after sending says so in a toast (premium M5, N3 — the Form's After sending ▸ "Keep the
6090
+ * fields and show a toast"). Its words are the form's own Sent words — the ones its submit button shows once it has sent
6091
+ * (the author's "Once sent", else "Sent" in the page's language) — so the toast and the button never disagree. ONE
6092
+ * helper for the component path and the string path.
6093
+ */
6094
+ /** The Sent words a form says: its submit button's Sent label, else "Sent" in its language. */
6095
+ function formSentWords(form) {
6096
+ const label = form.querySelector(`.${SubmitLabelClass.Sent}`)?.textContent?.trim();
6097
+ if (label)
6098
+ return label;
6099
+ return sentWord(form.closest('[lang]')?.getAttribute('lang') ?? form.ownerDocument?.documentElement?.lang);
6100
+ }
6101
+ /** True when the rendered form keeps its fields and toasts (the attribute every renderer writes). */
6102
+ function formToastsOnSend(form) {
6103
+ return form?.getAttribute(FORM_AFTER_SEND_ATTRIBUTE) === FormAfterSend.Toast;
6104
+ }
6105
+ /** Raise the form's Sent toast (from the form — its scope and its look). */
6106
+ function raiseFormToast(form) {
6107
+ return showToast(form.ownerDocument, formSentWords(form), { from: form });
6108
+ }
6109
+
5515
6110
  const IOX_FORM_MESSAGES = new InjectionToken('IOX_FORM_MESSAGES', {
5516
6111
  providedIn: 'root',
5517
6112
  factory: () => defaultMessageResolver,
@@ -5726,6 +6321,7 @@ class IoxFormComponent {
5726
6321
  syncSubmitButtons(this.host?.nativeElement, state);
5727
6322
  }
5728
6323
  get renderState() { return this._renderState; }
6324
+ get keepsFields() { return formAfterSend(this.afterSend) === FormAfterSend.Toast; }
5729
6325
  /**
5730
6326
  * Constructor injection rather than `inject()`, for the same reason as Field: this library's
5731
6327
  * specs construct components directly. `runtime` is provided by THIS component (see
@@ -5743,6 +6339,8 @@ class IoxFormComponent {
5743
6339
  this.name = '';
5744
6340
  this._renderState = FormRenderState.Idle;
5745
6341
  this.feedback = FeedbackTiming.Smart;
6342
+ /** After sending (premium M5): the success in place (`message`), or keep the fields and say it in a toast. */
6343
+ this.afterSend = FormAfterSend.Message;
5746
6344
  /**
5747
6345
  * Move focus to the first field that failed, instead of leaving the visitor to hunt for red
5748
6346
  * text. On by default — the single most valuable thing a failed submit can do.
@@ -5833,7 +6431,7 @@ class IoxFormComponent {
5833
6431
  /** Turn the server's answer into what the visitor sees — the shared `submitOutcome`, which the string runtime
5834
6432
  * applies too. Public so a host-owned transport can reuse it. */
5835
6433
  applyResponse(response) {
5836
- const outcome = submitOutcome(response);
6434
+ const outcome = submitOutcome(response, this.afterSend);
5837
6435
  this.renderState = outcome.state;
5838
6436
  if (outcome.errors) {
5839
6437
  this.runtime.applyErrors(outcome.errors);
@@ -5850,6 +6448,8 @@ class IoxFormComponent {
5850
6448
  // the success message, and cannot resubmit them by pressing enter.
5851
6449
  this.runtime.reset();
5852
6450
  this.resetControls();
6451
+ if (outcome.toast)
6452
+ raiseFormToast(this.host.nativeElement);
5853
6453
  if (outcome.redirect && this.navigate)
5854
6454
  this.navigate(outcome.redirect);
5855
6455
  }
@@ -5896,7 +6496,7 @@ class IoxFormComponent {
5896
6496
  }
5897
6497
  }
5898
6498
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxFormComponent, deps: [{ token: FormRuntimeService }, { token: i0.ElementRef }, { token: IOX_FORM_TRANSPORT, optional: true }, { token: IOX_FORM_NAVIGATOR, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
5899
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: IoxFormComponent, isStandalone: false, selector: "form[iox-form]", inputs: { nodeId: "nodeId", formId: "formId", name: "name", renderState: "renderState", feedback: "feedback", focusFirstProblem: "focusFirstProblem", announceFailure: "announceFailure" }, outputs: { formSubmit: "formSubmit", validationFailed: "validationFailed" }, host: { listeners: { "submit": "onSubmit($event)" }, properties: { "class": "'iox-form iox-node-' + nodeId", "attr.novalidate": "''", "attr.data-iox-form": "formId || null", "attr.data-iox-state": "renderState", "attr.aria-busy": "renderState === 'submitting' ? 'true' : null" } }, providers: [FormRuntimeService], viewQueries: [{ propertyName: "childrenHost", first: true, predicate: ["childrenHost"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
6499
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: IoxFormComponent, isStandalone: false, selector: "form[iox-form]", inputs: { nodeId: "nodeId", formId: "formId", name: "name", renderState: "renderState", feedback: "feedback", afterSend: "afterSend", focusFirstProblem: "focusFirstProblem", announceFailure: "announceFailure" }, outputs: { formSubmit: "formSubmit", validationFailed: "validationFailed" }, host: { listeners: { "submit": "onSubmit($event)" }, properties: { "class": "'iox-form iox-node-' + nodeId", "attr.novalidate": "''", "attr.data-iox-form": "formId || null", "attr.data-iox-state": "renderState", "attr.aria-busy": "renderState === 'submitting' ? 'true' : null", "attr.data-iox-after-send": "keepsFields ? 'toast' : null" } }, providers: [FormRuntimeService], viewQueries: [{ propertyName: "childrenHost", first: true, predicate: ["childrenHost"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
5900
6500
  <div class="iox-form__hp" aria-hidden="true">
5901
6501
  <input type="text" [attr.name]="honeypotField" tabindex="-1" autocomplete="off" />
5902
6502
  </div>
@@ -5932,6 +6532,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
5932
6532
  '[attr.data-iox-form]': 'formId || null',
5933
6533
  '[attr.data-iox-state]': 'renderState',
5934
6534
  '[attr.aria-busy]': "renderState === 'submitting' ? 'true' : null",
6535
+ '[attr.data-iox-after-send]': "keepsFields ? 'toast' : null",
5935
6536
  '(submit)': 'onSubmit($event)',
5936
6537
  }, providers: [FormRuntimeService], standalone: false, styles: [":host{box-sizing:border-box}\n"] }]
5937
6538
  }], ctorParameters: () => [{ type: FormRuntimeService }, { type: i0.ElementRef }, { type: undefined, decorators: [{
@@ -5954,6 +6555,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
5954
6555
  type: Input
5955
6556
  }], feedback: [{
5956
6557
  type: Input
6558
+ }], afterSend: [{
6559
+ type: Input
5957
6560
  }], focusFirstProblem: [{
5958
6561
  type: Input
5959
6562
  }], announceFailure: [{
@@ -5967,47 +6570,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
5967
6570
  args: ['childrenHost', { read: ViewContainerRef, static: true }]
5968
6571
  }] } });
5969
6572
 
5970
- /**
5971
- * placeFloating — where a panel anchored to a control goes (premium components N8, reused by M5's tooltip and popover).
5972
- *
5973
- * ONE pure decision for every floating panel: under its anchor, aligned to the anchor's reading start (the left edge in
5974
- * LTR, the right in RTL); above it when there is no room below and more above; held inside the viewport with a margin;
5975
- * and the transform-origin AT the anchor, so the panel grows from the control that opened it (Emil 47) — even when the
5976
- * clamp moved it sideways.
5977
- *
5978
- * Lives in the main entry: nothing server-side places a panel, and the date picker (now) and the floating family (M5)
5979
- * are DOM runtimes of the main entry. Pure — no DOM read here: the caller measures, this decides, the caller writes.
5980
- */
5981
- var FloatingSide;
5982
- (function (FloatingSide) {
5983
- FloatingSide["Below"] = "below";
5984
- FloatingSide["Above"] = "above";
5985
- })(FloatingSide || (FloatingSide = {}));
5986
- /** Emil's popover spacing: close enough to read as the control's, clear of its focus ring. */
5987
- const FLOATING_GAP = 8;
5988
- /** The viewport margin — a panel never touches the screen's edge. */
5989
- const FLOATING_MARGIN = 12;
5990
- const clamp = (value, min, max) => Math.min(Math.max(value, min), Math.max(min, max));
5991
- /** Where `panel` goes beside `anchor` inside `viewport`. */
5992
- function placeFloating(anchor, panel, viewport, options = {}) {
5993
- const gap = options.gap ?? FLOATING_GAP;
5994
- const margin = options.margin ?? FLOATING_MARGIN;
5995
- const anchorBottom = anchor.top + anchor.height;
5996
- const roomBelow = viewport.height - anchorBottom - gap - margin;
5997
- const roomAbove = anchor.top - gap - margin;
5998
- // Below is the default; above only when below is too short AND above is roomier (a field near the screen's foot).
5999
- const side = roomBelow >= panel.height || roomBelow >= roomAbove ? FloatingSide.Below : FloatingSide.Above;
6000
- const wantTop = side === FloatingSide.Below ? anchorBottom + gap : anchor.top - gap - panel.height;
6001
- const top = clamp(wantTop, margin, viewport.height - margin - panel.height);
6002
- const start = options.rtl ? anchor.left + anchor.width : anchor.left;
6003
- const wantLeft = options.rtl ? start - panel.width : start;
6004
- const left = clamp(wantLeft, margin, viewport.width - margin - panel.width);
6005
- // The origin is the anchor's start corner, in the panel's own coordinates, kept on the panel's box.
6006
- const originX = Math.round(clamp(start - left, 0, panel.width));
6007
- const originY = side === FloatingSide.Below ? 0 : Math.round(panel.height);
6008
- return { top: Math.round(top), left: Math.round(left), side, origin: `${originX}px ${originY}px` };
6009
- }
6010
-
6011
6573
  var CalendarClass;
6012
6574
  (function (CalendarClass) {
6013
6575
  CalendarClass["Panel"] = "iox-cal";
@@ -6028,9 +6590,9 @@ const CALENDAR_CLOSING_ATTRIBUTE = 'data-iox-closing';
6028
6590
  const CALENDAR_INSTANT_ATTRIBUTE = 'data-iox-instant';
6029
6591
  /** On the panel the builder canvas draws for its design-time Open state (in the flow, inert). */
6030
6592
  const CALENDAR_DESIGN_ATTRIBUTE = 'data-iox-cal-design';
6031
- /** A phone gets the sheet (the drawer curve, the scrim) — the same width the shared CSS keys on. */
6032
- const CALENDAR_SHEET_QUERY = '(max-width: 767px)';
6033
- const RUNTIME = '__ioxDatePicker';
6593
+ /** A phone gets the sheet (the drawer curve, the scrim) — the floating family's ONE decision (a popover too). */
6594
+ const CALENDAR_SHEET_QUERY = FLOATING_SHEET_QUERY;
6595
+ const RUNTIME$1 = '__ioxDatePicker';
6034
6596
  const ISO_ATTRIBUTE = 'data-iox-iso';
6035
6597
  const CHEVRON_BACK = 'M15 18l-6-6 6-6';
6036
6598
  const CHEVRON_AHEAD = 'M9 18l6-6-6-6';
@@ -6191,7 +6753,7 @@ class DatePickerRuntime {
6191
6753
  const form = field.closest('form');
6192
6754
  if (form && !options.onPick)
6193
6755
  this.listen(form, 'reset', () => setDateFieldValue(field, '', { lang: this.lang() }));
6194
- field[RUNTIME] = this;
6756
+ field[RUNTIME$1] = this;
6195
6757
  }
6196
6758
  get isOpen() { return !!this.panel; }
6197
6759
  value() { return partsOf(this.field).input?.value ?? ''; }
@@ -6200,7 +6762,7 @@ class DatePickerRuntime {
6200
6762
  for (const [target, type, fn] of this.listeners)
6201
6763
  target.removeEventListener(type, fn);
6202
6764
  this.listeners.length = 0;
6203
- delete this.field[RUNTIME];
6765
+ delete this.field[RUNTIME$1];
6204
6766
  }
6205
6767
  /** Open the calendar; `instant` (opened from the keyboard) skips the entrance and, later, the exit. */
6206
6768
  open(instant = false) {
@@ -6356,7 +6918,7 @@ class DatePickerRuntime {
6356
6918
  }
6357
6919
  /** The runtime already running a field, if any. */
6358
6920
  function datePickerOf(field) {
6359
- return field?.[RUNTIME] ?? null;
6921
+ return field?.[RUNTIME$1] ?? null;
6360
6922
  }
6361
6923
  /** Run a date field — once: a field already running is returned as is. Null when `field` holds no date button. */
6362
6924
  function attachDatePicker(field, options = {}) {
@@ -7269,6 +7831,257 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
7269
7831
  args: ['childrenHost', { read: ViewContainerRef, static: true }]
7270
7832
  }] } });
7271
7833
 
7834
+ /**
7835
+ * THE POPOVER on the live site (premium components M5, N4) — an anchored, NON-modal panel of ordinary nodes, opened by
7836
+ * its opener's `openPopover` / `togglePopover` action and closed by `closePopover`, Esc or a press outside it. ONE DOM
7837
+ * runtime for both engine paths (the component path and the string path render the same element: `[data-iox-popover]`,
7838
+ * `popover="manual"`, `role="dialog"`, `tabindex="-1"`, the node's own id); the builder canvas has its own twin that
7839
+ * places itself with the same `placeFloating`.
7840
+ *
7841
+ * - In the TOP LAYER (`showPopover`): above a fixed header and a slider's clipping; it still inherits the section's tokens
7842
+ * and scheme through the DOM, where the node sits.
7843
+ * - Placed beside its opener on the side the author picked (flipped at the viewport's edge, shifted inside it), lined up
7844
+ * with the opener's reading start, growing from it (Emil 47/123); placed again while the page scrolls or resizes.
7845
+ * - On a phone (`FLOATING_SHEET_QUERY`, the date picker's decision) it is a bottom sheet over the look's scrim, and the
7846
+ * page holds still under it.
7847
+ * - Focus moves INTO it on open (the panel itself: no ring after a tap), and back to the opener when Esc closes it, or
7848
+ * when a press outside closes it and focus had nowhere else to go. Focus leaving it (Tab) closes it.
7849
+ * - A keyboard open (Enter / Space on the opener) and Esc have no motion (Emil 2); the exit is faster than the entrance
7850
+ * (the shared CSS, on the popover tokens).
7851
+ */
7852
+ var PopoverAttribute;
7853
+ (function (PopoverAttribute) {
7854
+ PopoverAttribute["Root"] = "data-iox-popover";
7855
+ PopoverAttribute["Placement"] = "data-iox-popover-placement";
7856
+ /** On the panel while it is a phone's bottom sheet. */
7857
+ PopoverAttribute["Sheet"] = "data-iox-sheet";
7858
+ /** The side it ended on (after a flip). */
7859
+ PopoverAttribute["Side"] = "data-iox-side";
7860
+ /** Opened from the keyboard: it appears and goes with no motion. */
7861
+ PopoverAttribute["Instant"] = "data-iox-instant";
7862
+ /** On the panel while it is open (the canvas twin reads the same name). */
7863
+ PopoverAttribute["Open"] = "data-iox-open";
7864
+ })(PopoverAttribute || (PopoverAttribute = {}));
7865
+ const POPOVER_SELECTOR = `[${PopoverAttribute.Root}]`;
7866
+ const STATE = '__ioxPopover';
7867
+ const stateOf = (el) => el[STATE];
7868
+ /** True while the popover is open (an exit that is playing counts as closed). */
7869
+ function isPopoverOpen(el) {
7870
+ return !!el && !!stateOf(el) && !isExiting(el);
7871
+ }
7872
+ /** The side a panel asks for (Below unless it names another). */
7873
+ function popoverSide(value) {
7874
+ return Object.values(FloatingSide).includes(String(value)) ? value : FloatingSide.Below;
7875
+ }
7876
+ /** The opener a popover is anchored to: the one that opened it, else the first element announcing it. */
7877
+ function anchorOf(el, opener) {
7878
+ if (opener?.isConnected)
7879
+ return opener;
7880
+ return el.id ? el.ownerDocument.querySelector(popoverOpenersSelector(el.id)) : null;
7881
+ }
7882
+ /** Every element announcing this popover says whether it is open (`aria-expanded`). */
7883
+ function syncOpeners(el, open) {
7884
+ if (!el.id)
7885
+ return;
7886
+ el.ownerDocument.querySelectorAll(popoverOpenersSelector(el.id)).forEach(o => o.setAttribute(OpenerAttribute.Expanded, open ? 'true' : 'false'));
7887
+ }
7888
+ /** Beside its opener, inside the viewport, growing from it — the ONE `placeFloating`. A sheet is the shared CSS's. */
7889
+ function placePopover(el, opener) {
7890
+ if (el.hasAttribute(PopoverAttribute.Sheet))
7891
+ return;
7892
+ const view = el.ownerDocument.defaultView;
7893
+ if (!view)
7894
+ return;
7895
+ const anchor = opener?.getBoundingClientRect();
7896
+ const rtl = view.getComputedStyle(opener ?? el).direction === 'rtl';
7897
+ const size = { width: el.offsetWidth, height: el.offsetHeight };
7898
+ const viewport = { width: view.innerWidth, height: view.innerHeight };
7899
+ // No opener on the page (an action from elsewhere, an opener scrolled away): under the top of the viewport, centred.
7900
+ const rect = anchor
7901
+ ? { top: anchor.top, left: anchor.left, width: anchor.width, height: anchor.height }
7902
+ : { top: 0, left: viewport.width / 2, width: 0, height: 0 };
7903
+ const spot = placeFloating(rect, size, viewport, { side: popoverSide(el.getAttribute(PopoverAttribute.Placement)), align: anchor ? FloatingAlign.Start : FloatingAlign.Center, rtl });
7904
+ el.style.top = `${spot.top}px`;
7905
+ el.style.left = `${spot.left}px`;
7906
+ el.style.transformOrigin = spot.origin;
7907
+ el.setAttribute(PopoverAttribute.Side, spot.side);
7908
+ }
7909
+ /** Open the popover beside `options.opener`. Opening one that is leaving keeps it open. */
7910
+ function openPopover(el, options = {}) {
7911
+ if (!el)
7912
+ return;
7913
+ const existing = stateOf(el);
7914
+ if (existing) {
7915
+ if (isExiting(el)) {
7916
+ cancelExit(el);
7917
+ existing.closing = null;
7918
+ }
7919
+ return;
7920
+ }
7921
+ const doc = el.ownerDocument;
7922
+ const view = doc.defaultView;
7923
+ const opener = anchorOf(el, options.opener);
7924
+ const sheet = !!view?.matchMedia?.(FLOATING_SHEET_QUERY).matches;
7925
+ const state = { opener, cleanups: [], closing: null };
7926
+ el[STATE] = state;
7927
+ el.toggleAttribute(PopoverAttribute.Sheet, sheet);
7928
+ el.toggleAttribute(PopoverAttribute.Instant, !!options.instant);
7929
+ el.setAttribute(PopoverAttribute.Open, '');
7930
+ // A sheet sits on the bottom edge (the shared CSS); a placement left from a laptop-sized open must not move it.
7931
+ if (sheet) {
7932
+ el.style.removeProperty('top');
7933
+ el.style.removeProperty('left');
7934
+ }
7935
+ showInTopLayer(el);
7936
+ placePopover(el, opener);
7937
+ el.focus({ preventScroll: true });
7938
+ syncOpeners(el, true);
7939
+ if (sheet && options.lockScroll) {
7940
+ options.lockScroll();
7941
+ state.unlockScroll = options.unlockScroll;
7942
+ }
7943
+ wire(el, state);
7944
+ }
7945
+ /** Close the popover with its exit (the shared CSS), then hand focus back as asked. */
7946
+ function closePopover(el, options = {}) {
7947
+ if (!el)
7948
+ return Promise.resolve();
7949
+ const state = stateOf(el);
7950
+ if (!state)
7951
+ return Promise.resolve();
7952
+ if (state.closing && isExiting(el))
7953
+ return state.closing;
7954
+ syncOpeners(el, false);
7955
+ for (const c of state.cleanups)
7956
+ c();
7957
+ state.cleanups.length = 0;
7958
+ const animate = !options.instant && !el.hasAttribute(PopoverAttribute.Instant) && motionAllowed(el.ownerDocument.defaultView);
7959
+ const done = runExitMotion(el, { animate }).then(finish => {
7960
+ if (!finish) {
7961
+ wire(el, state);
7962
+ syncOpeners(el, true);
7963
+ return;
7964
+ }
7965
+ finishClose(el, state, options.returnFocus);
7966
+ });
7967
+ state.closing = done;
7968
+ return done;
7969
+ }
7970
+ /** Toggle: close an open one, open a closed (or leaving) one. */
7971
+ function togglePopover(el, options = {}) {
7972
+ if (!el)
7973
+ return;
7974
+ if (isPopoverOpen(el))
7975
+ void closePopover(el, { instant: options.instant, returnFocus: options.instant ? true : 'if-lost' });
7976
+ else
7977
+ openPopover(el, options);
7978
+ }
7979
+ /** Close every open popover under `root` at once (a page leaving, a route change). */
7980
+ function closeAllPopovers(root) {
7981
+ root?.querySelectorAll?.(POPOVER_SELECTOR).forEach(el => { if (stateOf(el))
7982
+ void closePopover(el, { instant: true }); });
7983
+ }
7984
+ function finishClose(el, state, returnFocus) {
7985
+ const doc = el.ownerDocument;
7986
+ const active = doc.activeElement;
7987
+ const lost = !active || active === doc.body || el.contains(active);
7988
+ hideFromTopLayer(el);
7989
+ el.removeAttribute(PopoverAttribute.Open);
7990
+ el.removeAttribute(PopoverAttribute.Instant);
7991
+ cancelExit(el);
7992
+ state.unlockScroll?.();
7993
+ delete el[STATE];
7994
+ const opener = state.opener;
7995
+ if (opener?.isConnected && (returnFocus === true || (returnFocus === 'if-lost' && lost)))
7996
+ opener.focus({ preventScroll: true });
7997
+ }
7998
+ /** While open: Esc, a press outside, focus leaving, and the page moving under it. */
7999
+ function wire(el, state) {
8000
+ const doc = el.ownerDocument;
8001
+ const view = doc.defaultView;
8002
+ const on = (target, type, fn, opts) => {
8003
+ target.addEventListener(type, fn, opts);
8004
+ state.cleanups.push(() => target.removeEventListener(type, fn, opts));
8005
+ };
8006
+ const insideOpener = (n) => !!n && !!el.id && !!n.closest?.(popoverOpenersSelector(el.id));
8007
+ on(doc, 'keydown', e => {
8008
+ if (e.key !== 'Escape')
8009
+ return;
8010
+ e.stopPropagation();
8011
+ void closePopover(el, { instant: true, returnFocus: true });
8012
+ });
8013
+ on(doc, 'pointerdown', e => {
8014
+ const target = e.target;
8015
+ if (!target || el.contains(target) || insideOpener(target))
8016
+ return;
8017
+ void closePopover(el, { returnFocus: 'if-lost' });
8018
+ }, true);
8019
+ on(el, 'focusout', e => {
8020
+ const next = e.relatedTarget;
8021
+ if (!next || el.contains(next) || insideOpener(next))
8022
+ return;
8023
+ void closePopover(el, {});
8024
+ });
8025
+ if (view && !el.hasAttribute(PopoverAttribute.Sheet)) {
8026
+ let frame = 0;
8027
+ const follow = () => {
8028
+ if (frame)
8029
+ return;
8030
+ frame = view.requestAnimationFrame(() => { frame = 0; placePopover(el, state.opener); });
8031
+ };
8032
+ on(view, 'scroll', follow, { capture: true, passive: true });
8033
+ on(view, 'resize', follow, { passive: true });
8034
+ state.cleanups.push(() => { if (frame)
8035
+ view.cancelAnimationFrame(frame); });
8036
+ }
8037
+ }
8038
+
8039
+ /**
8040
+ * The Popover's LIVE twin on the engine's component path (premium components M5, N4). Directive-host: the panel IS the
8041
+ * host, carrying exactly the markup the string renderer writes — `data-iox-popover`, `popover="manual"` (closed in the
8042
+ * raw SSR with no script: the browser hides a popover until it is shown), `role="dialog"`, `tabindex="-1"` (focus moves
8043
+ * to the panel on open) and the side the author picked — so ONE runtime (`openPopover` / `closePopover`) treats both
8044
+ * paths alike. Its id and accessible name are the shared `nodeA11yAttributes`, applied by the engine.
8045
+ */
8046
+ class IoxPopoverComponent {
8047
+ constructor() {
8048
+ this.nodeId = '';
8049
+ this.placement = 'below';
8050
+ }
8051
+ get side() { return popoverSide(this.placement); }
8052
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8053
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: IoxPopoverComponent, isStandalone: false, selector: "div[iox-popover]", inputs: { nodeId: "nodeId", placement: "placement" }, host: { properties: { "class": "'iox-popover iox-node-' + nodeId", "attr.data-iox-popover": "''", "attr.popover": "'manual'", "attr.role": "'dialog'", "attr.tabindex": "'-1'", "attr.data-iox-popover-placement": "side" } }, viewQueries: [{ propertyName: "childrenHost", first: true, predicate: ["childrenHost"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: `<ng-container #childrenHost></ng-container>`, isInline: true, styles: [":host{box-sizing:border-box}\n"] }); }
8054
+ }
8055
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxPopoverComponent, decorators: [{
8056
+ type: Component,
8057
+ args: [{ selector: 'div[iox-popover]', template: `<ng-container #childrenHost></ng-container>`, host: {
8058
+ '[class]': "'iox-popover iox-node-' + nodeId",
8059
+ '[attr.data-iox-popover]': "''",
8060
+ '[attr.popover]': "'manual'",
8061
+ '[attr.role]': "'dialog'",
8062
+ '[attr.tabindex]': "'-1'",
8063
+ '[attr.data-iox-popover-placement]': 'side',
8064
+ }, standalone: false, styles: [":host{box-sizing:border-box}\n"] }]
8065
+ }], propDecorators: { nodeId: [{
8066
+ type: Input
8067
+ }], placement: [{
8068
+ type: Input
8069
+ }], childrenHost: [{
8070
+ type: ViewChild,
8071
+ args: ['childrenHost', { read: ViewContainerRef, static: true }]
8072
+ }] } });
8073
+ /** The attributes the string renderer writes on a Popover panel (beside its class, id and name) — the component's host
8074
+ * bindings, as data, so the two paths cannot drift. */
8075
+ function popoverPanelAttributes(placement) {
8076
+ return {
8077
+ [PopoverAttribute.Root]: '',
8078
+ popover: 'manual',
8079
+ role: 'dialog',
8080
+ tabindex: '-1',
8081
+ [PopoverAttribute.Placement]: popoverSide(typeof placement === 'string' ? placement : null),
8082
+ };
8083
+ }
8084
+
7272
8085
  const escapeHtml = (v) => v.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
7273
8086
  /**
7274
8087
  * `iox-video` — the Video's ONE component (CC3, board 5), like the Gallery's: the builder canvas, the admin's Preview
@@ -7437,6 +8250,7 @@ const COMPONENTS = [
7437
8250
  IoxFormSuccessComponent,
7438
8251
  IoxFormErrorComponent,
7439
8252
  IoxDialogComponent,
8253
+ IoxPopoverComponent,
7440
8254
  ];
7441
8255
  class IoxBuilderComponentsModule {
7442
8256
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxBuilderComponentsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
@@ -7465,7 +8279,8 @@ class IoxBuilderComponentsModule {
7465
8279
  IoxSubmitButtonComponent,
7466
8280
  IoxFormSuccessComponent,
7467
8281
  IoxFormErrorComponent,
7468
- IoxDialogComponent], imports: [CommonModule], exports: [BuilderImageComponent,
8282
+ IoxDialogComponent,
8283
+ IoxPopoverComponent], imports: [CommonModule], exports: [BuilderImageComponent,
7469
8284
  BuilderSpacerComponent,
7470
8285
  CardComponent,
7471
8286
  BuilderDividerComponent,
@@ -7490,7 +8305,8 @@ class IoxBuilderComponentsModule {
7490
8305
  IoxSubmitButtonComponent,
7491
8306
  IoxFormSuccessComponent,
7492
8307
  IoxFormErrorComponent,
7493
- IoxDialogComponent] }); }
8308
+ IoxDialogComponent,
8309
+ IoxPopoverComponent] }); }
7494
8310
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxBuilderComponentsModule, imports: [CommonModule] }); }
7495
8311
  }
7496
8312
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxBuilderComponentsModule, decorators: [{
@@ -7548,6 +8364,8 @@ class IoxComponentRegistryService {
7548
8364
  // Dialog (DS2) — the live <dialog> twin, so a page holding one no longer falls back to
7549
8365
  // the string renderer.
7550
8366
  ['Dialog', IoxDialogComponent],
8367
+ // Popover (premium M5) — the anchored, non-modal panel; its runtime is the shared popover.runtime.
8368
+ ['Popover', IoxPopoverComponent],
7551
8369
  // Video (CC3) — one component for the canvas (poster only), Preview and the live site.
7552
8370
  ['Video', IoxVideoComponent],
7553
8371
  ]);
@@ -8045,7 +8863,10 @@ function watchHeaders(source, root, options = {}) {
8045
8863
  const DECORATIVE_TYPES = ['Icon'];
8046
8864
  const DIALOG_NODE_TYPE = 'Dialog';
8047
8865
  /** Named for assistive technology, but never an icon-only CONTROL the audit should flag: a Dialog, a carousel. */
8048
- const NAME_NOT_REQUIRED_TYPES = [DIALOG_NODE_TYPE, 'SliderContainer'];
8866
+ const NAME_NOT_REQUIRED_TYPES = [DIALOG_NODE_TYPE, POPOVER_NODE_TYPE, 'SliderContainer'];
8867
+ /** The side a tooltip takes when its node names none — written to nothing (the runtime's default). */
8868
+ const TOOLTIP_DEFAULT_SIDE = 'above';
8869
+ const TOOLTIP_SIDES = ['above', 'below', 'start', 'end'];
8049
8870
  const SLIDER_PAUSE_ROLE = 'slider.pause';
8050
8871
  /** A Segmented control's root (CC4b) is a radio group that must be named (board 10: an empty name is an audit
8051
8872
  * finding). The main entry does not import composite-core, so the role is spelled here — a11y spec pins it. */
@@ -8066,6 +8887,7 @@ const MANAGED_A11Y_ATTRIBUTES = [
8066
8887
  A11yAttribute.Label, A11yAttribute.Hidden, A11yAttribute.Current, A11yAttribute.Id,
8067
8888
  'aria-haspopup', 'aria-expanded', 'aria-controls', DIALOG_CLOSE_ABOVE_ATTRIBUTE,
8068
8889
  HeaderAttribute.Behaviour, HeaderAttribute.Edge, SLIDE_CAPTION_ATTRIBUTE,
8890
+ TOOLTIP_ATTRIBUTE, TOOLTIP_PLACEMENT_ATTRIBUTE,
8069
8891
  ];
8070
8892
  /** A content trait's value from `props` (serialized) or `traits[]` (builder). */
8071
8893
  function nodeTraitValue(node, name) {
@@ -8101,7 +8923,15 @@ function nodeA11yAttributes(node, ctx = {}) {
8101
8923
  if (CURRENT_LINK_TYPES.includes(node.type) && isCurrentLink(ctx.href, ctx.currentPath, ctx.currentLink)) {
8102
8924
  out[A11yAttribute.Current] = ARIA_CURRENT_PAGE;
8103
8925
  }
8104
- Object.assign(out, dialogOpenerAttributes(node));
8926
+ // A dialog opener wins over a popover opener (one aria-controls); a node opening neither gets neither.
8927
+ const opener = dialogOpenerAttributes(node);
8928
+ Object.assign(out, Object.keys(opener).length ? opener : popoverOpenerAttributes(node));
8929
+ Object.assign(out, tooltipAttributes(node, !!name));
8930
+ if (node.type === POPOVER_NODE_TYPE) {
8931
+ const id = node.styleId ?? node.id;
8932
+ if (id)
8933
+ out[A11yAttribute.Id] = popoverDomId(id);
8934
+ }
8105
8935
  if (node.type === DIALOG_NODE_TYPE) {
8106
8936
  const id = node.styleId ?? node.id;
8107
8937
  if (id)
@@ -8119,6 +8949,28 @@ function nodeA11yAttributes(node, ctx = {}) {
8119
8949
  }
8120
8950
  return out;
8121
8951
  }
8952
+ /** The node's tooltip words (premium M5), trimmed, or '' — only on the types that carry one. */
8953
+ function tooltipWords(node) {
8954
+ return node?.type && TOOLTIP_TYPES.includes(node.type) ? trimmed$1(nodeTraitValue(node, A11yTrait.Tooltip)) : '';
8955
+ }
8956
+ /**
8957
+ * A tooltip's attributes: its words and its side for the runtime, and — on a control that shows only an icon and has no
8958
+ * Accessible name — the words as its `aria-label` too: the tooltip IS its name there (WCAG 4.1.2), and the runtime then
8959
+ * skips the description so it is not read twice. A control with words of its own keeps them as its name (2.5.3) and is
8960
+ * DESCRIBED by the tooltip.
8961
+ */
8962
+ function tooltipAttributes(node, named) {
8963
+ const words = tooltipWords(node);
8964
+ if (!words)
8965
+ return {};
8966
+ const out = { [TOOLTIP_ATTRIBUTE]: words };
8967
+ const side = trimmed$1(nodeTraitValue(node, A11yTrait.TooltipPlacement));
8968
+ if (side && side !== TOOLTIP_DEFAULT_SIDE && TOOLTIP_SIDES.includes(side))
8969
+ out[TOOLTIP_PLACEMENT_ATTRIBUTE] = side;
8970
+ if (!named && iconOnly(node))
8971
+ out[A11yAttribute.Label] = words;
8972
+ return out;
8973
+ }
8122
8974
  /** The attributes as an HTML string fragment (` name="value"`…) for a string renderer. */
8123
8975
  function serializeA11yAttributes(attrs, escape) {
8124
8976
  return Object.entries(attrs).map(([k, v]) => ` ${k}="${escape(v)}"`).join('');
@@ -8160,8 +9012,12 @@ function needsAccessibleName(node) {
8160
9012
  // A Slider's pause control is named by its runtime ("Stop / Start automatic slide show", CC3).
8161
9013
  if (node.role === SLIDER_PAUSE_ROLE)
8162
9014
  return false;
8163
- if (trimmed$1(nodeTraitValue(node, A11yTrait.AccessibleName)))
9015
+ if (trimmed$1(nodeTraitValue(node, A11yTrait.AccessibleName)) || tooltipWords(node))
8164
9016
  return false;
9017
+ return iconOnly(node);
9018
+ }
9019
+ /** True when the node shows an icon and no words of its own (scanning what it holds). */
9020
+ function iconOnly(node) {
8165
9021
  const acc = { words: false, icon: false };
8166
9022
  scanNames(node, acc);
8167
9023
  return acc.icon && !acc.words;
@@ -10499,7 +11355,7 @@ class StringFormRuntime {
10499
11355
  this.apply(response);
10500
11356
  }
10501
11357
  apply(response) {
10502
- const outcome = submitOutcome(response);
11358
+ const outcome = submitOutcome(response, this.form.getAttribute(FORM_AFTER_SEND_ATTRIBUTE));
10503
11359
  this.setRenderState(outcome.state);
10504
11360
  if (outcome.errors) {
10505
11361
  this.state = { ...this.state, submitted: true, submitting: false, errors: { ...outcome.errors } };
@@ -10515,6 +11371,8 @@ class StringFormRuntime {
10515
11371
  this.form.reset();
10516
11372
  this.state = resetFormState();
10517
11373
  this.render();
11374
+ if (outcome.toast)
11375
+ raiseFormToast(this.form);
10518
11376
  if (outcome.redirect)
10519
11377
  this.options.navigate?.(outcome.redirect);
10520
11378
  }
@@ -10648,6 +11506,342 @@ function attachStringForm(form, options = {}) {
10648
11506
  return spec ? new StringFormRuntime(form, spec, options) : null;
10649
11507
  }
10650
11508
 
11509
+ /**
11510
+ * THE TOOLTIP (premium components M5, N4) — words for a control that has none of its own (an icon button, a gallery
11511
+ * arrow). ONE DOM runtime for the live site (both engine paths) and the builder canvas; the trigger carries what the
11512
+ * shared `nodeA11yAttributes` rendered (`data-iox-tooltip`, `data-iox-tooltip-placement`), so the raw SSR already says it.
11513
+ *
11514
+ * - Shows on HOVER for a mouse or a pen (never a finger — a tap is not a hover) and on KEYBOARD focus
11515
+ * (`:focus-visible`), never on a tap. A press, Esc, a scroll or leaving hides it.
11516
+ * - The first waits (`TOOLTIP_FIRST_DELAY_MS`); while one is open, or within `TOOLTIP_WARM_MS` of one closing, the next
11517
+ * opens AT ONCE with no motion (Emil 125). Keyboard focus opens at once, with no motion (Emil 2).
11518
+ * - Grows from its trigger (the origin `placeFloating` gives, Emil 47/124); leaves faster than it came (Emil 30).
11519
+ * - Hoverable and dismissible (WCAG 1.4.13): the pointer may move onto it; Esc hides it until the pointer or focus
11520
+ * leaves the trigger.
11521
+ * - `aria-describedby` points at it while it is shown — unless the tooltip IS the control's name (an icon-only control:
11522
+ * `nodeA11yAttributes` already made the words its `aria-label`; describing it twice would read it twice).
11523
+ */
11524
+ /** On the drawn tooltip while it appears or moves with no motion (a neighbour, keyboard focus). */
11525
+ const TOOLTIP_INSTANT_ATTRIBUTE = 'data-iox-instant';
11526
+ /** On a trigger whose tooltip the builder canvas holds open (the design-time "Showing on canvas" state). */
11527
+ const TOOLTIP_PINNED_ATTRIBUTE = 'data-iox-tooltip-pinned';
11528
+ /** The side the drawn tooltip ended on (after a flip) — the shared CSS reads it for nothing but the e2e reads it. */
11529
+ const TOOLTIP_SIDE_ATTRIBUTE = 'data-iox-side';
11530
+ var TooltipClass;
11531
+ (function (TooltipClass) {
11532
+ TooltipClass["Tip"] = "iox-tooltip";
11533
+ /** The element style's marker — set when the trigger wears the design system (a legacy page keeps the literals). */
11534
+ TooltipClass["Marker"] = "iox-es-tooltip";
11535
+ })(TooltipClass || (TooltipClass = {}));
11536
+ /** The FIRST tooltip waits this long under a resting pointer (Emil 125: the first waits, the next does not). */
11537
+ const TOOLTIP_FIRST_DELAY_MS = 500;
11538
+ /** A tooltip opened within this window of another closing opens at once — moving along a row of icons. */
11539
+ const TOOLTIP_WARM_MS = 300;
11540
+ /** Leaving the trigger hides after this grace, so the pointer can cross onto the tooltip (WCAG 1.4.13 hoverable). */
11541
+ const TOOLTIP_HIDE_GRACE_MS = 100;
11542
+ /** The marker of a node wearing the design system (DS2.1): a floating element raised from it wears the look. */
11543
+ const DESIGN_MARKER = '.iox-es';
11544
+ /** How long before a tooltip shows, and whether it shows with no motion. Pure — the runtime asks it on every hover. */
11545
+ function tooltipTiming(state, now, firstDelayMs = TOOLTIP_FIRST_DELAY_MS, warmMs = TOOLTIP_WARM_MS) {
11546
+ const warm = state.open || (state.lastClosedAt !== null && now - state.lastClosedAt <= warmMs);
11547
+ return warm ? { delayMs: 0, instant: true } : { delayMs: firstDelayMs, instant: false };
11548
+ }
11549
+ /** The side a trigger asks for (Above when it names none, or names something else). */
11550
+ function tooltipSide(value) {
11551
+ return Object.values(FloatingSide).includes(String(value)) ? value : FloatingSide.Above;
11552
+ }
11553
+ const RUNTIME = '__ioxTooltips';
11554
+ let tipSeq = 0;
11555
+ const words = (el) => (el?.getAttribute(TOOLTIP_ATTRIBUTE) ?? '').trim();
11556
+ /** Keyboard focus (`:focus-visible`); an engine that cannot answer is treated as keyboard focus — a tooltip too many
11557
+ * beats a keyboard user with none. */
11558
+ const focusVisible = (el) => {
11559
+ try {
11560
+ return el.matches(':focus-visible');
11561
+ }
11562
+ catch {
11563
+ return true;
11564
+ }
11565
+ };
11566
+ /** The runtime over one root (the document on the site, the page's frame on the canvas). Built by `attachTooltips`. */
11567
+ class TooltipRuntime {
11568
+ constructor(root, options = {}) {
11569
+ this.root = root;
11570
+ this.options = options;
11571
+ this.tip = null;
11572
+ this.trigger = null;
11573
+ this.pinned = null;
11574
+ this.pinWatch = null;
11575
+ this.suppressed = null;
11576
+ this.showTimer = null;
11577
+ this.hideTimer = null;
11578
+ this.lastClosedAt = null;
11579
+ this.exiting = false;
11580
+ this.listeners = [];
11581
+ const doc = this.doc;
11582
+ this.listen(root, 'pointerover', e => this.onPointerOver(e));
11583
+ this.listen(root, 'pointerout', e => this.onPointerOut(e));
11584
+ this.listen(root, 'focusin', e => this.onFocusIn(e));
11585
+ this.listen(root, 'focusout', e => this.onFocusOut(e));
11586
+ this.listen(root, 'pointerdown', () => this.dismiss(), true);
11587
+ this.listen(doc, 'keydown', e => { if (e.key === 'Escape')
11588
+ this.escape(); });
11589
+ if (doc.defaultView)
11590
+ this.listen(doc.defaultView, 'scroll', () => this.onScroll(), { capture: true, passive: true });
11591
+ root[RUNTIME] = this;
11592
+ }
11593
+ /** The trigger whose tooltip is shown, or null. */
11594
+ get shownFor() { return this.tip && !this.tip.hidden && !this.exiting ? this.trigger : null; }
11595
+ /** The drawn tooltip (created on first show). */
11596
+ get element() { return this.tip; }
11597
+ /** Show the trigger's tooltip now. `instant` skips the entrance (a neighbour, keyboard focus, the canvas pin). */
11598
+ show(trigger, instant = false) {
11599
+ const text = words(trigger);
11600
+ if (!text)
11601
+ return;
11602
+ this.clearTimers();
11603
+ const tip = this.ensureTip(trigger);
11604
+ if (this.trigger && this.trigger !== trigger)
11605
+ this.describe(this.trigger, false);
11606
+ this.trigger = trigger;
11607
+ if (this.exiting) {
11608
+ this.exiting = false;
11609
+ cancelExit(tip);
11610
+ }
11611
+ tip.textContent = text;
11612
+ tip.classList.toggle(TooltipClass.Marker, !!trigger.closest(DESIGN_MARKER));
11613
+ tip.toggleAttribute(TOOLTIP_INSTANT_ATTRIBUTE, instant);
11614
+ tip.hidden = false;
11615
+ this.place(trigger, tip);
11616
+ this.describe(trigger, true);
11617
+ }
11618
+ /** Hide it — with its exit, or `immediate`ly (a press, Esc, a scroll, keyboard focus leaving). */
11619
+ hide(immediate = false) {
11620
+ this.clearTimers();
11621
+ const tip = this.tip;
11622
+ const trigger = this.trigger;
11623
+ if (!tip || tip.hidden || this.exiting)
11624
+ return;
11625
+ if (trigger === this.pinned)
11626
+ return;
11627
+ if (trigger)
11628
+ this.describe(trigger, false);
11629
+ this.lastClosedAt = this.now();
11630
+ const animate = !immediate && !tip.hasAttribute(TOOLTIP_INSTANT_ATTRIBUTE) && motionAllowed(this.doc.defaultView);
11631
+ this.exiting = true;
11632
+ void runExitMotion(tip, { animate }).then(done => {
11633
+ if (!done || !this.exiting)
11634
+ return;
11635
+ this.exiting = false;
11636
+ tip.hidden = true;
11637
+ cancelExit(tip);
11638
+ this.trigger = null;
11639
+ });
11640
+ }
11641
+ /** The builder canvas's design-time state: hold the trigger's tooltip open (true) or let it go (false). */
11642
+ pin(trigger, on) {
11643
+ if (on) {
11644
+ this.pinned = trigger;
11645
+ trigger.setAttribute(TOOLTIP_PINNED_ATTRIBUTE, '');
11646
+ this.show(trigger, true);
11647
+ // The author edits its words or side while it is held up: it follows at once.
11648
+ this.pinWatch?.disconnect();
11649
+ const View = this.doc.defaultView;
11650
+ if (View?.MutationObserver) {
11651
+ this.pinWatch = new View.MutationObserver(() => { if (this.pinned === trigger)
11652
+ this.show(trigger, true); });
11653
+ this.pinWatch.observe(trigger, { attributes: true, attributeFilter: [TOOLTIP_ATTRIBUTE, TOOLTIP_PLACEMENT_ATTRIBUTE] });
11654
+ }
11655
+ return;
11656
+ }
11657
+ trigger.removeAttribute(TOOLTIP_PINNED_ATTRIBUTE);
11658
+ if (this.pinned === trigger) {
11659
+ this.pinned = null;
11660
+ this.pinWatch?.disconnect();
11661
+ this.pinWatch = null;
11662
+ }
11663
+ if (this.trigger === trigger)
11664
+ this.hide(true);
11665
+ }
11666
+ /** Place the shown tooltip again (the trigger moved — the canvas re-laid out). */
11667
+ reposition() {
11668
+ if (this.tip && this.trigger && !this.tip.hidden)
11669
+ this.place(this.trigger, this.tip);
11670
+ }
11671
+ destroy() {
11672
+ this.clearTimers();
11673
+ this.pinWatch?.disconnect();
11674
+ for (const [target, type, fn, opts] of this.listeners)
11675
+ target.removeEventListener(type, fn, opts);
11676
+ this.listeners.length = 0;
11677
+ if (this.trigger)
11678
+ this.describe(this.trigger, false);
11679
+ this.tip?.remove();
11680
+ this.tip = null;
11681
+ delete this.root[RUNTIME];
11682
+ }
11683
+ get doc() {
11684
+ return this.root.nodeType === 9 ? this.root : this.root.ownerDocument;
11685
+ }
11686
+ now() {
11687
+ return this.options.now?.() ?? (this.doc.defaultView?.performance?.now() ?? Date.now());
11688
+ }
11689
+ triggerOf(target) {
11690
+ const el = target?.closest?.(`[${TOOLTIP_ATTRIBUTE}]`);
11691
+ return el && words(el) ? el : null;
11692
+ }
11693
+ onPointerOver(e) {
11694
+ const trigger = this.triggerOf(e.target);
11695
+ if (this.tip && e.target && this.tip.contains(e.target)) {
11696
+ this.cancelHide();
11697
+ return;
11698
+ }
11699
+ if (!trigger || !isHoverPointer(e) || trigger === this.suppressed)
11700
+ return;
11701
+ if (trigger === this.shownFor) {
11702
+ this.cancelHide();
11703
+ return;
11704
+ }
11705
+ const timing = tooltipTiming({ open: !!this.shownFor, lastClosedAt: this.lastClosedAt }, this.now());
11706
+ this.clearTimers();
11707
+ if (timing.delayMs <= 0) {
11708
+ this.show(trigger, timing.instant);
11709
+ return;
11710
+ }
11711
+ this.showTimer = setTimeout(() => { this.showTimer = null; this.show(trigger, timing.instant); }, timing.delayMs);
11712
+ }
11713
+ onPointerOut(e) {
11714
+ const from = this.triggerOf(e.target) ?? (this.tip && this.tip.contains(e.target) ? this.trigger : null);
11715
+ if (!from)
11716
+ return;
11717
+ const to = e.relatedTarget;
11718
+ if (to && (from.contains(to) || this.tip?.contains(to)))
11719
+ return;
11720
+ if (from === this.suppressed)
11721
+ this.suppressed = null;
11722
+ if (this.showTimer) {
11723
+ clearTimeout(this.showTimer);
11724
+ this.showTimer = null;
11725
+ }
11726
+ if (this.shownFor !== from)
11727
+ return;
11728
+ this.cancelHide();
11729
+ this.hideTimer = setTimeout(() => { this.hideTimer = null; this.hide(); }, TOOLTIP_HIDE_GRACE_MS);
11730
+ }
11731
+ onFocusIn(e) {
11732
+ const trigger = this.triggerOf(e.target);
11733
+ if (!trigger || trigger !== e.target || !focusVisible(trigger))
11734
+ return;
11735
+ this.show(trigger, true);
11736
+ }
11737
+ onFocusOut(e) {
11738
+ const trigger = this.triggerOf(e.target);
11739
+ if (!trigger)
11740
+ return;
11741
+ if (trigger === this.suppressed)
11742
+ this.suppressed = null;
11743
+ if (trigger === this.shownFor)
11744
+ this.hide(true);
11745
+ }
11746
+ escape() {
11747
+ if (!this.shownFor || this.shownFor === this.pinned)
11748
+ return;
11749
+ this.suppressed = this.shownFor;
11750
+ this.hide(true);
11751
+ }
11752
+ /** A scroll hides a hover / focus tooltip (it would float away from its trigger); a pinned one follows it. */
11753
+ onScroll() {
11754
+ if (this.pinned && this.shownFor === this.pinned) {
11755
+ this.reposition();
11756
+ return;
11757
+ }
11758
+ this.dismiss();
11759
+ }
11760
+ /** A press, a scroll: gone at once (and a pending one never shows). */
11761
+ dismiss() {
11762
+ if (this.showTimer) {
11763
+ clearTimeout(this.showTimer);
11764
+ this.showTimer = null;
11765
+ }
11766
+ if (this.shownFor && this.shownFor !== this.pinned)
11767
+ this.hide(true);
11768
+ }
11769
+ cancelHide() {
11770
+ if (this.hideTimer) {
11771
+ clearTimeout(this.hideTimer);
11772
+ this.hideTimer = null;
11773
+ }
11774
+ }
11775
+ clearTimers() {
11776
+ if (this.showTimer) {
11777
+ clearTimeout(this.showTimer);
11778
+ this.showTimer = null;
11779
+ }
11780
+ this.cancelHide();
11781
+ }
11782
+ /** The drawn tooltip, in the layer of the trigger's scope (a lightbox's arrow → the lightbox's own layer). */
11783
+ ensureTip(trigger) {
11784
+ const scope = floatingScope(trigger, this.options);
11785
+ const layer = scope ? floatingLayer(scope, this.options) : null;
11786
+ if (!this.tip) {
11787
+ const tip = this.doc.createElement('div');
11788
+ tip.className = TooltipClass.Tip;
11789
+ tip.id = `iox-tooltip-${++tipSeq}`;
11790
+ tip.setAttribute('role', 'tooltip');
11791
+ tip.hidden = true;
11792
+ // The tooltip lives in the floating layer, outside the root the runtime listens on: crossing onto it and
11793
+ // leaving it are heard on the tooltip itself (WCAG 1.4.13 hoverable).
11794
+ this.listen(tip, 'pointerenter', () => this.cancelHide());
11795
+ this.listen(tip, 'pointerleave', e => this.onPointerOut(e));
11796
+ this.tip = tip;
11797
+ }
11798
+ if (layer && this.tip.parentElement !== layer)
11799
+ layer.appendChild(this.tip);
11800
+ return this.tip;
11801
+ }
11802
+ /** Where it goes: the side the trigger asks for (flipped when there is no room), centred on it, inside the layer. */
11803
+ place(trigger, tip) {
11804
+ const layer = tip.parentElement;
11805
+ if (!layer)
11806
+ return;
11807
+ const frame = layerFrame(layer);
11808
+ const rtl = this.doc.defaultView?.getComputedStyle(trigger).direction === 'rtl';
11809
+ const spot = placeFloating(rectInLayer(trigger, frame), { width: tip.offsetWidth, height: tip.offsetHeight }, frame.viewport, { side: tooltipSide(trigger.getAttribute(TOOLTIP_PLACEMENT_ATTRIBUTE)), align: FloatingAlign.Center, rtl });
11810
+ tip.style.top = `${spot.top}px`;
11811
+ tip.style.left = `${spot.left}px`;
11812
+ tip.style.transformOrigin = spot.origin;
11813
+ tip.setAttribute(TOOLTIP_SIDE_ATTRIBUTE, spot.side);
11814
+ }
11815
+ /** Point the trigger's description at the tooltip — unless the tooltip is already its name. */
11816
+ describe(trigger, on) {
11817
+ const id = this.tip?.id;
11818
+ if (!id)
11819
+ return;
11820
+ const ids = (trigger.getAttribute('aria-describedby') ?? '').split(/\s+/).filter(x => x && x !== id);
11821
+ const isName = (trigger.getAttribute('aria-label') ?? '').trim() === words(trigger);
11822
+ if (on && !isName)
11823
+ ids.push(id);
11824
+ if (ids.length)
11825
+ trigger.setAttribute('aria-describedby', ids.join(' '));
11826
+ else
11827
+ trigger.removeAttribute('aria-describedby');
11828
+ }
11829
+ listen(target, type, fn, opts) {
11830
+ target.addEventListener(type, fn, opts);
11831
+ this.listeners.push([target, type, fn, opts]);
11832
+ }
11833
+ }
11834
+ /** The runtime already running over `root`, if any. */
11835
+ function tooltipsOf(root) {
11836
+ return root?.[RUNTIME] ?? null;
11837
+ }
11838
+ /** Run tooltips over `root` — once: a root already running is returned as is. */
11839
+ function attachTooltips(root, options = {}) {
11840
+ if (!root)
11841
+ return null;
11842
+ return tooltipsOf(root) ?? new TooltipRuntime(root, options);
11843
+ }
11844
+
10651
11845
  /*
10652
11846
  * Public API Surface of iox-ui
10653
11847
  */
@@ -10656,5 +11850,5 @@ function attachStringForm(form, options = {}) {
10656
11850
  * Generated bundle index. Do not edit.
10657
11851
  */
10658
11852
 
10659
- export { A11yAttribute, A11yTrait, ACCESSIBLE_NAME_TYPES, AOSService, ARIA_CURRENT_PAGE, BACKDROP_TWIN_KEY, BACKDROP_WHOLE_KEY, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CALENDAR_CLOSING_ATTRIBUTE, CALENDAR_DESIGN_ATTRIBUTE, CALENDAR_INSTANT_ATTRIBUTE, CALENDAR_SHEET_QUERY, COMPONENT_STATE_SELECTOR, CONTAINER_HTML_TAGS, CONTEXT_ACTION_TYPES, CURRENT_LINK_TYPES, CalendarClass, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ComponentState, ContainerComponent, ContextStore, DECORATIVE_TYPES, DEFAULT_GALLERY_SCHEDULER, DEFAULT_INTERACTION_EASING, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_PHOSPHOR_WEIGHT, DEFAULT_WIDTH_BY_TYPE, DIALOG_CLOSE_ABOVE_ATTRIBUTE, DIALOG_NODE_TYPE, DatePickerRuntime, DeclarationContext, EASING_KEYWORDS, EASING_TOKENS, ELEMENT_STYLE_TRANSITION_EXTRAS, ENTRANCE_ACTION_TYPES, ENTRANCE_FALLBACK_CSS, ENTRANCE_NOSCRIPT_CSS, ENTRANCE_PRESTATE_ATTRIBUTE, EXIT_ACTION_TYPES, EasingKeyword, EasingToken, FLOATING_GAP, FLOATING_MARGIN, FloatingSide, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, HEADER_SCROLL_HYSTERESIS_PX, HOVER_DEFAULT_TRANSITION_TRAITS, HOVER_MEDIA_QUERY, HOVER_STATES, HeaderRuntime, INTERACTION_ANIMATION_ID, INTERACTION_HIDDEN_ATTRIBUTE, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxDialogComponent, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxSubmitButtonComponent, IoxUiModule, IoxVideoComponent, LAST_EMITTED_STATES, LEGACY_DIRECTION_KEY, LEGACY_HOVER_TRANSITION_TRAITS, LEGACY_SWIPER_CONTROLS, LinkedContainerComponent, MANAGED_A11Y_ATTRIBUTES, MAX_LINES_KEY, NAMED_GROUP_ROLES, NO_PAGE_TRANSITION, ON_STATE_ROLES, OUTER_STYLE_PROPS, PAGE_STYLE_TOKENS, PAGE_TOKEN_FALLBACKS, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PART_PAGINATION_MODIFIER, PHONE_TRANSITION_QUERY, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, PageStyleField, PageStyleSource, PointerKind, REDUCED_MOTION_QUERY, RELATIONAL_STATE_PSEUDO, RevealActionType, SCROLLED_ATTRIBUTE, SCROLLED_STATE_ROLE, SCROLLED_THRESHOLD_PX, SCROLL_EASINGS, SELF_PLAYING_TRIGGERS, SHAKE_DURATION_MS, SHAKE_EASING, SHAKE_KEYFRAMES, SLIDER_FEEL, SLIDER_INPUTS_ATTRIBUTE, SLIDER_INPUT_NAMES, SLIDER_PHONE_BREAKPOINT, SLIDER_RUNWAY_CLASS, SLIDER_STEPS_PROPERTY, SLIDE_CAPTION_ATTRIBUTE, STATE_PSEUDO_CLASS, STRUCTURAL_STATES, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_A11Y_OPTIONS, SWIPER_RESPONSIVE_TRAITS, SWIPER_TRIGGER_EVENTS, ScrollEasing, SectionComponent, SettleMode, StringFormRuntime, SwiperModuleName, TARGETED_STATE_SUFFIX, TEXT_ALIGN_KEY, TEXT_DIRECTION_KEY, TRANSITION_FEELS, TRANSITION_PROPERTY_KEY, TextAlign, TextBlockComponent, VALIDATION_STATES, VALIDATION_STATE_CLASS, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, VisibilityActionType, activeActions, animateAction, applyBindingPipes, applyFeel, attachDatePicker, attachDatePickers, attachStringForm, bootSlider, breakpointDef, breakpointForWidth, breakpointQueryFor, buildInteractionKeyframes, buildPartSelector, buildSwiperBreakpoints, buildTargetedStateSelector, buildTargetedSuffixSelector, buildTransitionTimeline, cascadeChain, clearInputOverride, clearOverride, closeAboveMediaQuery, collectContextSlotKeys, compileDeclarations, compilePageStyle, componentStatesFor, composeVirtualTraits, containerHtmlTag, contextActionEffect, contextSlotKeys, countOverrides, datePickerOf, declaredInputNames, decomposeBackdropFilter, defaultHoverTransitionTraits, defaultMessageResolver, deriveSeoFromContent, describePageStyle, describeSeoFields, dialogCloseAboveMinWidth, domHasSliderParts, durationMs, effectiveActionType, effectiveTransitionId, entrancePreStateStyle, entranceStartsHidden, feelFor, fieldLanguage, finalKeyframe, framesFromLiveValue, gateHoverRule, getSeoValue, hasHoverState, hasOwnPageTransition, hasOwnTransition, headerShouldHide, hidingHeadersIn, initialSlotValues, isActionEnabled, isContextAction, isCubicBezier, isCurrentLink, isEasingToken, isExternalOrFragment, isGradientPaint, isHoverPointer, isHoverState, isInputOverriddenAt, isInteractionEasing, isLastEmittedStateKey, isOverriddenAt, isPressFeedback, isScrolled, isStructuralState, isValidSlotKey, isWholeBackdropSet, keyframePropertyName, keyframeStyleEntries, legacyEnterToTransition, mediaQueryFor, mergeSwiperBreakpoints, migrateLegacyDirection, migrateLegacyTextAlign, motionAllowed, nearestCommonAncestorId, needsAccessibleName, needsGroupName, nodeA11yAttributes, nodeCssId, nodeTraitValue, nodeUsesContext, normalizeAppPath, normalizeGalleryImage, normalizeIconClass, orderStatesForEmit, pageTransitionOf, parentBreakpoint, parseUrlList, partClassName, placeFloating, planPartRule, planResponsiveRules, planScroll, planStateRule, planSwiperOptions, playDualLayerTransition, playInteraction, prefersReducedMotion, renderDefaultDeclarations, resetPageStyleField, resetSeoField, resolveBoundValue, resolveContextValue, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolveInteractionEasing, resolvePageTransition, resolvePath, resolveSeo, resolveStateStyles, resolveStyleProps, resolveToggleVisibility, resolveValueChain, rewriteViewportUnits, safeSetInput, serializeA11yAttributes, setDateFieldValue, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, settleInteraction, settleModeFor, shakeElement, showCalendarDesign, sliderControlElements, sliderInputsFromProps, sliderInputsOf, sliderRunwayHtml, splitOuterProps, splitPressFeedback, stateNamesIn, statePseudoClass, stateSelectorSuffix, stateTransitionProperties, stringFormRuntimeOf, stripSuppressedProps, swiperAutoplayAllowed, swiperLoopOptions, swiperModuleNames, syncNodeA11yAttributes, syncSubmitButtons, targetedStateSelector, transitionPropertyList, viewportTransitionContext, watchHeaders, watchScrolled, withContextSlots, withPhoneDefaults, withStateTransitions, wrapInBreakpointQuery, wrapInMediaQuery };
11853
+ export { A11yAttribute, A11yTrait, ACCESSIBLE_NAME_TYPES, AOSService, ARIA_CURRENT_PAGE, BACKDROP_TWIN_KEY, BACKDROP_WHOLE_KEY, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CALENDAR_CLOSING_ATTRIBUTE, CALENDAR_DESIGN_ATTRIBUTE, CALENDAR_INSTANT_ATTRIBUTE, CALENDAR_SHEET_QUERY, COMPONENT_STATE_SELECTOR, CONTAINER_HTML_TAGS, CONTEXT_ACTION_TYPES, CURRENT_LINK_TYPES, CalendarClass, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ComponentState, ContainerComponent, ContextStore, DECORATIVE_TYPES, DEFAULT_GALLERY_SCHEDULER, DEFAULT_INTERACTION_EASING, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_PHOSPHOR_WEIGHT, DEFAULT_WIDTH_BY_TYPE, DIALOG_CLOSE_ABOVE_ATTRIBUTE, DIALOG_NODE_TYPE, DatePickerRuntime, DeclarationContext, EASING_KEYWORDS, EASING_TOKENS, ELEMENT_STYLE_TRANSITION_EXTRAS, ENTRANCE_ACTION_TYPES, ENTRANCE_FALLBACK_CSS, ENTRANCE_NOSCRIPT_CSS, ENTRANCE_PRESTATE_ATTRIBUTE, EXIT_ACTION_TYPES, EasingKeyword, EasingToken, FLOATING_GAP, FLOATING_LAYER_CLASS, FLOATING_MARGIN, FLOATING_SHEET_QUERY, FloatingAlign, FloatingSide, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, HEADER_SCROLL_HYSTERESIS_PX, HOVER_DEFAULT_TRANSITION_TRAITS, HOVER_MEDIA_QUERY, HOVER_STATES, HeaderRuntime, INTERACTION_ANIMATION_ID, INTERACTION_HIDDEN_ATTRIBUTE, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxDialogComponent, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxPopoverComponent, IoxSubmitButtonComponent, IoxUiModule, IoxVideoComponent, LAST_EMITTED_STATES, LEGACY_DIRECTION_KEY, LEGACY_HOVER_TRANSITION_TRAITS, LEGACY_SWIPER_CONTROLS, LinkedContainerComponent, MANAGED_A11Y_ATTRIBUTES, MAX_LINES_KEY, NAMED_GROUP_ROLES, NO_PAGE_TRANSITION, ON_STATE_ROLES, OUTER_STYLE_PROPS, PAGE_STYLE_TOKENS, PAGE_TOKEN_FALLBACKS, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PART_PAGINATION_MODIFIER, PHONE_TRANSITION_QUERY, PIPE_DESCRIPTORS, PIPE_REGISTRY, POPOVER_SELECTOR, PageScrollProvider, PageStyleField, PageStyleSource, PausableTimer, PauseReason, PointerKind, PopoverAttribute, REDUCED_MOTION_QUERY, RELATIONAL_STATE_PSEUDO, RevealActionType, SCROLLED_ATTRIBUTE, SCROLLED_STATE_ROLE, SCROLLED_THRESHOLD_PX, SCROLL_EASINGS, SELF_PLAYING_TRIGGERS, SHAKE_DURATION_MS, SHAKE_EASING, SHAKE_KEYFRAMES, SLIDER_FEEL, SLIDER_INPUTS_ATTRIBUTE, SLIDER_INPUT_NAMES, SLIDER_PHONE_BREAKPOINT, SLIDER_RUNWAY_CLASS, SLIDER_STEPS_PROPERTY, SLIDE_CAPTION_ATTRIBUTE, STATE_PSEUDO_CLASS, STRUCTURAL_STATES, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_A11Y_OPTIONS, SWIPER_RESPONSIVE_TRAITS, SWIPER_TRIGGER_EVENTS, ScrollEasing, SectionComponent, SettleMode, StringFormRuntime, SwiperModuleName, TARGETED_STATE_SUFFIX, TEXT_ALIGN_KEY, TEXT_DIRECTION_KEY, TOAST_CLOSE_WORDS, TOAST_DESIGN_ATTRIBUTE, TOAST_EDGE_ATTRIBUTE, TOAST_FIRST_LANDS_MS, TOAST_MAX_MS, TOAST_MAX_VISIBLE, TOAST_MIN_MS, TOAST_MS_PER_CHAR, TOOLTIP_ATTRIBUTE, TOOLTIP_FIRST_DELAY_MS, TOOLTIP_HIDE_GRACE_MS, TOOLTIP_INSTANT_ATTRIBUTE, TOOLTIP_PINNED_ATTRIBUTE, TOOLTIP_PLACEMENT_ATTRIBUTE, TOOLTIP_SIDE_ATTRIBUTE, TOOLTIP_TYPES, TOOLTIP_WARM_MS, TRANSITION_FEELS, TRANSITION_PROPERTY_KEY, TextAlign, TextBlockComponent, ToastClass, ToastEdge, TooltipClass, TooltipRuntime, VALIDATION_STATES, VALIDATION_STATE_CLASS, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, VisibilityActionType, activeActions, animateAction, applyBindingPipes, applyFeel, attachDatePicker, attachDatePickers, attachStringForm, attachTooltips, bootSlider, breakpointDef, breakpointForWidth, breakpointQueryFor, buildInteractionKeyframes, buildPartSelector, buildSwiperBreakpoints, buildTargetedStateSelector, buildTargetedSuffixSelector, buildTransitionTimeline, canPopover, cascadeChain, clearInputOverride, clearOverride, clearToasts, closeAboveMediaQuery, closeAllPopovers, closePopover, collectContextSlotKeys, compileDeclarations, compilePageStyle, componentStatesFor, composeVirtualTraits, containerHtmlTag, contextActionEffect, contextSlotKeys, countOverrides, datePickerOf, declaredInputNames, decomposeBackdropFilter, defaultHoverTransitionTraits, defaultMessageResolver, deriveSeoFromContent, describePageStyle, describeSeoFields, dialogCloseAboveMinWidth, domHasSliderParts, durationMs, effectiveActionType, effectiveTransitionId, entrancePreStateStyle, entranceStartsHidden, feelFor, fieldLanguage, finalKeyframe, floatingLayer, floatingScope, formSentWords, formToastsOnSend, framesFromLiveValue, gateHoverRule, getSeoValue, hasHoverState, hasOwnPageTransition, hasOwnTransition, headerShouldHide, hideFromTopLayer, hidingHeadersIn, initialSlotValues, isActionEnabled, isContextAction, isCubicBezier, isCurrentLink, isEasingToken, isExternalOrFragment, isGradientPaint, isHoverPointer, isHoverState, isInputOverriddenAt, isInteractionEasing, isLastEmittedStateKey, isOpenPopover, isOverriddenAt, isPopoverOpen, isPressFeedback, isScrolled, isStructuralState, isValidSlotKey, isWholeBackdropSet, keyframePropertyName, keyframeStyleEntries, layerFrame, legacyEnterToTransition, mediaQueryFor, mergeSwiperBreakpoints, migrateLegacyDirection, migrateLegacyTextAlign, motionAllowed, nearestCommonAncestorId, needsAccessibleName, needsGroupName, nodeA11yAttributes, nodeCssId, nodeTraitValue, nodeUsesContext, normalizeAppPath, normalizeGalleryImage, normalizeIconClass, openPopover, orderStatesForEmit, pageTransitionOf, parentBreakpoint, parseUrlList, partClassName, placeFloating, placePopover, planPartRule, planResponsiveRules, planScroll, planStateRule, planSwiperOptions, playDualLayerTransition, playInteraction, popoverPanelAttributes, popoverSide, prefersReducedMotion, raiseFloatingLayer, raiseFormToast, rectInLayer, renderDefaultDeclarations, resetPageStyleField, resetSeoField, resolveBoundValue, resolveContextValue, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolveInteractionEasing, resolvePageTransition, resolvePath, resolveSeo, resolveStateStyles, resolveStyleProps, resolveToggleVisibility, resolveValueChain, rewriteViewportUnits, safeSetInput, serializeA11yAttributes, setDateFieldValue, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, settleInteraction, settleModeFor, shakeElement, showCalendarDesign, showInTopLayer, showToast, showToastDesign, sliderControlElements, sliderInputsFromProps, sliderInputsOf, sliderRunwayHtml, splitOuterProps, splitPressFeedback, stateNamesIn, statePseudoClass, stateSelectorSuffix, stateTransitionProperties, stringFormRuntimeOf, stripSuppressedProps, swiperAutoplayAllowed, swiperLoopOptions, swiperModuleNames, syncNodeA11yAttributes, syncSubmitButtons, targetedStateSelector, toLayerRect, toastCloseWord, toastDuration, toastEdge, toastsToEvict, togglePopover, tooltipSide, tooltipTiming, tooltipWords, tooltipsOf, transitionPropertyList, viewportTransitionContext, watchHeaders, watchScrolled, withContextSlots, withPhoneDefaults, withStateTransitions, wrapInBreakpointQuery, wrapInMediaQuery };
10660
11854
  //# sourceMappingURL=vectoriox-iox-ui.mjs.map