@vectoriox/iox-ui 5.84.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.
Files changed (47) hide show
  1. package/actions-core/index.d.ts +47 -3
  2. package/composite-core/index.d.ts +93 -15
  3. package/design-core/index.d.ts +2013 -187
  4. package/fesm2022/vectoriox-iox-ui-actions-core.mjs +153 -70
  5. package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
  6. package/fesm2022/vectoriox-iox-ui-composite-core.mjs +307 -39
  7. package/fesm2022/vectoriox-iox-ui-composite-core.mjs.map +1 -1
  8. package/fesm2022/vectoriox-iox-ui-design-core.mjs +2222 -85
  9. package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
  10. package/fesm2022/vectoriox-iox-ui-forms-core.mjs +27 -4
  11. package/fesm2022/vectoriox-iox-ui-forms-core.mjs.map +1 -1
  12. package/fesm2022/vectoriox-iox-ui-generation-core.mjs +250 -12
  13. package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
  14. package/fesm2022/vectoriox-iox-ui-scene-core.mjs +101 -5
  15. package/fesm2022/vectoriox-iox-ui-scene-core.mjs.map +1 -1
  16. package/fesm2022/vectoriox-iox-ui.mjs +1545 -78
  17. package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
  18. package/forms-core/index.d.ts +23 -4
  19. package/generation-core/index.d.ts +13 -0
  20. package/index.d.ts +563 -62
  21. package/package.json +1 -1
  22. package/scene-core/index.d.ts +32 -4
  23. package/src/actions-core/action.model.ts +1 -1
  24. package/src/actions-core/dialog-motion.ts +25 -0
  25. package/src/actions-core/dialog-opener.ts +9 -3
  26. package/src/actions-core/element-actions.ts +25 -1
  27. package/src/actions-core/popover-opener.ts +50 -0
  28. package/src/actions-core/public-api.ts +1 -0
  29. package/src/composite-core/header.ts +25 -0
  30. package/src/composite-core/public-api.ts +1 -0
  31. package/src/composite-core/slider-runtime.ts +82 -0
  32. package/src/composite-core/slider.ts +194 -27
  33. package/src/composite-core/structure.ts +14 -9
  34. package/src/design-core/flavor-spec.generated.ts +2145 -85
  35. package/src/design-core/header-behaviour.ts +76 -0
  36. package/src/design-core/public-api.ts +1 -0
  37. package/src/design-core/theme-color.ts +21 -5
  38. package/src/forms-core/submit-lifecycle.ts +30 -3
  39. package/src/generation-core/baseline-roles.ts +1 -0
  40. package/src/generation-core/compose-site.ts +8 -0
  41. package/src/generation-core/node-baselines.generated.ts +231 -11
  42. package/src/generation-core/patterns.ts +2 -0
  43. package/src/generation-core/site-spec.generated.ts +8 -0
  44. package/src/scene-core/scene.ts +18 -0
  45. package/src/scene-core/scroll-driver.ts +83 -6
  46. package/styles/iox-design.css +431 -1
  47. package/styles/iox-forms.css +29 -4
@@ -6,16 +6,16 @@ import { Subject, fromEvent } from 'rxjs';
6
6
  import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
7
7
  import * as i1 from '@angular/common';
8
8
  import { CommonModule, isPlatformBrowser } from '@angular/common';
9
- import { SPEC_AXES, resolveDesignMotion } from '@vectoriox/iox-ui/design-core';
9
+ import { SPEC_AXES, resolveDesignMotion, HeaderAttribute, HeaderBehaviour, headerAttributes, HeaderTrait } from '@vectoriox/iox-ui/design-core';
10
10
  export * from '@vectoriox/iox-ui/design-core';
11
11
  import Lenis from 'lenis';
12
- import { SLIDER_WORDS, sliderPartElement, SliderRole, sliderWords, attachCarouselA11y, isSliderPart } from '@vectoriox/iox-ui/composite-core';
12
+ import { SLIDER_WORDS, sliderPartElement, SliderRole, sliderWords, attachCarouselA11y, attachSliderBar, isSliderPart, SLIDE_TYPE, SLIDE_CAPTION_TRAIT } from '@vectoriox/iox-ui/composite-core';
13
+ import { SceneAttribute, SCENE_WAKE_EVENT, SCENE_REVEAL_DEFAULT, SceneRevealFrom, revealClipPath, TextRevealBy, playTextReveal } from '@vectoriox/iox-ui/scene-core';
13
14
  import { getByPath } from '@vectoriox/iox-ui/data-core';
14
15
  export * from '@vectoriox/iox-ui/data-core';
15
- 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';
16
17
  import { VideoKind, planVideo, videoRootAttributes, videoInnerHtml, videoWords, VideoAttribute, attachVideoAt, VideoClass } from '@vectoriox/iox-ui/media-core';
17
18
  import * as i1$1 from '@angular/platform-browser';
18
- import { SCENE_REVEAL_DEFAULT, SceneRevealFrom, revealClipPath, TextRevealBy, playTextReveal } from '@vectoriox/iox-ui/scene-core';
19
19
 
