@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.
- package/actions-core/index.d.ts +47 -3
- package/composite-core/index.d.ts +93 -15
- package/design-core/index.d.ts +2013 -187
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs +153 -70
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs +307 -39
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +2222 -85
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-forms-core.mjs +27 -4
- package/fesm2022/vectoriox-iox-ui-forms-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +250 -12
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs +101 -5
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui.mjs +1545 -78
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/forms-core/index.d.ts +23 -4
- package/generation-core/index.d.ts +13 -0
- package/index.d.ts +563 -62
- package/package.json +1 -1
- package/scene-core/index.d.ts +32 -4
- package/src/actions-core/action.model.ts +1 -1
- package/src/actions-core/dialog-motion.ts +25 -0
- package/src/actions-core/dialog-opener.ts +9 -3
- package/src/actions-core/element-actions.ts +25 -1
- package/src/actions-core/popover-opener.ts +50 -0
- package/src/actions-core/public-api.ts +1 -0
- package/src/composite-core/header.ts +25 -0
- package/src/composite-core/public-api.ts +1 -0
- package/src/composite-core/slider-runtime.ts +82 -0
- package/src/composite-core/slider.ts +194 -27
- package/src/composite-core/structure.ts +14 -9
- package/src/design-core/flavor-spec.generated.ts +2145 -85
- package/src/design-core/header-behaviour.ts +76 -0
- package/src/design-core/public-api.ts +1 -0
- package/src/design-core/theme-color.ts +21 -5
- package/src/forms-core/submit-lifecycle.ts +30 -3
- package/src/generation-core/baseline-roles.ts +1 -0
- package/src/generation-core/compose-site.ts +8 -0
- package/src/generation-core/node-baselines.generated.ts +231 -11
- package/src/generation-core/patterns.ts +2 -0
- package/src/generation-core/site-spec.generated.ts +8 -0
- package/src/scene-core/scene.ts +18 -0
- package/src/scene-core/scroll-driver.ts +83 -6
- package/styles/iox-design.css +431 -1
- 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
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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
|
|
2146
|
-
* (or a raw `transition`) always wins. Pure:
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
4912
|
-
*
|
|
4913
|
-
|
|
4914
|
-
|
|
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
|
|
5933
|
-
const CALENDAR_SHEET_QUERY =
|
|
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, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>');
|
|
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
|
|
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
|
-
|
|
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
|