@vectoriox/iox-ui 5.2.0 → 5.4.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.
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The visitor-consent vocabulary (consent plan C1). Framework-free for the same forced reason as
3
+ * forms-core/seo-core/actions-core: iox-cms consumes it under plain Node, the engine at SSR time,
4
+ * the builder for design-time preview — ONE implementation of what a decision is.
5
+ */
6
+ declare enum ConsentCategory {
7
+ /** Always on, never user-controllable — the site working at all. */
8
+ Necessary = "necessary",
9
+ Analytics = "analytics",
10
+ Marketing = "marketing"
11
+ }
12
+ /** A visitor's recorded decision. `necessary` is implied true and never stored as false. */
13
+ interface ConsentDecision {
14
+ categories: Partial<Record<ConsentCategory, boolean>>;
15
+ /** Bumping the org's policy version invalidates every prior decision. */
16
+ policyVersion: string;
17
+ /** ISO timestamp of the decision. */
18
+ decidedAt: string;
19
+ anonymousId: string;
20
+ }
21
+ /** A fresh "nothing granted" decision skeleton — used when a visitor must be asked. */
22
+ declare function emptyDecision(anonymousId: string, policyVersion: string, now?: Date): ConsentDecision;
23
+ /** `null` (no decision yet) grants nothing; `necessary` is always granted. */
24
+ declare function hasConsent(decision: ConsentDecision | null | undefined, category: ConsentCategory): boolean;
25
+ /** A decision older than `maxMonths` no longer counts (default re-ask: 12 months, D-C6). */
26
+ declare function isExpired(decision: ConsentDecision, now: Date, maxMonths: number): boolean;
27
+ /**
28
+ * The function the dead fork got wrong: **a recorded "no" is a decision.** The banner shows only
29
+ * when there is no decision, the decision expired, or the policy version moved on — never because
30
+ * the visitor declined.
31
+ */
32
+ declare function needsPrompt(decision: ConsentDecision | null | undefined, policyVersion: string, now?: Date, maxMonths?: number): boolean;
33
+
34
+ /** First-party cookie name — the visitor's browser carries the proof (D-C1). */
35
+ declare const CONSENT_COOKIE_NAME = "iox-consent";
36
+ /** Cookie payload. Versioned so a future shape change can read old cookies tolerantly. */
37
+ declare function encode(decision: ConsentDecision): string;
38
+ /**
39
+ * Tolerant by contract: bad JSON, a foreign shape, a tampered cookie — all read as `null`
40
+ * ("no decision"), never a throw and never a guess. The old fork threw its way to a re-prompt.
41
+ */
42
+ declare function decode(raw: string | null | undefined): ConsentDecision | null;
43
+ /** `Set-Cookie` attributes for the decision: a NECESSARY cookie, 12 months, first-party. */
44
+ declare function cookieAttributes(maxMonths?: number): string;
45
+
46
+ /**
47
+ * Google Consent Mode v2 (consent plan §4): four parameters, mandatory for EEA/UK measurement
48
+ * since March 2024. Our categories map onto them; `necessary` maps to none.
49
+ */
50
+ type ConsentModeSignal = 'granted' | 'denied';
51
+ declare const CONSENT_MODE_PARAMS: readonly ["analytics_storage", "ad_storage", "ad_user_data", "ad_personalization"];
52
+ /** What the SSR head declares BEFORE any tag can load (invariant I1): everything denied. */
53
+ declare const CONSENT_DEFAULTS_DENIED: Readonly<Record<(typeof CONSENT_MODE_PARAMS)[number], ConsentModeSignal>>;
54
+ /** A decision → the four v2 signals. `null` (undecided) is indistinguishable from denied. */
55
+ declare function toConsentModeSignals(decision: ConsentDecision | null | undefined): Record<string, ConsentModeSignal>;
56
+ /** The inline `<head>` bootstrap (basic mode, D-C2): defaults denied before anything loads. */
57
+ declare function consentModeBootstrapJs(): string;
58
+
59
+ export { CONSENT_COOKIE_NAME, CONSENT_DEFAULTS_DENIED, CONSENT_MODE_PARAMS, ConsentCategory, consentModeBootstrapJs, cookieAttributes, decode, emptyDecision, encode, hasConsent, isExpired, needsPrompt, toConsentModeSignals };
60
+ export type { ConsentDecision, ConsentModeSignal };
@@ -0,0 +1,3 @@
1
+ {
2
+ "module": "../fesm2022/vectoriox-iox-ui-consent-core.mjs"
3
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The visitor-consent vocabulary (consent plan C1). Framework-free for the same forced reason as
3
+ * forms-core/seo-core/actions-core: iox-cms consumes it under plain Node, the engine at SSR time,
4
+ * the builder for design-time preview — ONE implementation of what a decision is.
5
+ */
6
+ var ConsentCategory;
7
+ (function (ConsentCategory) {
8
+ /** Always on, never user-controllable — the site working at all. */
9
+ ConsentCategory["Necessary"] = "necessary";
10
+ ConsentCategory["Analytics"] = "analytics";
11
+ ConsentCategory["Marketing"] = "marketing";
12
+ })(ConsentCategory || (ConsentCategory = {}));
13
+ /** A fresh "nothing granted" decision skeleton — used when a visitor must be asked. */
14
+ function emptyDecision(anonymousId, policyVersion, now = new Date()) {
15
+ return {
16
+ categories: { [ConsentCategory.Necessary]: true },
17
+ policyVersion,
18
+ decidedAt: now.toISOString(),
19
+ anonymousId,
20
+ };
21
+ }
22
+ /** `null` (no decision yet) grants nothing; `necessary` is always granted. */
23
+ function hasConsent(decision, category) {
24
+ if (category === ConsentCategory.Necessary)
25
+ return true;
26
+ if (!decision)
27
+ return false;
28
+ return decision.categories?.[category] === true;
29
+ }
30
+ /** A decision older than `maxMonths` no longer counts (default re-ask: 12 months, D-C6). */
31
+ function isExpired(decision, now, maxMonths) {
32
+ const decided = new Date(decision.decidedAt);
33
+ if (Number.isNaN(decided.getTime()))
34
+ return true;
35
+ const expiry = new Date(decided);
36
+ expiry.setMonth(expiry.getMonth() + maxMonths);
37
+ return now >= expiry;
38
+ }
39
+ /**
40
+ * The function the dead fork got wrong: **a recorded "no" is a decision.** The banner shows only
41
+ * when there is no decision, the decision expired, or the policy version moved on — never because
42
+ * the visitor declined.
43
+ */
44
+ function needsPrompt(decision, policyVersion, now = new Date(), maxMonths = 12) {
45
+ if (!decision)
46
+ return true;
47
+ if (decision.policyVersion !== policyVersion)
48
+ return true;
49
+ return isExpired(decision, now, maxMonths);
50
+ }
51
+
52
+ /** First-party cookie name — the visitor's browser carries the proof (D-C1). */
53
+ const CONSENT_COOKIE_NAME = 'iox-consent';
54
+ /** Cookie payload. Versioned so a future shape change can read old cookies tolerantly. */
55
+ function encode(decision) {
56
+ return JSON.stringify({ v: 1, ...decision });
57
+ }
58
+ /**
59
+ * Tolerant by contract: bad JSON, a foreign shape, a tampered cookie — all read as `null`
60
+ * ("no decision"), never a throw and never a guess. The old fork threw its way to a re-prompt.
61
+ */
62
+ function decode(raw) {
63
+ if (!raw)
64
+ return null;
65
+ try {
66
+ const parsed = JSON.parse(raw);
67
+ if (!parsed || typeof parsed !== 'object')
68
+ return null;
69
+ if (typeof parsed.anonymousId !== 'string' || !parsed.anonymousId)
70
+ return null;
71
+ if (typeof parsed.policyVersion !== 'string')
72
+ return null;
73
+ if (!parsed.categories || typeof parsed.categories !== 'object' || Array.isArray(parsed.categories))
74
+ return null;
75
+ const categories = {};
76
+ for (const [k, v] of Object.entries(parsed.categories)) {
77
+ categories[k] = v === true;
78
+ }
79
+ categories[ConsentCategory.Necessary] = true;
80
+ return {
81
+ categories,
82
+ policyVersion: parsed.policyVersion,
83
+ decidedAt: typeof parsed.decidedAt === 'string' ? parsed.decidedAt : '',
84
+ anonymousId: parsed.anonymousId,
85
+ };
86
+ }
87
+ catch {
88
+ return null;
89
+ }
90
+ }
91
+ /** `Set-Cookie` attributes for the decision: a NECESSARY cookie, 12 months, first-party. */
92
+ function cookieAttributes(maxMonths = 12) {
93
+ const maxAge = Math.floor(maxMonths * 30.44 * 24 * 60 * 60);
94
+ return `Path=/; Max-Age=${maxAge}; SameSite=Lax; Secure`;
95
+ }
96
+
97
+ const CONSENT_MODE_PARAMS = [
98
+ 'analytics_storage', 'ad_storage', 'ad_user_data', 'ad_personalization',
99
+ ];
100
+ /** What the SSR head declares BEFORE any tag can load (invariant I1): everything denied. */
101
+ const CONSENT_DEFAULTS_DENIED = Object.freeze({
102
+ analytics_storage: 'denied',
103
+ ad_storage: 'denied',
104
+ ad_user_data: 'denied',
105
+ ad_personalization: 'denied',
106
+ });
107
+ /** A decision → the four v2 signals. `null` (undecided) is indistinguishable from denied. */
108
+ function toConsentModeSignals(decision) {
109
+ const analytics = hasConsent(decision ?? null, ConsentCategory.Analytics) ? 'granted' : 'denied';
110
+ const marketing = hasConsent(decision ?? null, ConsentCategory.Marketing) ? 'granted' : 'denied';
111
+ return {
112
+ analytics_storage: analytics,
113
+ ad_storage: marketing,
114
+ ad_user_data: marketing,
115
+ ad_personalization: marketing,
116
+ };
117
+ }
118
+ /** The inline `<head>` bootstrap (basic mode, D-C2): defaults denied before anything loads. */
119
+ function consentModeBootstrapJs() {
120
+ return "window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}" +
121
+ `gtag('consent','default',${JSON.stringify(CONSENT_DEFAULTS_DENIED)});`;
122
+ }
123
+
124
+ /**
125
+ * @vectoriox/iox-ui/consent-core — the shared, FRAMEWORK-FREE core of visitor consent.
126
+ *
127
+ * What a decision IS (`needsPrompt`: a recorded "no" is a decision), how it travels (a tolerant
128
+ * first-party cookie), and what it tells Google (Consent Mode v2 signals, denied-by-default).
129
+ * Consumed by the SSR engine (the gate, C2), the builder (banner preview, C3) and iox-cms (the
130
+ * optional per-org log, C4) — the fourth entry point on the forms-core pattern, replacing the
131
+ * dead byte-identical fork C0 deleted.
132
+ * ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, tsconfig
133
+ * paths — or `nest start` dies with ERR_REQUIRE_ESM while jest and build both pass.
134
+ */
135
+
136
+ /**
137
+ * Generated bundle index. Do not edit.
138
+ */
139
+
140
+ export { CONSENT_COOKIE_NAME, CONSENT_DEFAULTS_DENIED, CONSENT_MODE_PARAMS, ConsentCategory, consentModeBootstrapJs, cookieAttributes, decode, emptyDecision, encode, hasConsent, isExpired, needsPrompt, toConsentModeSignals };
141
+ //# sourceMappingURL=vectoriox-iox-ui-consent-core.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vectoriox-iox-ui-consent-core.mjs","sources":["../../../projects/iox-ui/src/consent-core/consent.model.ts","../../../projects/iox-ui/src/consent-core/storage.ts","../../../projects/iox-ui/src/consent-core/consent-mode.ts","../../../projects/iox-ui/src/consent-core/public-api.ts","../../../projects/iox-ui/src/consent-core/vectoriox-iox-ui-consent-core.ts"],"sourcesContent":["/**\n * The visitor-consent vocabulary (consent plan C1). Framework-free for the same forced reason as\n * forms-core/seo-core/actions-core: iox-cms consumes it under plain Node, the engine at SSR time,\n * the builder for design-time preview — ONE implementation of what a decision is.\n */\n\nexport enum ConsentCategory {\n /** Always on, never user-controllable — the site working at all. */\n Necessary = 'necessary',\n Analytics = 'analytics',\n Marketing = 'marketing',\n}\n\n/** A visitor's recorded decision. `necessary` is implied true and never stored as false. */\nexport interface ConsentDecision {\n categories: Partial<Record<ConsentCategory, boolean>>;\n /** Bumping the org's policy version invalidates every prior decision. */\n policyVersion: string;\n /** ISO timestamp of the decision. */\n decidedAt: string;\n anonymousId: string;\n}\n\n/** A fresh \"nothing granted\" decision skeleton — used when a visitor must be asked. */\nexport function emptyDecision(anonymousId: string, policyVersion: string, now: Date = new Date()): ConsentDecision {\n return {\n categories: { [ConsentCategory.Necessary]: true },\n policyVersion,\n decidedAt: now.toISOString(),\n anonymousId,\n };\n}\n\n/** `null` (no decision yet) grants nothing; `necessary` is always granted. */\nexport function hasConsent(decision: ConsentDecision | null | undefined, category: ConsentCategory): boolean {\n if (category === ConsentCategory.Necessary) return true;\n if (!decision) return false;\n return decision.categories?.[category] === true;\n}\n\n/** A decision older than `maxMonths` no longer counts (default re-ask: 12 months, D-C6). */\nexport function isExpired(decision: ConsentDecision, now: Date, maxMonths: number): boolean {\n const decided = new Date(decision.decidedAt);\n if (Number.isNaN(decided.getTime())) return true;\n const expiry = new Date(decided);\n expiry.setMonth(expiry.getMonth() + maxMonths);\n return now >= expiry;\n}\n\n/**\n * The function the dead fork got wrong: **a recorded \"no\" is a decision.** The banner shows only\n * when there is no decision, the decision expired, or the policy version moved on — never because\n * the visitor declined.\n */\nexport function needsPrompt(\n decision: ConsentDecision | null | undefined,\n policyVersion: string,\n now: Date = new Date(),\n maxMonths = 12,\n): boolean {\n if (!decision) return true;\n if (decision.policyVersion !== policyVersion) return true;\n return isExpired(decision, now, maxMonths);\n}\n","import { ConsentCategory, ConsentDecision } from './consent.model';\n\n/** First-party cookie name — the visitor's browser carries the proof (D-C1). */\nexport const CONSENT_COOKIE_NAME = 'iox-consent';\n\n/** Cookie payload. Versioned so a future shape change can read old cookies tolerantly. */\nexport function encode(decision: ConsentDecision): string {\n return JSON.stringify({ v: 1, ...decision });\n}\n\n/**\n * Tolerant by contract: bad JSON, a foreign shape, a tampered cookie — all read as `null`\n * (\"no decision\"), never a throw and never a guess. The old fork threw its way to a re-prompt.\n */\nexport function decode(raw: string | null | undefined): ConsentDecision | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw);\n if (!parsed || typeof parsed !== 'object') return null;\n if (typeof parsed.anonymousId !== 'string' || !parsed.anonymousId) return null;\n if (typeof parsed.policyVersion !== 'string') return null;\n if (!parsed.categories || typeof parsed.categories !== 'object' || Array.isArray(parsed.categories)) return null;\n const categories: Partial<Record<ConsentCategory, boolean>> = {};\n for (const [k, v] of Object.entries(parsed.categories)) {\n categories[k as ConsentCategory] = v === true;\n }\n categories[ConsentCategory.Necessary] = true;\n return {\n categories,\n policyVersion: parsed.policyVersion,\n decidedAt: typeof parsed.decidedAt === 'string' ? parsed.decidedAt : '',\n anonymousId: parsed.anonymousId,\n };\n } catch {\n return null;\n }\n}\n\n/** `Set-Cookie` attributes for the decision: a NECESSARY cookie, 12 months, first-party. */\nexport function cookieAttributes(maxMonths = 12): string {\n const maxAge = Math.floor(maxMonths * 30.44 * 24 * 60 * 60);\n return `Path=/; Max-Age=${maxAge}; SameSite=Lax; Secure`;\n}\n","import { ConsentCategory, ConsentDecision, hasConsent } from './consent.model';\n\n/**\n * Google Consent Mode v2 (consent plan §4): four parameters, mandatory for EEA/UK measurement\n * since March 2024. Our categories map onto them; `necessary` maps to none.\n */\nexport type ConsentModeSignal = 'granted' | 'denied';\n\nexport const CONSENT_MODE_PARAMS = [\n 'analytics_storage', 'ad_storage', 'ad_user_data', 'ad_personalization',\n] as const;\n\n/** What the SSR head declares BEFORE any tag can load (invariant I1): everything denied. */\nexport const CONSENT_DEFAULTS_DENIED: Readonly<Record<(typeof CONSENT_MODE_PARAMS)[number], ConsentModeSignal>> =\n Object.freeze({\n analytics_storage: 'denied',\n ad_storage: 'denied',\n ad_user_data: 'denied',\n ad_personalization: 'denied',\n });\n\n/** A decision → the four v2 signals. `null` (undecided) is indistinguishable from denied. */\nexport function toConsentModeSignals(decision: ConsentDecision | null | undefined): Record<string, ConsentModeSignal> {\n const analytics: ConsentModeSignal = hasConsent(decision ?? null, ConsentCategory.Analytics) ? 'granted' : 'denied';\n const marketing: ConsentModeSignal = hasConsent(decision ?? null, ConsentCategory.Marketing) ? 'granted' : 'denied';\n return {\n analytics_storage: analytics,\n ad_storage: marketing,\n ad_user_data: marketing,\n ad_personalization: marketing,\n };\n}\n\n/** The inline `<head>` bootstrap (basic mode, D-C2): defaults denied before anything loads. */\nexport function consentModeBootstrapJs(): string {\n return \"window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}\" +\n `gtag('consent','default',${JSON.stringify(CONSENT_DEFAULTS_DENIED)});`;\n}\n","/**\n * @vectoriox/iox-ui/consent-core — the shared, FRAMEWORK-FREE core of visitor consent.\n *\n * What a decision IS (`needsPrompt`: a recorded \"no\" is a decision), how it travels (a tolerant\n * first-party cookie), and what it tells Google (Consent Mode v2 signals, denied-by-default).\n * Consumed by the SSR engine (the gate, C2), the builder (banner preview, C3) and iox-cms (the\n * optional per-org log, C4) — the fourth entry point on the forms-core pattern, replacing the\n * dead byte-identical fork C0 deleted.\n * ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, tsconfig\n * paths — or `nest start` dies with ERR_REQUIRE_ESM while jest and build both pass.\n */\nexport * from './consent.model';\nexport * from './storage';\nexport * from './consent-mode';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -4995,6 +4995,95 @@ function compileTokenCss(tokens) {
4995
4995
  };