20
20
  class ViewPositionDirective {
21
21
  constructor(_elr) {
@@ -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. */
@@ -2140,10 +2156,19 @@ function stateTransitionProperties(stateStyles, responsive, type) {
2140
2156
  }
2141
2157
  return out;
2142
2158
  }
2159
+ /**
2160
+ * What an element style animates beyond the properties its states change (premium M4): a link's underline is DRAWN by
2161
+ * the shared CSS (`iox-design.css`) as a background line that grows from the reading start on hover — its growth is a
2162
+ * `background-size` the node's own transition must name, or it would jump.
2163
+ */
2164
+ const ELEMENT_STYLE_TRANSITION_EXTRAS = {
2165
+ link: ['background-size'],
2166
+ };
2143
2167
  /**
2144
2168
  * `styleProps` as a renderer compiles them: a hovering node with no timing of its own takes the default hover timing,
2145
- * and a node whose transition names no properties gets the ones its states change. An author's own `transitionProperty`
2146
- * (or a raw `transition`) always wins. Pure: the node's stored styles are never touched.
2169
+ * and a node whose transition names no properties gets the ones its states change (and what its element style draws,
2170
+ * `ELEMENT_STYLE_TRANSITION_EXTRAS`). An author's own `transitionProperty` (or a raw `transition`) always wins. Pure:
2171
+ * the node's stored styles are never touched.
2147
2172
  */
2148
2173
  function withStateTransitions(styleProps, stateStyles, elementStyle, responsive, type) {
2149
2174
  let out = styleProps;
@@ -2151,6 +2176,9 @@ function withStateTransitions(styleProps, stateStyles, elementStyle, responsive,
2151
2176
  out = { ...out, ...defaultHoverTransitionTraits(elementStyle) };
2152
2177
  if (!isFilled(out['transitionProperty']) && !isFilled(out['transition'])) {
2153
2178
  const props = stateTransitionProperties(stateStyles, responsive, type);
2179
+ for (const extra of (elementStyle ? ELEMENT_STYLE_TRANSITION_EXTRAS[elementStyle] : null) ?? [])
2180
+ if (props.length && !props.includes(extra))
2181
+ props.push(extra);
2154
2182
  if (props.length)
2155
2183
  out = { ...out, transitionProperty: props.join(', ') };
2156
2184
  }
@@ -2840,6 +2868,16 @@ const LEGACY_SWIPER_CONTROLS = {
2840
2868
  pagination: '.swiper-pagination',
2841
2869
  scrollbar: '.swiper-scrollbar',
2842
2870
  };
2871
+ /**
2872
+ * The feel every live slider gets (premium M4, T3 — Emil 114/121): past either end a drag meets friction, not a wall
2873
+ * (resistance), and a swipe commits only after a few px of travel, so a vertical scroll that drifts never moves a slide.
2874
+ * The easing of the slides is the look's (`slider-ease`, iox-design.css).
2875
+ */
2876
+ const SLIDER_FEEL = { resistanceRatio: 0.65, threshold: 6 };
2877
+ /** At or under this width one picture takes the full width (M1.3) unless the author said otherwise there. */
2878
+ const SLIDER_PHONE_BREAKPOINT = 'mobile';
2879
+ /** Effects that always show one slide (a stack, a turn): a phone keeps their own layout. */
2880
+ const ONE_SLIDE_EFFECTS = new Set(['fade', 'cube', 'flip', 'cards']);
2843
2881
  /** The pagination part's classes — Swiper's own position CSS (`.swiper-pagination-bullets.swiper-pagination-horizontal`)
2844
2882
  * must not pin a PART: its place is its own styles'. The dots keep Swiper's bullet class. */
2845
2883
  const PART_PAGINATION_MODIFIER = 'iox-slider-pagination-';
@@ -2862,6 +2900,24 @@ function perView(v) {
2862
2900
  const n = num$1(v, 1);
2863
2901
  return n > 0 ? n : 'auto';
2864
2902
  }
2903
+ /**
2904
+ * One picture the full width on a phone (M1.3) — the default the M1 prototype proved: a 1.15 "peek" left an empty strip
2905
+ * beside the picture. Added as a phone layer only where the author set no slides-per-view at a phone width; an effect
2906
+ * that shows one slide anyway keeps its own (a "cards" stack stays a centred card, iox-design.css).
2907
+ */
2908
+ function withPhoneDefaults(inputs) {
2909
+ const responsive = inputs.responsive ?? null;
2910
+ if (ONE_SLIDE_EFFECTS.has(String(inputs.effect ?? 'slide')))
2911
+ return responsive;
2912
+ const authored = (bp) => {
2913
+ const layer = responsive?.[bp];
2914
+ return layer?.inputs?.['slidesPerView'] !== undefined;
2915
+ };
2916
+ if (authored('mobile') || authored('small'))
2917
+ return responsive;
2918
+ const phone = responsive?.[SLIDER_PHONE_BREAKPOINT];
2919
+ return { ...(responsive ?? {}), [SLIDER_PHONE_BREAKPOINT]: { ...(phone ?? {}), inputs: { ...(phone?.inputs ?? {}), slidesPerView: 1 } } };
2920
+ }
2865
2921
  function collectResponsiveInputs(inputs) {
2866
2922
  const all = { slidesPerView: inputs.slidesPerView, slidesPerGroup: inputs.slidesPerGroup, spaceBetween: inputs.spaceBetween };
2867
2923
  const out = {};
@@ -2876,7 +2932,10 @@ function planSwiperOptions(inputs, ctx) {
2876
2932
  const edit = !!ctx.edit;
2877
2933
  const hasNav = parts ? !!(parts.prev || parts.next) : on(inputs.enableNavigation);
2878
2934
  const hasDots = parts ? !!parts.pagination : on(inputs.enablePagination);
2879
- const autoplay = !edit && on(inputs.enableAutoplay);
2935
+ // M4: a slider the scroll plays is moved by the page's scroll only (and only while motion is allowed — under reduced
2936
+ // motion it is an ordinary slider).
2937
+ const played = !edit && on(inputs.scrollPlays) && ctx.motion;
2938
+ const autoplay = !edit && !played && on(inputs.enableAutoplay);
2880
2939
  const flags = {
2881
2940
  enableNavigation: hasNav,
2882
2941
  enablePagination: hasDots,
@@ -2915,10 +2974,17 @@ function planSwiperOptions(inputs, ctx) {
2915
2974
  // disabling them at either end (a disabled <button> takes no click).
2916
2975
  options['rewind'] = true;
2917
2976
  }
2977
+ else if (played) {
2978
+ options['allowTouchMove'] = false;
2979
+ options['simulateTouch'] = false;
2980
+ options['grabCursor'] = false;
2981
+ options['loop'] = false;
2982
+ options['rewind'] = false;
2983
+ }
2918
2984
  else {
2919
2985
  options['grabCursor'] = inputs.grabCursor !== false;
2920
2986
  options['slideToClickedSlide'] = on(inputs.slideToClickedSlide);
2921
- Object.assign(options, swiperLoopOptions(inputs.loop));
2987
+ Object.assign(options, swiperLoopOptions(inputs.loop), SLIDER_FEEL);
2922
2988
  if (on(inputs.freeMode))
2923
2989
  options['freeMode'] = true;
2924
2990
  }
@@ -2966,7 +3032,7 @@ function planSwiperOptions(inputs, ctx) {
2966
3032
  containerRole: ctx.named ? 'region' : 'group',
2967
3033
  };
2968
3034
  if (!edit) {
2969
- const derived = buildSwiperBreakpoints(collectResponsiveInputs(inputs), inputs.responsive ?? null);
3035
+ const derived = buildSwiperBreakpoints(collectResponsiveInputs(inputs), withPhoneDefaults(inputs));
2970
3036
  const map = mergeSwiperBreakpoints(derived, inputs.breakpoints ?? '');
2971
3037
  if (map)
2972
3038
  options['breakpoints'] = map;
@@ -3020,8 +3086,46 @@ async function bootSlider(el, inputs, options = {}) {
3020
3086
  const swiper = new Swiper(el, { ...plan.options, modules, on });
3021
3087
  if (!options.edit)
3022
3088
  attachCarouselA11y(swiper, { words });
3089
+ attachSliderBar(swiper);
3090
+ if (inputs.scrollPlays)
3091
+ prepareScrollPlay(el, swiper);
3023
3092
  return swiper;
3024
3093
  }
3094
+ /** The element a slider the scroll plays is held in: its runway, the Scene root the ONE driver drives (premium M4). */
3095
+ const SLIDER_RUNWAY_CLASS = 'iox-slider-runway';
3096
+ /** The runway's steps (slides − 1): how many half-screens of scroll it reserves (iox-design.css). */
3097
+ const SLIDER_STEPS_PROPERTY = '--slider-steps';
3098
+ /**
3099
+ * The runway a slider the scroll plays sits in — ONE answer for the string path (it writes this wrapper, steps known at
3100
+ * render, so the page never shifts) and the component path (the same class and Scene root; its boot writes the steps).
3101
+ * Null when the scroll does not play it.
3102
+ */
3103
+ function sliderRunwayHtml(inputs, slideCount) {
3104
+ if (!(inputs?.scrollPlays === true || inputs?.scrollPlays === 'true'))
3105
+ return null;
3106
+ const steps = Math.max(0, Math.floor(slideCount) - 1);
3107
+ return { open: `<div class="${SLIDER_RUNWAY_CLASS}" ${SceneAttribute.Root}="" style="${SLIDER_STEPS_PROPERTY}: ${steps}">`, close: '</div>' };
3108
+ }
3109
+ /**
3110
+ * Ready a slider the scroll plays: its runway learns how tall it is (it holds the slider mid-screen — iox-design.css) and
3111
+ * how many steps it has, its stage is marked for the scene driver, and the driver is woken — the Swiper it plays has
3112
+ * just arrived (an event, never a DOM scroll). Measured on resize, never per frame.
3113
+ */
3114
+ function prepareScrollPlay(el, swiper) {
3115
+ const runway = el.closest(`.${SLIDER_RUNWAY_CLASS}`);
3116
+ // The stage is what the runway holds: the `.swiper` itself, or the canvas's shell around it.
3117
+ const stage = runway ? Array.from(runway.children).find(c => c.contains(el)) : undefined;
3118
+ if (!runway || !stage)
3119
+ return;
3120
+ stage.setAttribute(SceneAttribute.Stage, '');
3121
+ runway.style.setProperty(SLIDER_STEPS_PROPERTY, String(Math.max(0, (swiper.slides?.length ?? 1) - 1)));
3122
+ const measure = () => runway.style.setProperty('--slider-h', `${stage.offsetHeight}px`);
3123
+ measure();
3124
+ const Observer = stage.ownerDocument?.defaultView?.ResizeObserver;
3125
+ if (Observer)
3126
+ new Observer(measure).observe(stage);
3127
+ el.dispatchEvent(new CustomEvent(SCENE_WAKE_EVENT, { bubbles: true }));
3128
+ }
3025
3129
  /** The inputs a string-path slider carries (`data-iox-slider`), or null. */
3026
3130
  function sliderInputsOf(el) {
3027
3131
  const raw = el.getAttribute(SLIDER_INPUTS_ATTRIBUTE);
@@ -3041,6 +3145,7 @@ const SLIDER_INPUT_NAMES = [
3041
3145
  'autoHeight', 'slideToClickedSlide', 'freeMode', 'loop', 'enableNavigation', 'enablePagination', 'enableScrollbar', 'enableAutoplay',
3042
3146
  'enableKeyboard', 'enableMousewheel', 'enableZoom', 'enableGrid', 'enableParallax', 'autoplayDelay', 'autoplayPauseOnMouseEnter',
3043
3147
  'autoplayDisableOnInteraction', 'paginationType', 'paginationClickable', 'paginationDynamicBullets', 'gridRows', 'breakpoints',
3148
+ 'scrollPlays',
3044
3149
  ];
3045
3150
  function sliderInputsFromProps(props, responsive) {
3046
3151
  const out = {};
@@ -3057,7 +3162,8 @@ function sliderInputsFromProps(props, responsive) {
3057
3162
  * shared boot). The slides render into `.swiper-wrapper` (`childrenHost` — the `.swiper-slide` direct-child invariant,
3058
3163
  * slider.md); the control PARTS (CC3: Previous, Next, Pause, Pagination — `childHostFor`) render beside it, inside
3059
3164
  * `.swiper`: those before the first slide before the wrapper, the rest after it. A saved slider with no parts keeps
3060
- * Swiper-drawn controls (the legacy switches). Swiper is booted by the ONE shared `bootSlider`.
3165
+ * Swiper-drawn controls (the legacy switches). Swiper is booted by the ONE shared `bootSlider`. A slider the scroll plays
3166
+ * (premium M4) sits in its runway — the string path writes the same `iox-slider-runway` wrapper.
3061
3167
  */
3062
3168
  class ClientSliderContainerComponent {
3063
3169
  constructor(platformId) {
@@ -3103,6 +3209,9 @@ class ClientSliderContainerComponent {
3103
3209
  this.responsive = null;
3104
3210
  /** The slider's controls are parts (set by the renderer from the node): Swiper draws none of its own. */
3105
3211
  this.controlParts = false;
3212
+ /** Premium M4: the scroll plays it — a runway (a Scene root the ONE driver drives) holds it mid-screen; otherwise the
3213
+ * wrapper is `display: contents` and the slider lays out exactly as before. */
3214
+ this.scrollPlays = false;
3106
3215
  this.swiper = null;
3107
3216
  this.slidesSeen = false;
3108
3217
  this.instanceRegistry = inject(ComponentInstanceRegistryService);
@@ -3151,11 +3260,12 @@ class ClientSliderContainerComponent {
3151
3260
  enableZoom: this.enableZoom, enableGrid: this.enableGrid, enableParallax: this.enableParallax, autoplayDelay: this.autoplayDelay,
3152
3261
  autoplayPauseOnMouseEnter: this.autoplayPauseOnMouseEnter, autoplayDisableOnInteraction: this.autoplayDisableOnInteraction,
3153
3262
  paginationType: this.paginationType, paginationClickable: this.paginationClickable, paginationDynamicBullets: this.paginationDynamicBullets,
3154
- gridRows: this.gridRows, breakpoints: this.breakpoints, responsive: this.responsive,
3263
+ gridRows: this.gridRows, breakpoints: this.breakpoints, responsive: this.responsive, scrollPlays: this.scrollPlays,
3155
3264
  };
3156
3265
  }
3157
3266
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ClientSliderContainerComponent, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Component }); }
3158
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: ClientSliderContainerComponent, isStandalone: false, selector: "iox-slider-container", inputs: { nodeId: "nodeId", dir: "dir", direction: "direction", loop: "loop", slidesPerView: "slidesPerView", slidesPerGroup: "slidesPerGroup", spaceBetween: "spaceBetween", speed: "speed", effect: "effect", grabCursor: "grabCursor", centeredSlides: "centeredSlides", initialSlide: "initialSlide", autoHeight: "autoHeight", slideToClickedSlide: "slideToClickedSlide", freeMode: "freeMode", enableNavigation: "enableNavigation", enablePagination: "enablePagination", enableScrollbar: "enableScrollbar", enableAutoplay: "enableAutoplay", enableKeyboard: "enableKeyboard", enableMousewheel: "enableMousewheel", enableZoom: "enableZoom", enableGrid: "enableGrid", enableParallax: "enableParallax", autoplayDelay: "autoplayDelay", autoplayPauseOnMouseEnter: "autoplayPauseOnMouseEnter", autoplayDisableOnInteraction: "autoplayDisableOnInteraction", paginationType: "paginationType", paginationClickable: "paginationClickable", paginationDynamicBullets: "paginationDynamicBullets", navigationColor: "navigationColor", paginationColor: "paginationColor", gridRows: "gridRows", breakpoints: "breakpoints", responsive: "responsive", controlParts: "controlParts" }, viewQueries: [{ propertyName: "childrenHost", first: true, predicate: ["childrenHost"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "leadHost", first: true, predicate: ["leadHost"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "trailHost", first: true, predicate: ["trailHost"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "swiperEl", first: true, predicate: ["swiperEl"], descendants: true, static: true }], ngImport: i0, template: `
3267
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: ClientSliderContainerComponent, isStandalone: false, selector: "iox-slider-container", inputs: { nodeId: "nodeId", dir: "dir", direction: "direction", loop: "loop", slidesPerView: "slidesPerView", slidesPerGroup: "slidesPerGroup", spaceBetween: "spaceBetween", speed: "speed", effect: "effect", grabCursor: "grabCursor", centeredSlides: "centeredSlides", initialSlide: "initialSlide", autoHeight: "autoHeight", slideToClickedSlide: "slideToClickedSlide", freeMode: "freeMode", enableNavigation: "enableNavigation", enablePagination: "enablePagination", enableScrollbar: "enableScrollbar", enableAutoplay: "enableAutoplay", enableKeyboard: "enableKeyboard", enableMousewheel: "enableMousewheel", enableZoom: "enableZoom", enableGrid: "enableGrid", enableParallax: "enableParallax", autoplayDelay: "autoplayDelay", autoplayPauseOnMouseEnter: "autoplayPauseOnMouseEnter", autoplayDisableOnInteraction: "autoplayDisableOnInteraction", paginationType: "paginationType", paginationClickable: "paginationClickable", paginationDynamicBullets: "paginationDynamicBullets", navigationColor: "navigationColor", paginationColor: "paginationColor", gridRows: "gridRows", breakpoints: "breakpoints", responsive: "responsive", controlParts: "controlParts", scrollPlays: "scrollPlays" }, viewQueries: [{ propertyName: "childrenHost", first: true, predicate: ["childrenHost"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "leadHost", first: true, predicate: ["leadHost"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "trailHost", first: true, predicate: ["trailHost"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "swiperEl", first: true, predicate: ["swiperEl"], descendants: true, static: true }], ngImport: i0, template: `
3268
+ <div [class.iox-slider-runway]="scrollPlays" [attr.data-iox-scene]="scrollPlays ? '' : null" [style.display]="scrollPlays ? null : 'contents'">
3159
3269
  <div class="swiper" [class]="'swiper iox-node-' + nodeId" [attr.dir]="dir" [style.direction]="dir"
3160
3270
  [style.--swiper-navigation-color]="legacyControls ? (navigationColor || null) : null"
3161
3271
  [style.--swiper-pagination-color]="legacyControls ? (paginationColor || null) : null" #swiperEl>
@@ -3169,11 +3279,13 @@ class ClientSliderContainerComponent {
3169
3279
  <div *ngIf="legacyControls && enablePagination" class="swiper-pagination"></div>
3170
3280
  <div *ngIf="enableScrollbar" class="swiper-scrollbar"></div>
3171
3281
  </div>
3282
+ </div>
3172
3283
  `, isInline: true, styles: [".swiper{width:100%;height:100%;box-sizing:border-box}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
3173
3284
  }
3174
3285
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ClientSliderContainerComponent, decorators: [{
3175
3286
  type: Component,
3176
3287
  args: [{ selector: 'iox-slider-container', template: `
3288
+ <div [class.iox-slider-runway]="scrollPlays" [attr.data-iox-scene]="scrollPlays ? '' : null" [style.display]="scrollPlays ? null : 'contents'">
3177
3289
  <div class="swiper" [class]="'swiper iox-node-' + nodeId" [attr.dir]="dir" [style.direction]="dir"
3178
3290
  [style.--swiper-navigation-color]="legacyControls ? (navigationColor || null) : null"
3179
3291
  [style.--swiper-pagination-color]="legacyControls ? (paginationColor || null) : null" #swiperEl>
@@ -3187,6 +3299,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
3187
3299
  <div *ngIf="legacyControls && enablePagination" class="swiper-pagination"></div>
3188
3300
  <div *ngIf="enableScrollbar" class="swiper-scrollbar"></div>
3189
3301
  </div>
3302
+ </div>
3190
3303
  `, standalone: false, styles: [".swiper{width:100%;height:100%;box-sizing:border-box}\n"] }]
3191
3304
  }], ctorParameters: () => [{ type: undefined, decorators: [{
3192
3305
  type: Inject,
@@ -3263,6 +3376,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
3263
3376
  type: Input
3264
3377
  }], controlParts: [{
3265
3378
  type: Input
3379
+ }], scrollPlays: [{
3380
+ type: Input
3266
3381
  }], childrenHost: [{
3267
3382
  type: ViewChild,
3268
3383
  args: ['childrenHost', { read: ViewContainerRef, static: true }]
@@ -4908,10 +5023,33 @@ var FormRenderState;
4908
5023
  /** The attribute every renderer writes a form's state on (the canvas switcher, both live paths). */
4909
5024
  const FORM_STATE_ATTRIBUTE = 'data-iox-state';
4910
5025
  /**
4911
- * The server's answer → what the visitor sees. Only a confirmed success is Success; per-field verdicts
4912
- * land on the fields; anything else (a dropped network included) is the designed Error block.
4913
- */
4914
- 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
+ }
4915
5053
  if (response?.ok)
4916
5054
  return { state: FormRenderState.Success, reset: true, ...(response.redirect ? { redirect: response.redirect } : {}) };
4917
5055
  if (response?.errors && Object.keys(response.errors).length)
@@ -5413,6 +5551,562 @@ function dateHtml(p, options, describedBy) {
5413
5551
  * disagree. See iox-ai-guidance `architecture/builder/forms-and-inputs-plan.md` §5.
5414
5552
  */
5415
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
+
5416
6110
  const IOX_FORM_MESSAGES = new InjectionToken('IOX_FORM_MESSAGES', {
5417
6111
  providedIn: 'root',
5418
6112
  factory: () => defaultMessageResolver,
@@ -5627,6 +6321,7 @@ class IoxFormComponent {
5627
6321
  syncSubmitButtons(this.host?.nativeElement, state);
5628
6322
  }
5629
6323
  get renderState() { return this._renderState; }
6324
+ get keepsFields() { return formAfterSend(this.afterSend) === FormAfterSend.Toast; }
5630
6325
  /**
5631
6326
  * Constructor injection rather than `inject()`, for the same reason as Field: this library's
5632
6327
  * specs construct components directly. `runtime` is provided by THIS component (see
@@ -5644,6 +6339,8 @@ class IoxFormComponent {
5644
6339
  this.name = '';
5645
6340
  this._renderState = FormRenderState.Idle;
5646
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;
5647
6344
  /**
5648
6345
  * Move focus to the first field that failed, instead of leaving the visitor to hunt for red
5649
6346
  * text. On by default — the single most valuable thing a failed submit can do.
@@ -5734,7 +6431,7 @@ class IoxFormComponent {
5734
6431
  /** Turn the server's answer into what the visitor sees — the shared `submitOutcome`, which the string runtime
5735
6432
  * applies too. Public so a host-owned transport can reuse it. */
5736
6433
  applyResponse(response) {
5737
- const outcome = submitOutcome(response);
6434
+ const outcome = submitOutcome(response, this.afterSend);
5738
6435
  this.renderState = outcome.state;
5739
6436
  if (outcome.errors) {
5740
6437
  this.runtime.applyErrors(outcome.errors);
@@ -5751,6 +6448,8 @@ class IoxFormComponent {
5751
6448
  // the success message, and cannot resubmit them by pressing enter.
5752
6449
  this.runtime.reset();
5753
6450
  this.resetControls();
6451
+ if (outcome.toast)
6452
+ raiseFormToast(this.host.nativeElement);
5754
6453
  if (outcome.redirect && this.navigate)
5755
6454
  this.navigate(outcome.redirect);
5756
6455
  }
@@ -5797,7 +6496,7 @@ class IoxFormComponent {
5797
6496
  }
5798
6497
  }
5799
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 }); }
5800
- 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: `
5801
6500
  <div class="iox-form__hp" aria-hidden="true">
5802
6501
  <input type="text" [attr.name]="honeypotField" tabindex="-1" autocomplete="off" />
5803
6502
  </div>
@@ -5833,6 +6532,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
5833
6532
  '[attr.data-iox-form]': 'formId || null',
5834
6533
  '[attr.data-iox-state]': 'renderState',
5835
6534
  '[attr.aria-busy]': "renderState === 'submitting' ? 'true' : null",
6535
+ '[attr.data-iox-after-send]': "keepsFields ? 'toast' : null",
5836
6536
  '(submit)': 'onSubmit($event)',
5837
6537
  }, providers: [FormRuntimeService], standalone: false, styles: [":host{box-sizing:border-box}\n"] }]
5838
6538
  }], ctorParameters: () => [{ type: FormRuntimeService }, { type: i0.ElementRef }, { type: undefined, decorators: [{
@@ -5855,6 +6555,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
5855
6555
  type: Input
5856
6556
  }], feedback: [{
5857
6557
  type: Input
6558
+ }], afterSend: [{
6559
+ type: Input
5858
6560
  }], focusFirstProblem: [{
5859
6561
  type: Input
5860
6562
  }], announceFailure: [{
@@ -5868,47 +6570,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
5868
6570
  args: ['childrenHost', { read: ViewContainerRef, static: true }]
5869
6571
  }] } });
5870
6572
 
5871
- /**
5872
- * placeFloating — where a panel anchored to a control goes (premium components N8, reused by M5's tooltip and popover).
5873
- *
5874
- * ONE pure decision for every floating panel: under its anchor, aligned to the anchor's reading start (the left edge in
5875
- * LTR, the right in RTL); above it when there is no room below and more above; held inside the viewport with a margin;
5876
- * and the transform-origin AT the anchor, so the panel grows from the control that opened it (Emil 47) — even when the
5877
- * clamp moved it sideways.
5878
- *
5879
- * Lives in the main entry: nothing server-side places a panel, and the date picker (now) and the floating family (M5)
5880
- * are DOM runtimes of the main entry. Pure — no DOM read here: the caller measures, this decides, the caller writes.
5881
- */
5882
- var FloatingSide;
5883
- (function (FloatingSide) {
5884
- FloatingSide["Below"] = "below";
5885
- FloatingSide["Above"] = "above";
5886
- })(FloatingSide || (FloatingSide = {}));
5887
- /** Emil's popover spacing: close enough to read as the control's, clear of its focus ring. */
5888
- const FLOATING_GAP = 8;
5889
- /** The viewport margin — a panel never touches the screen's edge. */
5890
- const FLOATING_MARGIN = 12;
5891
- const clamp = (value, min, max) => Math.min(Math.max(value, min), Math.max(min, max));
5892
- /** Where `panel` goes beside `anchor` inside `viewport`. */
5893
- function placeFloating(anchor, panel, viewport, options = {}) {
5894
- const gap = options.gap ?? FLOATING_GAP;
5895
- const margin = options.margin ?? FLOATING_MARGIN;
5896
- const anchorBottom = anchor.top + anchor.height;
5897
- const roomBelow = viewport.height - anchorBottom - gap - margin;
5898
- const roomAbove = anchor.top - gap - margin;
5899
- // Below is the default; above only when below is too short AND above is roomier (a field near the screen's foot).
5900
- const side = roomBelow >= panel.height || roomBelow >= roomAbove ? FloatingSide.Below : FloatingSide.Above;
5901
- const wantTop = side === FloatingSide.Below ? anchorBottom + gap : anchor.top - gap - panel.height;
5902
- const top = clamp(wantTop, margin, viewport.height - margin - panel.height);
5903
- const start = options.rtl ? anchor.left + anchor.width : anchor.left;
5904
- const wantLeft = options.rtl ? start - panel.width : start;
5905
- const left = clamp(wantLeft, margin, viewport.width - margin - panel.width);
5906
- // The origin is the anchor's start corner, in the panel's own coordinates, kept on the panel's box.
5907
- const originX = Math.round(clamp(start - left, 0, panel.width));
5908
- const originY = side === FloatingSide.Below ? 0 : Math.round(panel.height);
5909
- return { top: Math.round(top), left: Math.round(left), side, origin: `${originX}px ${originY}px` };
5910
- }
5911
-
5912
6573
  var CalendarClass;
5913
6574
  (function (CalendarClass) {
5914
6575
  CalendarClass["Panel"] = "iox-cal";
@@ -5929,9 +6590,9 @@ const CALENDAR_CLOSING_ATTRIBUTE = 'data-iox-closing';
5929
6590
  const CALENDAR_INSTANT_ATTRIBUTE = 'data-iox-instant';
5930
6591
  /** On the panel the builder canvas draws for its design-time Open state (in the flow, inert). */
5931
6592
  const CALENDAR_DESIGN_ATTRIBUTE = 'data-iox-cal-design';
5932
- /** A phone gets the sheet (the drawer curve, the scrim) — the same width the shared CSS keys on. */
5933
- const CALENDAR_SHEET_QUERY = '(max-width: 767px)';
5934
- 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';
5935
6596
  const ISO_ATTRIBUTE = 'data-iox-iso';
5936
6597
  const CHEVRON_BACK = 'M15 18l-6-6 6-6';
5937
6598
  const CHEVRON_AHEAD = 'M9 18l6-6-6-6';
@@ -6092,7 +6753,7 @@ class DatePickerRuntime {
6092
6753
  const form = field.closest('form');
6093
6754
  if (form && !options.onPick)
6094
6755
  this.listen(form, 'reset', () => setDateFieldValue(field, '', { lang: this.lang() }));
6095
- field[RUNTIME] = this;
6756
+ field[RUNTIME$1] = this;
6096
6757
  }
6097
6758
  get isOpen() { return !!this.panel; }
6098
6759
  value() { return partsOf(this.field).input?.value ?? ''; }
@@ -6101,7 +6762,7 @@ class DatePickerRuntime {
6101
6762
  for (const [target, type, fn] of this.listeners)
6102
6763
  target.removeEventListener(type, fn);
6103
6764
  this.listeners.length = 0;
6104
- delete this.field[RUNTIME];
6765
+ delete this.field[RUNTIME$1];
6105
6766
  }
6106
6767
  /** Open the calendar; `instant` (opened from the keyboard) skips the entrance and, later, the exit. */
6107
6768
  open(instant = false) {
@@ -6257,7 +6918,7 @@ class DatePickerRuntime {
6257
6918
  }
6258
6919
  /** The runtime already running a field, if any. */
6259
6920
  function datePickerOf(field) {
6260
- return field?.[RUNTIME] ?? null;
6921
+ return field?.[RUNTIME$1] ?? null;
6261
6922
  }
6262
6923
  /** Run a date field — once: a field already running is returned as is. Null when `field` holds no date button. */
6263
6924
  function attachDatePicker(field, options = {}) {
@@ -7170,6 +7831,257 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
7170
7831
  args: ['childrenHost', { read: ViewContainerRef, static: true }]
7171
7832
  }] } });
7172
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
+
7173
8085
  const escapeHtml = (v) => v.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
7174
8086
  /**
7175
8087
  * `iox-video` — the Video's ONE component (CC3, board 5), like the Gallery's: the builder canvas, the admin's Preview
@@ -7338,6 +8250,7 @@ const COMPONENTS = [
7338
8250
  IoxFormSuccessComponent,
7339
8251
  IoxFormErrorComponent,
7340
8252
  IoxDialogComponent,
8253
+ IoxPopoverComponent,
7341
8254
  ];
7342
8255
  class IoxBuilderComponentsModule {
7343
8256
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxBuilderComponentsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
@@ -7366,7 +8279,8 @@ class IoxBuilderComponentsModule {
7366
8279
  IoxSubmitButtonComponent,
7367
8280
  IoxFormSuccessComponent,
7368
8281
  IoxFormErrorComponent,
7369
- IoxDialogComponent], imports: [CommonModule], exports: [BuilderImageComponent,
8282
+ IoxDialogComponent,
8283
+ IoxPopoverComponent], imports: [CommonModule], exports: [BuilderImageComponent,
7370
8284
  BuilderSpacerComponent,
7371
8285
  CardComponent,
7372
8286
  BuilderDividerComponent,
@@ -7391,7 +8305,8 @@ class IoxBuilderComponentsModule {
7391
8305
  IoxSubmitButtonComponent,
7392
8306
  IoxFormSuccessComponent,
7393
8307
  IoxFormErrorComponent,
7394
- IoxDialogComponent] }); }
8308
+ IoxDialogComponent,
8309
+ IoxPopoverComponent] }); }
7395
8310
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxBuilderComponentsModule, imports: [CommonModule] }); }
7396
8311
  }
7397
8312
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxBuilderComponentsModule, decorators: [{
@@ -7449,6 +8364,8 @@ class IoxComponentRegistryService {
7449
8364
  // Dialog (DS2) — the live <dialog> twin, so a page holding one no longer falls back to
7450
8365
  // the string renderer.
7451
8366
  ['Dialog', IoxDialogComponent],
8367
+ // Popover (premium M5) — the anchored, non-modal panel; its runtime is the shared popover.runtime.
8368
+ ['Popover', IoxPopoverComponent],
7452
8369
  // Video (CC3) — one component for the canvas (poster only), Preview and the live site.
7453
8370
  ['Video', IoxVideoComponent],
7454
8371
  ]);
@@ -7771,6 +8688,170 @@ function isCurrentLink(href, currentPath, options = {}) {
7771
8688
  return !!options.prefix && target !== '' && here.startsWith(`${target}/`);
7772
8689
  }
7773
8690
 
8691
+ /**
8692
+ * A header that HIDES (premium components T2, M4): it steps aside while the page scrolls down and comes back the moment
8693
+ * it scrolls up — ONE runtime for the live site (the window's scroll) and the builder canvas (its Lenis scroll, handed in
8694
+ * through `update`). It never moves while the visitor is inside it: keyboard focus in the header, or its own drawer
8695
+ * open, keeps it in place, and it is always there at the top of the page.
8696
+ *
8697
+ * Motion (Emil 30/41/52/59/192): `translate` only, from the live value (a run interrupted by a change of direction turns
8698
+ * back from where it is — never stacked); the exit is faster than the return (dur-fast out, dur-ui back, on the look's
8699
+ * standard easing — the tokens collapse with the look's motion); under reduced motion it fades in place (opacity, no
8700
+ * movement — gentler, not zero), and is not clickable while faded. The return RELEASES (no inline frame is left), the
8701
+ * hidden frame is held, as every exit in the product (M2's settle rule).
8702
+ *
8703
+ * A header that only STAYS needs no runtime: the shared CSS keeps it at the top (`iox-design.css`).
8704
+ */
8705
+ /** Page px the scroll must travel in one direction before the header answers — past a trackpad's jitter. */
8706
+ const HEADER_SCROLL_HYSTERESIS_PX = 6;
8707
+ const FALLBACK_HIDE_MS = 150;
8708
+ const FALLBACK_SHOW_MS = 200;
8709
+ const FALLBACK_EASING = 'ease-out';
8710
+ const HIDE_DURATION_TOKEN = '--iox-dur-fast';
8711
+ const SHOW_DURATION_TOKEN = '--iox-dur-ui';
8712
+ const EASING_TOKEN = '--iox-ease-standard';
8713
+ const SUPPORTED_EASING = /^(linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier\(.+\)|linear\(.+\)|steps\(.+\))$/;
8714
+ const OPEN_DIALOG = 'dialog[open]';
8715
+ /** `120ms` / `0.2s` → milliseconds; anything else → null. */
8716
+ function durationMs(value) {
8717
+ const m = /^(-?\d*\.?\d+)(ms|s)$/.exec(String(value ?? '').trim());
8718
+ if (!m)
8719
+ return null;
8720
+ const n = Number(m[1]) * (m[2] === 's' ? 1000 : 1);
8721
+ return Number.isFinite(n) && n >= 0 ? n : null;
8722
+ }
8723
+ /**
8724
+ * Whether a hiding header should step aside now: the page moved DOWN past the header's own height, the header is not
8725
+ * held (focus inside, its drawer open). Pure: the direction and the offsets come from the caller.
8726
+ */
8727
+ function headerShouldHide(offset, delta, headerHeight, held) {
8728
+ if (held || !(delta > 0))
8729
+ return false;
8730
+ return offset > Math.max(0, headerHeight);
8731
+ }
8732
+ class HeaderRuntime {
8733
+ constructor(header, options = {}) {
8734
+ this.header = header;
8735
+ this.options = options;
8736
+ this.lastOffset = 0;
8737
+ this.hidden = false;
8738
+ this.height = 0;
8739
+ this.anim = null;
8740
+ this.onFocusIn = () => this.setHidden(false);
8741
+ const view = header.ownerDocument?.defaultView;
8742
+ this.height = header.offsetHeight;
8743
+ this.resize = view?.ResizeObserver ? new view.ResizeObserver(() => { this.height = this.header.offsetHeight; }) : null;
8744
+ this.resize?.observe(header);
8745
+ header.addEventListener('focusin', this.onFocusIn);
8746
+ }
8747
+ /** True while the header has stepped aside. */
8748
+ get isHidden() {
8749
+ return this.hidden;
8750
+ }
8751
+ /** The page now stands at `offset` page px (layout px — the canvas divides out its zoom). */
8752
+ update(offset) {
8753
+ if (!Number.isFinite(offset))
8754
+ return;
8755
+ const delta = offset - this.lastOffset;
8756
+ if (offset <= this.height) {
8757
+ // Its own height from the top: the header is where it belongs, whichever way the page moves.
8758
+ this.lastOffset = offset;
8759
+ this.setHidden(false);
8760
+ return;
8761
+ }
8762
+ if (Math.abs(delta) < HEADER_SCROLL_HYSTERESIS_PX)
8763
+ return;
8764
+ this.lastOffset = offset;
8765
+ this.setHidden(headerShouldHide(offset, delta, this.height, this.held()));
8766
+ }
8767
+ /** Back in place now, with no motion (a page change, the runtime going away). */
8768
+ reset() {
8769
+ this.anim?.cancel();
8770
+ this.anim = null;
8771
+ this.hidden = false;
8772
+ this.header.removeAttribute(HeaderAttribute.Hidden);
8773
+ }
8774
+ destroy() {
8775
+ this.reset();
8776
+ this.resize?.disconnect();
8777
+ this.header.removeEventListener('focusin', this.onFocusIn);
8778
+ }
8779
+ held() {
8780
+ return this.header.matches(':focus-within') || !!this.header.querySelector(OPEN_DIALOG);
8781
+ }
8782
+ setHidden(hidden) {
8783
+ if (hidden === this.hidden)
8784
+ return;
8785
+ this.hidden = hidden;
8786
+ this.header.toggleAttribute(HeaderAttribute.Hidden, hidden);
8787
+ const reduce = prefersReducedMotion(this.options.host ?? this.header.ownerDocument?.defaultView ?? null);
8788
+ const prop = reduce ? 'opacity' : 'translate';
8789
+ const shown = reduce ? '1' : '0px 0px';
8790
+ const away = reduce ? '0' : '0px -100%';
8791
+ const style = this.header.ownerDocument?.defaultView?.getComputedStyle(this.header);
8792
+ // From the live value when a run is in flight (a change of direction mid-way turns back from where it is).
8793
+ const from = this.anim ? (style?.getPropertyValue(prop) || (hidden ? shown : away)) : (hidden ? shown : away);
8794
+ this.anim?.cancel();
8795
+ this.anim = null;
8796
+ if (typeof this.header.animate !== 'function')
8797
+ return;
8798
+ const duration = durationMs(style?.getPropertyValue(hidden ? HIDE_DURATION_TOKEN : SHOW_DURATION_TOKEN)) ?? (hidden ? FALLBACK_HIDE_MS : FALLBACK_SHOW_MS);
8799
+ const easing = style?.getPropertyValue(EASING_TOKEN)?.trim() ?? '';
8800
+ const anim = this.header.animate([{ [prop]: from }, { [prop]: hidden ? away : shown }], {
8801
+ duration, easing: SUPPORTED_EASING.test(easing) ? easing : FALLBACK_EASING, fill: hidden ? 'forwards' : 'none',
8802
+ });
8803
+ this.anim = anim;
8804
+ if (!hidden)
8805
+ anim.finished?.then(() => { if (this.anim === anim) {
8806
+ anim.cancel();
8807
+ this.anim = null;
8808
+ } }).catch(() => { });
8809
+ }
8810
+ }
8811
+ const HIDING_SELECTOR = `[${HeaderAttribute.Behaviour}="${HeaderBehaviour.Hides}"]`;
8812
+ /** The headers that hide while the page scrolls down: in `root`, or among `elements` (each, and what it holds — what a
8813
+ * runtime instance owns: the page's nodes or the globals'). */
8814
+ function hidingHeadersIn(root) {
8815
+ if (typeof root.querySelectorAll === 'function')
8816
+ return Array.from(root.querySelectorAll(HIDING_SELECTOR));
8817
+ const out = new Set();
8818
+ for (const el of root) {
8819
+ if (el.matches?.(HIDING_SELECTOR))
8820
+ out.add(el);
8821
+ el.querySelectorAll?.(HIDING_SELECTOR).forEach(h => out.add(h));
8822
+ }
8823
+ return [...out];
8824
+ }
8825
+ /**
8826
+ * The live site's door: every hiding header in `root` (or among `elements`) follows `source` (the window), through ONE
8827
+ * passive listener read once per frame. Returns the stop.
8828
+ */
8829
+ function watchHeaders(source, root, options = {}) {
8830
+ const runtimes = hidingHeadersIn(root).map(el => new HeaderRuntime(el, options));
8831
+ if (!runtimes.length)
8832
+ return () => { };
8833
+ let frame = 0;
8834
+ const raf = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : (fn) => setTimeout(() => fn(0), 16);
8835
+ const caf = typeof cancelAnimationFrame === 'function' ? cancelAnimationFrame : (id) => clearTimeout(id);
8836
+ const sync = () => {
8837
+ frame = 0;
8838
+ const offset = source.scrollY ?? source.scrollTop ?? 0;
8839
+ for (const r of runtimes)
8840
+ r.update(offset);
8841
+ };
8842
+ const onScroll = () => { if (!frame)
8843
+ frame = raf(sync); };
8844
+ source.addEventListener('scroll', onScroll, { passive: true });
8845
+ sync();
8846
+ return () => {
8847
+ source.removeEventListener('scroll', onScroll);
8848
+ if (frame)
8849
+ caf(frame);
8850
+ for (const r of runtimes)
8851
+ r.destroy();
8852
+ };
8853
+ }
8854
+
7774
8855
  /**
7775
8856
  * The accessibility attributes a node renders (CC1: N2 current page, N3 dialog openers, N4 a drawer
7776
8857
  * that closes when the window widens, N6 accessible names). ONE function decides them and every
@@ -7782,13 +8863,18 @@ function isCurrentLink(href, currentPath, options = {}) {
7782
8863
  const DECORATIVE_TYPES = ['Icon'];
7783
8864
  const DIALOG_NODE_TYPE = 'Dialog';
7784
8865
  /** Named for assistive technology, but never an icon-only CONTROL the audit should flag: a Dialog, a carousel. */
7785
- 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'];
7786
8870
  const SLIDER_PAUSE_ROLE = 'slider.pause';
7787
8871
  /** A Segmented control's root (CC4b) is a radio group that must be named (board 10: an empty name is an audit
7788
8872
  * finding). The main entry does not import composite-core, so the role is spelled here — a11y spec pins it. */
7789
8873
  const NAMED_GROUP_ROLES = ['segmented'];
7790
8874
  /** Carries the min-width (px) at which an open dialog closes itself; the engine listens on it. */
7791
8875
  const DIALOG_CLOSE_ABOVE_ATTRIBUTE = 'data-iox-dialog-close-above';
8876
+ /** A slide's caption (premium M4, T3): the Slider's Caption part shows the caption of the slide in view. */
8877
+ const SLIDE_CAPTION_ATTRIBUTE = 'data-iox-caption';
7792
8878
  var A11yAttribute;
7793
8879
  (function (A11yAttribute) {
7794
8880
  A11yAttribute["Label"] = "aria-label";
@@ -7800,6 +8886,8 @@ var A11yAttribute;
7800
8886
  const MANAGED_A11Y_ATTRIBUTES = [
7801
8887
  A11yAttribute.Label, A11yAttribute.Hidden, A11yAttribute.Current, A11yAttribute.Id,
7802
8888
  'aria-haspopup', 'aria-expanded', 'aria-controls', DIALOG_CLOSE_ABOVE_ATTRIBUTE,
8889
+ HeaderAttribute.Behaviour, HeaderAttribute.Edge, SLIDE_CAPTION_ATTRIBUTE,
8890
+ TOOLTIP_ATTRIBUTE, TOOLTIP_PLACEMENT_ATTRIBUTE,
7803
8891
  ];
7804
8892
  /** A content trait's value from `props` (serialized) or `traits[]` (builder). */
7805
8893
  function nodeTraitValue(node, name) {
@@ -7835,7 +8923,15 @@ function nodeA11yAttributes(node, ctx = {}) {
7835
8923
  if (CURRENT_LINK_TYPES.includes(node.type) && isCurrentLink(ctx.href, ctx.currentPath, ctx.currentLink)) {
7836
8924
  out[A11yAttribute.Current] = ARIA_CURRENT_PAGE;
7837
8925
  }
7838
- 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
+ }
7839
8935
  if (node.type === DIALOG_NODE_TYPE) {
7840
8936
  const id = node.styleId ?? node.id;
7841
8937
  if (id)
@@ -7844,6 +8940,35 @@ function nodeA11yAttributes(node, ctx = {}) {
7844
8940
  if (minWidth !== null)
7845
8941
  out[DIALOG_CLOSE_ABOVE_ATTRIBUTE] = String(minWidth);
7846
8942
  }
8943
+ // T2 (M4): how a header behaves as the page scrolls — the shared CSS and the header runtime read these.
8944
+ Object.assign(out, headerAttributes(nodeTraitValue(node, HeaderTrait.Behaviour), nodeTraitValue(node, HeaderTrait.Edge)));
8945
+ if (node.type === SLIDE_TYPE) {
8946
+ const caption = trimmed$1(nodeTraitValue(node, SLIDE_CAPTION_TRAIT));
8947
+ if (caption)
8948
+ out[SLIDE_CAPTION_ATTRIBUTE] = caption;
8949
+ }
8950
+ return out;
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;
7847
8972
  return out;
7848
8973
  }
7849
8974
  /** The attributes as an HTML string fragment (` name="value"`…) for a string renderer. */
@@ -7887,8 +9012,12 @@ function needsAccessibleName(node) {
7887
9012
  // A Slider's pause control is named by its runtime ("Stop / Start automatic slide show", CC3).
7888
9013
  if (node.role === SLIDER_PAUSE_ROLE)
7889
9014
  return false;
7890
- if (trimmed$1(nodeTraitValue(node, A11yTrait.AccessibleName)))
9015
+ if (trimmed$1(nodeTraitValue(node, A11yTrait.AccessibleName)) || tooltipWords(node))
7891
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) {
7892
9021
  const acc = { words: false, icon: false };
7893
9022
  scanNames(node, acc);
7894
9023
  return acc.icon && !acc.words;
@@ -10226,7 +11355,7 @@ class StringFormRuntime {
10226
11355
  this.apply(response);
10227
11356
  }
10228
11357
  apply(response) {
10229
- const outcome = submitOutcome(response);
11358
+ const outcome = submitOutcome(response, this.form.getAttribute(FORM_AFTER_SEND_ATTRIBUTE));
10230
11359
  this.setRenderState(outcome.state);
10231
11360
  if (outcome.errors) {
10232
11361
  this.state = { ...this.state, submitted: true, submitting: false, errors: { ...outcome.errors } };
@@ -10242,6 +11371,8 @@ class StringFormRuntime {
10242
11371
  this.form.reset();
10243
11372
  this.state = resetFormState();
10244
11373
  this.render();
11374
+ if (outcome.toast)
11375
+ raiseFormToast(this.form);
10245
11376
  if (outcome.redirect)
10246
11377
  this.options.navigate?.(outcome.redirect);
10247
11378
  }
@@ -10375,6 +11506,342 @@ function attachStringForm(form, options = {}) {
10375
11506
  return spec ? new StringFormRuntime(form, spec, options) : null;
10376
11507
  }
10377
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
+
10378
11845
  /*
10379
11846
  * Public API Surface of iox-ui
10380
11847
  */
@@ -10383,5 +11850,5 @@ function attachStringForm(form, options = {}) {
10383
11850
  * Generated bundle index. Do not edit.
10384
11851
  */
10385
11852
 
10386
- 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, 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, HOVER_DEFAULT_TRANSITION_TRAITS, HOVER_MEDIA_QUERY, HOVER_STATES, 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_INPUTS_ATTRIBUTE, SLIDER_INPUT_NAMES, 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, effectiveActionType, effectiveTransitionId, entrancePreStateStyle, entranceStartsHidden, feelFor, fieldLanguage, finalKeyframe, framesFromLiveValue, gateHoverRule, getSeoValue, hasHoverState, hasOwnPageTransition, hasOwnTransition, 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, splitOuterProps, splitPressFeedback, stateNamesIn, statePseudoClass, stateSelectorSuffix, stateTransitionProperties, stringFormRuntimeOf, stripSuppressedProps, swiperAutoplayAllowed, swiperLoopOptions, swiperModuleNames, syncNodeA11yAttributes, syncSubmitButtons, targetedStateSelector, transitionPropertyList, viewportTransitionContext, watchScrolled, withContextSlots, 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 };
10387
11854
  //# sourceMappingURL=vectoriox-iox-ui.mjs.map