4996
4996
  }
4997
4997
 
4998
+ /** trait → `var(--iox-<token>, <packValue>)` for one node type. Unknown type → {}. */
4999
+ function resolveFlavorDefaults(type, pack) {
5000
+ const map = pack?.defaults?.[type];
5001
+ if (!map)
5002
+ return {};
5003
+ const byName = new Map((pack.tokens ?? []).map(t => [t.name, t.value]));
5004
+ const out = {};
5005
+ for (const [trait, tokenName] of Object.entries(map)) {
5006
+ const fallback = byName.get(tokenName);
5007
+ if (fallback === undefined)
5008
+ continue; // validator fails the build; runtime stays safe
5009
+ out[trait] = `var(--iox-${tokenName}, ${fallback})`;
5010
+ }
5011
+ return out;
5012
+ }
5013
+ /**
5014
+ * The ENGINE's half of the cascade: kebab `prop: var(…)` declarations for every pack default
5015
+ * the node's persisted styleProps does NOT set. Appended beside renderDefaultDeclarations —
5016
+ * same guard-on-undefined discipline, so a node saved post-F1 (var already serialised via the
5017
+ * builder's trait defaults) is untouched, and a legacy node picks the pack default up at
5018
+ * render time. Node styleProps always wins.
5019
+ */
5020
+ function flavorDefaultDeclarations(type, styleProps, pack) {
5021
+ const sp = styleProps ?? {};
5022
+ const out = [];
5023
+ for (const [trait, value] of Object.entries(resolveFlavorDefaults(type, pack))) {
5024
+ if (sp[trait] === undefined)
5025
+ out.push(`${styleKeyToKebab(trait)}: ${value}`);
5026
+ }
5027
+ return out;
5028
+ }
5029
+ /**
5030
+ * The CI gate (flavors plan §10 C4): a pack may only name types and traits that exist in the
5031
+ * live component registry, and tokens it defines itself. Returns human-readable errors; a
5032
+ * consumer spec feeds it the REAL registry and asserts [].
5033
+ */
5034
+ function validateFlavorPack(pack, knownTraitsByType) {
5035
+ const errors = [];
5036
+ const tokenNames = new Set((pack.tokens ?? []).map(t => t.name));
5037
+ for (const t of pack.tokens ?? []) {
5038
+ if (!t.name)
5039
+ errors.push(`${pack.id}: token with empty name`);
5040
+ if (!t.value)
5041
+ errors.push(`${pack.id}: token "${t.name}" has no value`);
5042
+ }
5043
+ for (const [type, traits] of Object.entries(pack.defaults ?? {})) {
5044
+ const known = knownTraitsByType[type];
5045
+ if (!known) {
5046
+ errors.push(`${pack.id}: unknown component type "${type}"`);
5047
+ continue;
5048
+ }
5049
+ const knownSet = known instanceof Set ? known : new Set(known);
5050
+ for (const [trait, tokenName] of Object.entries(traits)) {
5051
+ if (!knownSet.has(trait))
5052
+ errors.push(`${pack.id}: "${type}" has no style trait "${trait}"`);
5053
+ if (!tokenNames.has(tokenName))
5054
+ errors.push(`${pack.id}: "${type}.${trait}" references undefined token "${tokenName}"`);
5055
+ if (/[:(]|^#|\d/.test(tokenName))
5056
+ errors.push(`${pack.id}: "${type}.${trait}" must name a token, not a value ("${tokenName}")`);
5057
+ }
5058
+ }
5059
+ return errors;
5060
+ }
5061
+ /**
5062
+ * Swiss — the default pack (flavors plan §8: "the flavor that can never embarrass anyone").
5063
+ * Deliberately SMALL at F1: radius + control padding on the clickables, surface radius on Card.
5064
+ * Colour, type and motion arrive with the brand step (F2) — a colour default here would repaint
5065
+ * legacy pages before anyone chose anything.
5066
+ */
5067
+ const SWISS_FLAVOR = {
5068
+ id: 'swiss',
5069
+ version: 1,
5070
+ label: 'Swiss',
5071
+ blurb: 'Grid discipline, hairline rules, near-zero motion.',
5072
+ tokens: [
5073
+ { name: 'radius-control', value: '4px', type: 'size', group: 'radius' },
5074
+ { name: 'radius-surface', value: '4px', type: 'size', group: 'radius' },
5075
+ { name: 'space-control', value: '10px 18px', type: 'spacing', group: 'space' },
5076
+ { name: 'space-stack', value: '12px', type: 'spacing', group: 'space' },
5077
+ ],
5078
+ defaults: {
5079
+ Button: { borderRadius: 'radius-control', padding: 'space-control' },
5080
+ ButtonBlock: { borderRadius: 'radius-control', padding: 'space-control' },
5081
+ Card: { borderRadius: 'radius-surface' },
5082
+ },
5083
+ };
5084
+ /** What both renderers use until F2 lets an org choose. */
5085
+ const DEFAULT_FLAVOR = SWISS_FLAVOR;
5086
+
4998
5087
  /**
4999
5088
  * page-style — the ONE compiler that turns a page's style settings into CSS.
5000
5089
  *
@@ -6832,5 +6921,5 @@ function effectiveTransitionId(anim) {
6832
6921
  * Generated bundle index. Do not edit.
6833
6922
  */
6834
6923
 
6835
- export { AOSService, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CONTEXT_ACTION_TYPES, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ContainerComponent, ContextStore, DEFAULT_GALLERY_SCHEDULER, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_PHOSPHOR_WEIGHT, DEFAULT_WIDTH_BY_TYPE, DeclarationContext, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxSubmitButtonComponent, IoxUiModule, LinkedContainerComponent, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, RESERVED_ALIAS_PREFIX, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_RESPONSIVE_TRAITS, SectionComponent, TRANSITION_FEELS, TextBlockComponent, VALIDATION_STATES, VALIDATION_STATE_CLASS, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, activeActions, applyBindingPipes, applyFeel, breakpointDef, breakpointForWidth, breakpointQueryFor, buildDataSourceFilter, buildPartSelector, buildRelatedFilter, buildSwiperBreakpoints, buildTargetedStateSelector, buildTransitionTimeline, cascadeChain, clearInputOverride, clearOverride, collectContextSlotKeys, collectReferencedAliases, compileDeclarations, compilePageStyle, compileTokenCss, composeVirtualTraits, computeRelative, contextActionEffect, contextSlotKeys, countOverrides, dataSourcePlanToRequest, declaredInputNames, defaultMessageResolver, deriveSeoFromContent, describeSeoFields, effectiveTransitionId, excludeSelf, feelFor, getByPath, getSeoValue, initialSlotValues, isActionEnabled, isContextAction, isInputOverriddenAt, isOverriddenAt, isReservedAlias, isValidSlotKey, legacyEnterToTransition, matchRouteParams, mediaQueryFor, mergeSwiperBreakpoints, nearestCommonAncestorId, nodeCssId, nodeUsesContext, normalizeCollectionResult, normalizeGalleryImage, normalizeIconClass, parentBreakpoint, parseUrlList, partClassName, planDataSource, planResponsiveRules, referencedDataSources, referencedDataSourcesDeep, renderDefaultDeclarations, resetSeoField, resolveBoundValue, resolveContextValue, resolveDerivedInto, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolvePageTransition, resolvePath, resolveRelativeInto, resolveRepeaterItems, resolveSeo, resolveSiblings, resolveSingleItemId, resolveStateStyles, resolveStyleProps, resolveValueChain, rewriteViewportUnits, safeSetInput, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, stateNamesIn, stripSuppressedProps, styleKeyToKebab, tokensToDarkVarMap, tokensToVarMap, variableAliases, variableItemsFrom, withContextSlots, wrapInBreakpointQuery, wrapInMediaQuery };
6924
+ export { AOSService, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CONTEXT_ACTION_TYPES, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ContainerComponent, ContextStore, DEFAULT_FLAVOR, DEFAULT_GALLERY_SCHEDULER, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_PHOSPHOR_WEIGHT, DEFAULT_WIDTH_BY_TYPE, DeclarationContext, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxSubmitButtonComponent, IoxUiModule, LinkedContainerComponent, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, RESERVED_ALIAS_PREFIX, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_RESPONSIVE_TRAITS, SWISS_FLAVOR, SectionComponent, TRANSITION_FEELS, TextBlockComponent, VALIDATION_STATES, VALIDATION_STATE_CLASS, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, activeActions, applyBindingPipes, applyFeel, breakpointDef, breakpointForWidth, breakpointQueryFor, buildDataSourceFilter, buildPartSelector, buildRelatedFilter, buildSwiperBreakpoints, buildTargetedStateSelector, buildTransitionTimeline, cascadeChain, clearInputOverride, clearOverride, collectContextSlotKeys, collectReferencedAliases, compileDeclarations, compilePageStyle, compileTokenCss, composeVirtualTraits, computeRelative, contextActionEffect, contextSlotKeys, countOverrides, dataSourcePlanToRequest, declaredInputNames, defaultMessageResolver, deriveSeoFromContent, describeSeoFields, effectiveTransitionId, excludeSelf, feelFor, flavorDefaultDeclarations, getByPath, getSeoValue, initialSlotValues, isActionEnabled, isContextAction, isInputOverriddenAt, isOverriddenAt, isReservedAlias, isValidSlotKey, legacyEnterToTransition, matchRouteParams, mediaQueryFor, mergeSwiperBreakpoints, nearestCommonAncestorId, nodeCssId, nodeUsesContext, normalizeCollectionResult, normalizeGalleryImage, normalizeIconClass, parentBreakpoint, parseUrlList, partClassName, planDataSource, planResponsiveRules, referencedDataSources, referencedDataSourcesDeep, renderDefaultDeclarations, resetSeoField, resolveBoundValue, resolveContextValue, resolveDerivedInto, resolveFlavorDefaults, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolvePageTransition, resolvePath, resolveRelativeInto, resolveRepeaterItems, resolveSeo, resolveSiblings, resolveSingleItemId, resolveStateStyles, resolveStyleProps, resolveValueChain, rewriteViewportUnits, safeSetInput, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, stateNamesIn, stripSuppressedProps, styleKeyToKebab, tokensToDarkVarMap, tokensToVarMap, validateFlavorPack, variableAliases, variableItemsFrom, withContextSlots, wrapInBreakpointQuery, wrapInMediaQuery };
6836
6925
  //# sourceMappingURL=vectoriox-iox-ui.mjs.map