@vectoriox/iox-ui 5.11.0 → 5.12.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/index.d.ts CHANGED
@@ -374,12 +374,21 @@ declare class SectionComponent {
374
374
  static ɵcmp: i0.ɵɵComponentDeclaration<SectionComponent, "section[iox-section]", never, { "nodeId": { "alias": "nodeId"; "required": false; }; }, {}, never, never, false, never>;
375
375
  }
376
376
 
377
- /** Directive-host container: the `<div>` IS the host — no wrapper, no inner/outer split. */
377
+ /**
378
+ * The tags a Container may render as (its `htmlTag` trait) — the builder canvas's list. The engine's
379
+ * string renderer and component path both pick from THIS list, so a Navigation bar is a `<nav>` on
380
+ * the live site exactly as on the canvas.
381
+ */
382
+ declare const CONTAINER_HTML_TAGS: ReadonlyArray<string>;
383
+ /** `htmlTag` when it is an allowed Container tag, else `div`. */
384
+ declare function containerHtmlTag(htmlTag: unknown): string;
385
+ /** Directive-host container: the host element IS the container — no wrapper, no inner/outer split.
386
+ * Variable tag: the engine creates it on an explicit `hostElement` of `containerHtmlTag(htmlTag)`. */
378
387
  declare class ContainerComponent {
379
388
  nodeId: string;
380
389
  childrenHost: ViewContainerRef;
381
390
  static ɵfac: i0.ɵɵFactoryDeclaration<ContainerComponent, never>;
382
- static ɵcmp: i0.ɵɵComponentDeclaration<ContainerComponent, "div[iox-container]", never, { "nodeId": { "alias": "nodeId"; "required": false; }; }, {}, never, never, false, never>;
391
+ static ɵcmp: i0.ɵɵComponentDeclaration<ContainerComponent, "div[iox-container],section[iox-container],article[iox-container],header[iox-container],footer[iox-container],main[iox-container],nav[iox-container],ul[iox-container],ol[iox-container],li[iox-container]", never, { "nodeId": { "alias": "nodeId"; "required": false; }; }, {}, never, never, false, never>;
383
392
  }
384
393
 
385
394
  /** Directive-host linked container: the `<a>` IS the host — no wrapper, no inner/outer split. */
@@ -1265,9 +1274,32 @@ declare class IoxFormErrorComponent {
1265
1274
  static ɵcmp: i0.ɵɵComponentDeclaration<IoxFormErrorComponent, "div[iox-form-error]", never, { "nodeId": { "alias": "nodeId"; "required": false; }; "message": { "alias": "message"; "required": false; }; }, {}, never, never, false, never>;
1266
1275
  }
1267
1276
 
1277
+ /**
1278
+ * The Dialog's LIVE twin on the engine's component path (actions plan A1b; design-system DS2).
1279
+ * Directive-host: the `<dialog>` IS the host, carrying exactly the markup the string renderer
1280
+ * writes (`data-iox-dialog`, the opt-in backdrop-close / consent-role / placement attributes and
1281
+ * the close X), so the runtime's dialog actions, the consent prompt and `styles.scss` treat both
1282
+ * paths alike. Ships CLOSED — only the dialog actions open it. Before this existed, a page holding
1283
+ * a Dialog fell back to the string renderer wholesale.
1284
+ */
1285
+ declare class IoxDialogComponent {
1286
+ nodeId: string;
1287
+ placement: string;
1288
+ closeOnBackdrop: boolean | string;
1289
+ showCloseButton: boolean | string;
1290
+ consentRole: boolean | string;
1291
+ childrenHost: ViewContainerRef;
1292
+ get backdropCloses(): boolean;
1293
+ get closeButtonShown(): boolean;
1294
+ get consentRoleOn(): boolean;
1295
+ get placementAttr(): string | null;
1296
+ static ɵfac: i0.ɵɵFactoryDeclaration<IoxDialogComponent, never>;
1297
+ static ɵcmp: i0.ɵɵComponentDeclaration<IoxDialogComponent, "dialog[iox-dialog]", never, { "nodeId": { "alias": "nodeId"; "required": false; }; "placement": { "alias": "placement"; "required": false; }; "closeOnBackdrop": { "alias": "closeOnBackdrop"; "required": false; }; "showCloseButton": { "alias": "showCloseButton"; "required": false; }; "consentRole": { "alias": "consentRole"; "required": false; }; }, {}, never, never, false, never>;
1298
+ }
1299
+
1268
1300
  declare class IoxBuilderComponentsModule {
1269
1301
  static ɵfac: i0.ɵɵFactoryDeclaration<IoxBuilderComponentsModule, never>;
1270
- static ɵmod: i0.ɵɵNgModuleDeclaration<IoxBuilderComponentsModule, [typeof BuilderImageComponent, typeof BuilderSpacerComponent, typeof CardComponent, typeof BuilderDividerComponent, typeof BuilderIconComponent, typeof BuilderLinkComponent, typeof BuilderButtonComponent, typeof BuilderHeadingComponent, typeof TextBlockComponent, typeof SectionComponent, typeof ContainerComponent, typeof LinkedContainerComponent, typeof ClientRepeaterComponent, typeof ButtonBlockComponent, typeof ClientSliderContainerComponent, typeof ClientSliderSlideComponent, typeof ClientListComponent, typeof ClientListItemComponent, typeof ClientGalleryComponent, typeof IoxFormComponent, typeof IoxFieldComponent, typeof IoxSubmitButtonComponent, typeof IoxFormSuccessComponent, typeof IoxFormErrorComponent], [typeof i2.CommonModule], [typeof BuilderImageComponent, typeof BuilderSpacerComponent, typeof CardComponent, typeof BuilderDividerComponent, typeof BuilderIconComponent, typeof BuilderLinkComponent, typeof BuilderButtonComponent, typeof BuilderHeadingComponent, typeof TextBlockComponent, typeof SectionComponent, typeof ContainerComponent, typeof LinkedContainerComponent, typeof ClientRepeaterComponent, typeof ButtonBlockComponent, typeof ClientSliderContainerComponent, typeof ClientSliderSlideComponent, typeof ClientListComponent, typeof ClientListItemComponent, typeof ClientGalleryComponent, typeof IoxFormComponent, typeof IoxFieldComponent, typeof IoxSubmitButtonComponent, typeof IoxFormSuccessComponent, typeof IoxFormErrorComponent]>;
1302
+ static ɵmod: i0.ɵɵNgModuleDeclaration<IoxBuilderComponentsModule, [typeof BuilderImageComponent, typeof BuilderSpacerComponent, typeof CardComponent, typeof BuilderDividerComponent, typeof BuilderIconComponent, typeof BuilderLinkComponent, typeof BuilderButtonComponent, typeof BuilderHeadingComponent, typeof TextBlockComponent, typeof SectionComponent, typeof ContainerComponent, typeof LinkedContainerComponent, typeof ClientRepeaterComponent, typeof ButtonBlockComponent, typeof ClientSliderContainerComponent, typeof ClientSliderSlideComponent, typeof ClientListComponent, typeof ClientListItemComponent, typeof ClientGalleryComponent, typeof IoxFormComponent, typeof IoxFieldComponent, typeof IoxSubmitButtonComponent, typeof IoxFormSuccessComponent, typeof IoxFormErrorComponent, typeof IoxDialogComponent], [typeof i2.CommonModule], [typeof BuilderImageComponent, typeof BuilderSpacerComponent, typeof CardComponent, typeof BuilderDividerComponent, typeof BuilderIconComponent, typeof BuilderLinkComponent, typeof BuilderButtonComponent, typeof BuilderHeadingComponent, typeof TextBlockComponent, typeof SectionComponent, typeof ContainerComponent, typeof LinkedContainerComponent, typeof ClientRepeaterComponent, typeof ButtonBlockComponent, typeof ClientSliderContainerComponent, typeof ClientSliderSlideComponent, typeof ClientListComponent, typeof ClientListItemComponent, typeof ClientGalleryComponent, typeof IoxFormComponent, typeof IoxFieldComponent, typeof IoxSubmitButtonComponent, typeof IoxFormSuccessComponent, typeof IoxFormErrorComponent, typeof IoxDialogComponent]>;
1271
1303
  static ɵinj: i0.ɵɵInjectorDeclaration<IoxBuilderComponentsModule>;
1272
1304
  }
1273
1305
 
@@ -1351,6 +1383,32 @@ declare enum DeclarationContext {
1351
1383
  * initial values are kept, because there they reset rather than no-op.
1352
1384
  */
1353
1385
  declare function compileDeclarations(props: Record<string, any>, importantProps?: ReadonlySet<string>, context?: DeclarationContext): string[];
1386
+ /**
1387
+ * The state names whose rule compiles to a DIFFERENT pseudo-class than their own name. `focus`
1388
+ * compiles to `:focus-visible` (design-system DS2): an element style's focus ring must appear for
1389
+ * keyboard users, not stick to a button after a mouse click. Stored states keep the name `focus` —
1390
+ * no migration — and a text field still matches, since browsers treat a focused text input as
1391
+ * focus-visible. Shared by the builder canvas and both engine paths.
1392
+ */
1393
+ declare const STATE_PSEUDO_CLASS: Readonly<Record<string, string>>;
1394
+ /** The pseudo-class a stored state compiles to (`focus` → `focus-visible`, everything else itself). */
1395
+ declare function statePseudoClass(state: string): string;
1396
+ /** The states driven by a pointer hovering something — each earns the default hover transition. */
1397
+ declare const HOVER_STATES: ReadonlyArray<string>;
1398
+ /** True when the node restyles on hover (its own, its parent's or a chosen trigger's). */
1399
+ declare function hasHoverState(stateStyles: Record<string, Record<string, any> | undefined> | null | undefined): boolean;
1400
+ /**
1401
+ * The base-rule transition a hovering node gets when it declares none of its own, so a hover
1402
+ * animates instead of snapping. Token-driven with today's literal as the fallback (T6), so an
1403
+ * unseeded org renders `all 0.2s ease` byte-identically. ONE copy for the builder canvas, the style
1404
+ * panel and the engine — it was hand-written in three places.
1405
+ */
1406
+ declare const HOVER_DEFAULT_TRANSITION = "all var(--iox-dur-fast, 0.2s) var(--iox-ease-standard, ease)";
1407
+ /** True when the RAW style map already yields a transition (an explicit one, or the composed
1408
+ * transitionDuration/TimingFunction an element style writes). */
1409
+ declare function hasOwnTransition(styleProps: Record<string, any> | null | undefined): boolean;
1410
+ /** `styleProps` plus the default hover transition when the node hovers and sets none itself. */
1411
+ declare function withDefaultHoverTransition(styleProps: Record<string, any>, stateStyles: Record<string, Record<string, any> | undefined> | null | undefined): Record<string, any>;
1354
1412
  /**
1355
1413
  * Per-type default `width` that the builder applies from each component's config
1356
1414
  * (NOT persisted in styleProps — see render-parity.md "saved node is not
@@ -1391,6 +1449,11 @@ interface VirtualTraitGroup {
1391
1449
  * (`::marker` styles are a nested map, not a single cancellable property). */
1392
1450
  readonly reset: string | null;
1393
1451
  }
1452
+ /** The whole-value backdrop trait (DS2). A plain key — it is copied, not stripped — but it feeds
1453
+ * the backdrop group's output, so a breakpoint layer that sets it must count as touching it. */
1454
+ declare const BACKDROP_WHOLE_KEY = "backdropFilter";
1455
+ /** The prefixed twin emitted beside every composed `backdropFilter` (Safari). */
1456
+ declare const BACKDROP_TWIN_KEY = "WebkitBackdropFilter";
1394
1457
  declare const VIRTUAL_TRAIT_GROUPS: readonly VirtualTraitGroup[];
1395
1458
  /** Every virtual trait key — derived from the groups so there is one list to maintain. */
1396
1459
  declare const VIRTUAL_TRAIT_KEYS: Set<string>;
@@ -2690,5 +2753,5 @@ declare class GalleryLoop {
2690
2753
  private emit;
2691
2754
  }
2692
2755
 
2693
- 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, 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, variableAliases, variableItemsFrom, withContextSlots, wrapInBreakpointQuery, wrapInMediaQuery };
2756
+ export { AOSService, BACKDROP_TWIN_KEY, BACKDROP_WHOLE_KEY, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CONTAINER_HTML_TAGS, 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, HOVER_DEFAULT_TRANSITION, HOVER_STATES, 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, LinkedContainerComponent, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, RESERVED_ALIAS_PREFIX, STATE_PSEUDO_CLASS, 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, composeVirtualTraits, computeRelative, containerHtmlTag, contextActionEffect, contextSlotKeys, countOverrides, dataSourcePlanToRequest, declaredInputNames, defaultMessageResolver, deriveSeoFromContent, describeSeoFields, effectiveTransitionId, excludeSelf, feelFor, getByPath, getSeoValue, hasHoverState, hasOwnTransition, 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, statePseudoClass, stripSuppressedProps, variableAliases, variableItemsFrom, withContextSlots, withDefaultHoverTransition, wrapInBreakpointQuery, wrapInMediaQuery };
2694
2757
  export type { BoundValueSpec, BreakpointDef, BuildRelatedFilterOptions, ContextActionEffect, ContextActionLike, ContextItemRef, ContextSlotDefault, ContextValueSpec, ContextWriteScope, DataSourceDomain, DataSourceEndpoints, DataSourceMode, DataSourcePlan, DataSourceQueryFilter, DataSourceRequest, DataSourceRequestSpec, DataSourceRouteFilter, DataSourceSpec, FormMessageResolver, GalleryBoundSource, GalleryDurations, GalleryEffect, GalleryEffectDescriptor, GalleryImage, GalleryLoopOptions, GalleryLoopScheduler, GalleryLoopState, GallerySlideState, IBuilderEventEmitter, InputMap, IoxContextSlot, IoxPipe, OverrideBreakpoint, PageStyleSettings, PageTransitionCategory, PageTransitionCategoryMeta, PageTransitionOptions, PageTransitionPreset, PipeArgSpec, PipeContext, PipeDescriptor, RelativeInputs, RelativeSourceKind, RelativeSourceSpec, ResolveSiblingsOptions, ResolvedPageTransition, ResponsiveLayer, ResponsiveOverrides, ResponsiveRuleSet, RouteContext, SeoDerivedContent, SeoFieldSource, SeoFieldState, SeoResolveContext, SetContextParams, SiblingsResult, StateStyleMap, StyleMap, StyleTreeNode, SwiperBreakpointMap, ToggleableAction, TransitionFeel, TransitionPhase, TransitionTimelineInput, ValueSource, VirtualTraitGroup };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectoriox/iox-ui",
3
- "version": "5.11.0",
3
+ "version": "5.12.0",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=20.0.0",
6
6
  "@angular/core": ">=20.0.0",
@@ -0,0 +1,271 @@
1
+ import {
2
+ SPEC_BUTTON_VARIANT_TO_STYLE, SPEC_DEFAULT_ELEMENT_STYLE, SPEC_ELEMENT_STYLES, SPEC_PALETTE_PRESETS,
3
+ } from './flavor-spec.generated';
4
+
5
+ /**
6
+ * Element styles — the author's "classes" (design-system plan §3b). A named look is an EXACT trait
7
+ * map written into a node; every value is a `var(--iox-…)` reference or a flavor-independent
8
+ * keyword, so a re-theme rewrites token rows and every node wearing the style follows. No renderer
9
+ * reads `elementStyle`: it records which look a node wears, for the panel row, Reset, adopt and AI.
10
+ *
11
+ * Works on two node shapes through one minimal interface, so the builder (a ComponentNode whose
12
+ * values live in `styleTraits[].traits[].default`) and the server-side composer (a plain serialized
13
+ * layout node with `styleProps`) share every function here.
14
+ */
15
+
16
+ export enum ElementStyleState {
17
+ Hover = 'hover',
18
+ Active = 'active',
19
+ Focus = 'focus',
20
+ }
21
+
22
+ export const ELEMENT_STYLE_STATES: ReadonlyArray<ElementStyleState> =
23
+ [ElementStyleState.Hover, ElementStyleState.Active, ElementStyleState.Focus];
24
+
25
+ export interface ElementStyle {
26
+ readonly id: string;
27
+ readonly label: string;
28
+ readonly group: string;
29
+ readonly appliesTo: ReadonlyArray<string>;
30
+ readonly base: Readonly<Record<string, string>>;
31
+ readonly hover?: Readonly<Record<string, string>>;
32
+ readonly active?: Readonly<Record<string, string>>;
33
+ readonly focus?: Readonly<Record<string, string>>;
34
+ }
35
+
36
+ export const ELEMENT_STYLES: ReadonlyArray<ElementStyle> = SPEC_ELEMENT_STYLES;
37
+
38
+ /** Legacy `Button.variant` → the element style it maps to (adopt, DS7). */
39
+ export const BUTTON_VARIANT_TO_STYLE: Readonly<Record<string, string>> = SPEC_BUTTON_VARIANT_TO_STYLE;
40
+
41
+ export interface PalettePreset {
42
+ readonly label: string;
43
+ readonly type: string;
44
+ readonly elementStyle: string;
45
+ readonly traits?: Readonly<Record<string, string>>;
46
+ readonly styleProps: Readonly<Record<string, string>>;
47
+ }
48
+
49
+ /** Palette entries that are a Container wearing an element style (Card · Panel · Navigation bar). */
50
+ export const PALETTE_PRESETS: ReadonlyArray<PalettePreset> = SPEC_PALETTE_PRESETS;
51
+
52
+ const BY_ID = new Map(ELEMENT_STYLES.map(s => [s.id, s]));
53
+
54
+ export function elementStyleById(id: string | null | undefined): ElementStyle | undefined {
55
+ return id ? BY_ID.get(id) : undefined;
56
+ }
57
+
58
+ /** The styles a node of `type` may wear — the Style panel's select. */
59
+ export function elementStylesFor(type: string): ElementStyle[] {
60
+ return ELEMENT_STYLES.filter(s => s.appliesTo.includes(type));
61
+ }
62
+
63
+ /** The minimal shape both a builder node and a serialized layout node satisfy. */
64
+ export interface ElementStyleTraitLike {
65
+ name: string;
66
+ default?: unknown;
67
+ }
68
+
69
+ export interface ElementStyleTarget {
70
+ type: string;
71
+ elementStyle?: string;
72
+ /** Builder shape: values live in `trait.default`. Wins when present. */
73
+ styleTraits?: ReadonlyArray<{ traits: ElementStyleTraitLike[] }>;
74
+ /** Serialized shape: a flat map. Created on first write when the target has no `styleTraits`. */
75
+ styleProps?: Record<string, unknown>;
76
+ stateStyles?: Partial<Record<string, Record<string, unknown>>>;
77
+ }
78
+
79
+ /** One trait an element style owns: a base trait, or a trait inside a pseudo-state. */
80
+ export interface OwnedTrait {
81
+ trait: string;
82
+ state?: ElementStyleState;
83
+ }
84
+
85
+ export enum OwnedTraitLink {
86
+ /** The node still holds the style's value. */
87
+ Linked = 'linked',
88
+ /** The author replaced it on this node. */
89
+ Changed = 'changed',
90
+ }
91
+
92
+ export interface OwnedTraitStatus extends OwnedTrait {
93
+ expected: string;
94
+ actual: unknown;
95
+ status: OwnedTraitLink;
96
+ }
97
+
98
+ export interface ApplyElementStyleResult {
99
+ applied: boolean;
100
+ /** Base traits the target has no slot for — a builder node silently drops these, so a spec
101
+ * asserts this list is empty for every style × every type it applies to. */
102
+ unknownTraits: string[];
103
+ }
104
+
105
+ export interface SwitchElementStyleResult {
106
+ applied: boolean;
107
+ /** Traits the old style owned whose value the author had changed — kept, never overwritten. */
108
+ kept: OwnedTrait[];
109
+ }
110
+
111
+ const usesTraits = (t: ElementStyleTarget): boolean => Array.isArray(t.styleTraits);
112
+
113
+ function findTrait(target: ElementStyleTarget, name: string): ElementStyleTraitLike | undefined {
114
+ for (const group of target.styleTraits ?? []) {
115
+ const hit = group.traits.find(tr => tr.name === name);
116
+ if (hit) return hit;
117
+ }
118
+ return undefined;
119
+ }
120
+
121
+ function readBase(target: ElementStyleTarget, name: string): unknown {
122
+ return usesTraits(target) ? findTrait(target, name)?.default : target.styleProps?.[name];
123
+ }
124
+
125
+ /** False when a builder node has no such trait — the value would be dropped. */
126
+ function writeBase(target: ElementStyleTarget, name: string, value: unknown): boolean {
127
+ if (usesTraits(target)) {
128
+ const trait = findTrait(target, name);
129
+ if (!trait) return false;
130
+ trait.default = value;
131
+ return true;
132
+ }
133
+ target.styleProps = target.styleProps ?? {};
134
+ if (value === undefined) delete target.styleProps[name];
135
+ else target.styleProps[name] = value;
136
+ return true;
137
+ }
138
+
139
+ function readState(target: ElementStyleTarget, state: ElementStyleState, name: string): unknown {
140
+ return target.stateStyles?.[state]?.[name];
141
+ }
142
+
143
+ function writeState(target: ElementStyleTarget, state: ElementStyleState, name: string, value: string): void {
144
+ target.stateStyles = target.stateStyles ?? {};
145
+ target.stateStyles[state] = { ...(target.stateStyles[state] ?? {}), [name]: value };
146
+ }
147
+
148
+ function deleteState(target: ElementStyleTarget, state: ElementStyleState, name: string): void {
149
+ const map = target.stateStyles?.[state];
150
+ if (!map) return;
151
+ const { [name]: _drop, ...rest } = map;
152
+ if (Object.keys(rest).length) target.stateStyles![state] = rest;
153
+ else delete target.stateStyles![state];
154
+ }
155
+
156
+ /** Every (state, trait, value) a style owns, base first. */
157
+ function ownedEntries(style: ElementStyle): Array<OwnedTrait & { value: string }> {
158
+ const out: Array<OwnedTrait & { value: string }> = [];
159
+ for (const [trait, value] of Object.entries(style.base)) out.push({ trait, value });
160
+ for (const state of ELEMENT_STYLE_STATES) {
161
+ for (const [trait, value] of Object.entries(style[state] ?? {})) out.push({ trait, state, value });
162
+ }
163
+ return out;
164
+ }
165
+
166
+ const sameOwned = (a: OwnedTrait, b: OwnedTrait) => a.trait === b.trait && a.state === b.state;
167
+
168
+ function writeEntries(target: ElementStyleTarget, style: ElementStyle, skip: OwnedTrait[] = []): string[] {
169
+ const unknown: string[] = [];
170
+ for (const entry of ownedEntries(style)) {
171
+ if (skip.some(s => sameOwned(s, entry))) continue;
172
+ if (entry.state) writeState(target, entry.state, entry.trait, entry.value);
173
+ else if (!writeBase(target, entry.trait, entry.value)) unknown.push(entry.trait);
174
+ }
175
+ target.elementStyle = style.id;
176
+ return unknown;
177
+ }
178
+
179
+ /** Write the style's exact base map and its hover/active/focus maps into the target. */
180
+ export function applyElementStyle(target: ElementStyleTarget, id: string): ApplyElementStyleResult {
181
+ const style = elementStyleById(id);
182
+ if (!style) return { applied: false, unknownTraits: [] };
183
+ return { applied: true, unknownTraits: writeEntries(target, style) };
184
+ }
185
+
186
+ /** `Heading[level=h3..h6]` → { type: 'Heading', prop: 'level', values: ['h3','h4','h5','h6'] }. */
187
+ interface DefaultRule {
188
+ type: string;
189
+ prop?: string;
190
+ values?: string[];
191
+ style: string;
192
+ }
193
+
194
+ const RULE_KEY = /^(\w+)(?:\[(\w+)=([^\]]+)\])?$/;
195
+ const RANGE = /^([a-z]*)(\d+)\.\.\1(\d+)$/;
196
+
197
+ function expandValues(raw: string): string[] {
198
+ const range = RANGE.exec(raw);
199
+ if (!range) return [raw];
200
+ const [, prefix, from, to] = range;
201
+ const out: string[] = [];
202
+ for (let n = Number(from); n <= Number(to); n++) out.push(`${prefix}${n}`);
203
+ return out;
204
+ }
205
+
206
+ const DEFAULT_RULES: ReadonlyArray<DefaultRule> = Object.entries(SPEC_DEFAULT_ELEMENT_STYLE).map(([key, style]) => {
207
+ const m = RULE_KEY.exec(key);
208
+ if (!m) throw new Error(`defaultElementStyle: unreadable key "${key}"`);
209
+ const [, type, prop, raw] = m;
210
+ return prop ? { type, prop, values: expandValues(raw), style } : { type, style };
211
+ });
212
+
213
+ /**
214
+ * The element style a NEW node of `type` is born wearing (the spec's `defaultElementStyle`), or null
215
+ * when the type has none. `traits` is the node's own trait values (props) — a Heading's default
216
+ * follows its `level`; a Heading with no level matches no rule.
217
+ */
218
+ export function defaultElementStyleFor(type: string, traits: Readonly<Record<string, unknown>> = {}): string | null {
219
+ for (const rule of DEFAULT_RULES) {
220
+ if (rule.type !== type) continue;
221
+ if (!rule.prop) return rule.style;
222
+ if (rule.values!.includes(String(traits[rule.prop]))) return rule.style;
223
+ }
224
+ return null;
225
+ }
226
+
227
+ /** Per owned trait: does the node still hold the style's value (linked) or the author's (changed)? */
228
+ export function ownedTraitStatus(target: ElementStyleTarget, id: string): OwnedTraitStatus[] {
229
+ const style = elementStyleById(id);
230
+ if (!style) return [];
231
+ return ownedEntries(style).map(({ trait, state, value }) => {
232
+ const actual = state ? readState(target, state, trait) : readBase(target, trait);
233
+ return {
234
+ trait, ...(state ? { state } : {}), expected: value, actual,
235
+ status: actual === value ? OwnedTraitLink.Linked : OwnedTraitLink.Changed,
236
+ };
237
+ });
238
+ }
239
+
240
+ /**
241
+ * Move a node from one style to another, rewriting ONLY what the old style owned:
242
+ * - an owned trait still holding the old style's value is released — a base trait goes back to
243
+ * `baseline[trait]` (the type's unstyled value; deleted when the baseline has none), a state trait
244
+ * is removed from its state map;
245
+ * - an owned trait the author changed is KEPT, and the new style does not overwrite it.
246
+ * Then the new style is written. Traits neither style owns are never touched.
247
+ */
248
+ export function switchElementStyle(
249
+ target: ElementStyleTarget,
250
+ fromId: string | null | undefined,
251
+ toId: string,
252
+ baseline: Readonly<Record<string, unknown>> = {},
253
+ ): SwitchElementStyleResult {
254
+ const to = elementStyleById(toId);
255
+ if (!to) return { applied: false, kept: [] };
256
+ const kept: OwnedTrait[] = [];
257
+ const from = elementStyleById(fromId);
258
+ if (from) {
259
+ for (const status of ownedTraitStatus(target, from.id)) {
260
+ const ref: OwnedTrait = status.state ? { trait: status.trait, state: status.state } : { trait: status.trait };
261
+ if (status.status === OwnedTraitLink.Changed) {
262
+ if (status.actual !== undefined) kept.push(ref);
263
+ continue;
264
+ }
265
+ if (ref.state) deleteState(target, ref.state, ref.trait);
266
+ else writeBase(target, ref.trait, baseline[ref.trait]);
267
+ }
268
+ }
269
+ writeEntries(target, to, kept);
270
+ return { applied: true, kept };
271
+ }
@@ -1,18 +1,11 @@
1
- import { styleKeyToKebab } from './css-names';
2
1
  import { DesignTokenGroup, DesignTokenTier, DesignTokenType } from './design-token.model';
3
2
 
4
3
  /**
5
- * Flavors (flavors-and-theming-plan.md, F1): the design language a studio launches with,
6
- * expressed as data. THE invariant — a flavor never writes a literal into a node; it writes
7
- * tokens, and element defaults that REFERENCE tokens. To that end a pack's `defaults` map
8
- * holds TOKEN NAMES, not CSS values: the resolver builds `var(--iox-<name>, <packValue>)`,
9
- * so smuggling a literal in is impossible by construction, and the validator only has to
10
- * check that every referenced token exists.
11
- *
12
- * The fallback is the bootstrap: until F2 seeds the org's `designtokens` rows, no `--iox-*`
13
- * var is defined anywhere, and `var(--iox-radius-control, 4px)` must still paint identically
14
- * in the builder canvas and the SSR page. Once tokens ARE seeded (or re-themed), the var wins
15
- * over the fallback everywhere — which is exactly the §3 propagation story.
4
+ * Flavors v1 (flavors-and-theming-plan.md, F1–F2): a pack is its TOKENS plus the accent hue
5
+ * opinion the brand derivation reads. The per-type `defaults` map and its resolvers are gone
6
+ * (design-system plan §3e, DS2): a new element is born wearing an ELEMENT STYLE
7
+ * (`element-styles.ts`), which references tokens and is flavor-independent. Swiss survives only
8
+ * until DS4 migrates orgs to Crisp.
16
9
  */
17
10
 
18
11
  export interface FlavorTokenDef {
@@ -32,82 +25,24 @@ export interface FlavorPack {
32
25
  label: string;
33
26
  blurb: string;
34
27
  tokens: FlavorTokenDef[];
35
- /** type → styleTrait → TOKEN NAME (never a CSS value — see the module header). */
36
- defaults: Record<string, Record<string, string>>;
37
28
  /** F2: the accent's hue rotation (degrees in OKLab) from the brand seed — a pack opinion. */
38
29
  accentHueDeg?: number;
39
30
  }
40
31
 
41
- /** trait → `var(--iox-<token>, <packValue>)` for one node type. Unknown type → {}. */
42
- export function resolveFlavorDefaults(
43
- type: string,
44
- pack: FlavorPack | null | undefined,
45
- ): Record<string, string> {
46
- const map = pack?.defaults?.[type];
47
- if (!map) return {};
48
- const byName = new Map((pack!.tokens ?? []).map(t => [t.name, t.value]));
49
- const out: Record<string, string> = {};
50
- for (const [trait, tokenName] of Object.entries(map)) {
51
- const fallback = byName.get(tokenName);
52
- if (fallback === undefined) continue; // validator fails the build; runtime stays safe
53
- out[trait] = `var(--iox-${tokenName}, ${fallback})`;
54
- }
55
- return out;
56
- }
57
-
58
- /**
59
- * The ENGINE's half of the cascade: kebab `prop: var(…)` declarations for every pack default
60
- * the node's persisted styleProps does NOT set. Appended beside renderDefaultDeclarations —
61
- * same guard-on-undefined discipline, so a node saved post-F1 (var already serialised via the
62
- * builder's trait defaults) is untouched, and a legacy node picks the pack default up at
63
- * render time. Node styleProps always wins.
64
- */
65
- export function flavorDefaultDeclarations(
66
- type: string,
67
- styleProps: Record<string, any> | null | undefined,
68
- pack: FlavorPack | null | undefined,
69
- ): string[] {
70
- const sp = styleProps ?? {};
71
- const out: string[] = [];
72
- for (const [trait, value] of Object.entries(resolveFlavorDefaults(type, pack))) {
73
- if (sp[trait] === undefined) out.push(`${styleKeyToKebab(trait)}: ${value}`);
74
- }
75
- return out;
76
- }
77
-
78
- /**
79
- * The CI gate (flavors plan §10 C4): a pack may only name types and traits that exist in the
80
- * live component registry, and tokens it defines itself. Returns human-readable errors; a
81
- * consumer spec feeds it the REAL registry and asserts [].
82
- */
83
- export function validateFlavorPack(
84
- pack: FlavorPack,
85
- knownTraitsByType: Record<string, ReadonlySet<string> | string[]>,
86
- ): string[] {
32
+ /** A pack's own sanity: every token row names itself and carries a value. Returns errors. */
33
+ export function validateFlavorPack(pack: FlavorPack): string[] {
87
34
  const errors: string[] = [];
88
- const tokenNames = new Set((pack.tokens ?? []).map(t => t.name));
89
35
  for (const t of pack.tokens ?? []) {
90
36
  if (!t.name) errors.push(`${pack.id}: token with empty name`);
91
37
  if (!t.value) errors.push(`${pack.id}: token "${t.name}" has no value`);
92
38
  }
93
- for (const [type, traits] of Object.entries(pack.defaults ?? {})) {
94
- const known = knownTraitsByType[type];
95
- if (!known) { errors.push(`${pack.id}: unknown component type "${type}"`); continue; }
96
- const knownSet = known instanceof Set ? known : new Set(known);
97
- for (const [trait, tokenName] of Object.entries(traits)) {
98
- if (!knownSet.has(trait)) errors.push(`${pack.id}: "${type}" has no style trait "${trait}"`);
99
- if (!tokenNames.has(tokenName)) errors.push(`${pack.id}: "${type}.${trait}" references undefined token "${tokenName}"`);
100
- if (/[:(]|^#|\d/.test(tokenName)) errors.push(`${pack.id}: "${type}.${trait}" must name a token, not a value ("${tokenName}")`);
101
- }
102
- }
103
39
  return errors;
104
40
  }
105
41
 
106
42
  /**
107
- * Swiss — the default pack (flavors plan §8: "the flavor that can never embarrass anyone").
108
- * Deliberately SMALL at F1: radius + control padding on the clickables, surface radius on Card.
109
- * Colour, type and motion arrive with the brand step (F2) — a colour default here would repaint
110
- * legacy pages before anyone chose anything.
43
+ * Swiss — the F1 pack: four tokens (control/surface radius, control/stack spacing) and the hue
44
+ * opinion F2's brand step reads. Kept until DS4 migrates `flavorId: swiss` orgs to Crisp; its four
45
+ * rows are the spec's `legacyAliases`.
111
46
  */
112
47
  export const SWISS_FLAVOR: FlavorPack = {
113
48
  id: 'swiss',
@@ -120,18 +55,6 @@ export const SWISS_FLAVOR: FlavorPack = {
120
55
  { name: 'space-control', value: '10px 18px', type: 'spacing', group: 'space' },
121
56
  { name: 'space-stack', value: '12px', type: 'spacing', group: 'space' },
122
57
  ],
123
- defaults: {
124
- Button: { borderRadius: 'radius-control', padding: 'space-control' },
125
- ButtonBlock: { borderRadius: 'radius-control', padding: 'space-control' },
126
- Card: { borderRadius: 'radius-surface' },
127
- },
128
- // F2: Swiss turns the seed toward a cool counterpoint. NOTE deliberately: the brand tokens
129
- // deriveBrandTokens() emits are SEEDED per org — they do not join `defaults` here, because a
130
- // colour default repaints legacy nodes whose saved styleProps lack the key (the engine fills
131
- // missing keys from config defaults). Coloured element defaults are a Swiss v2 upgrade
132
- // decision (§10 C2), taken with the picker in front of the author — never a side effect.
58
+ // F2: Swiss turns the seed toward a cool counterpoint.
133
59
  accentHueDeg: 150,
134
60
  };
135
-
136
- /** What both renderers use until F2 lets an org choose. */
137
- export const DEFAULT_FLAVOR: FlavorPack = SWISS_FLAVOR;
@@ -13,6 +13,7 @@ export * from './design-token.model';
13
13
  export * from './css-names';
14
14
  export * from './token-css';
15
15
  export * from './flavor';
16
+ export * from './element-styles';
16
17
  export * from './brand';
17
18
  export * from './flavor-tokens';
18
19
  export * from './flavor-seed';
@@ -1,3 +1,4 @@
1
+ import { DesignMode } from './design-token.model';
1
2
  import { SPEC_A11Y, SPEC_SCHEMES } from './flavor-spec.generated';
2
3
 
3
4
  /**
@@ -106,15 +107,18 @@ export function tokensToDarkVarMap(tokens: ReadonlyArray<DesignTokenLike> | null
106
107
  const TOKEN_REF = /var\(\s*--iox-([\w-]+)\s*(?:,([^()]*))?\)/g;
107
108
  const PLACEHOLDER = /\{\{\s*([\w-]+)\s*\}\}/g;
108
109
 
109
- type Mode = 'light' | 'dark';
110
+ /** A compiled colour mode — the contract's modes minus `auto`, which is a compile CHOICE, not values. */
111
+ export type ResolvedMode = Exclude<DesignMode, 'auto'>;
112
+ const LIGHT: ResolvedMode = 'light';
113
+ const DARK: ResolvedMode = 'dark';
110
114
 
111
- const modeValue = (t: DesignTokenLike, mode: Mode): string => (mode === 'dark' ? darkOf(t) ?? t.value : t.value);
115
+ const modeValue = (t: DesignTokenLike, mode: ResolvedMode): string => (mode === DARK ? darkOf(t) ?? t.value : t.value);
112
116
 
113
117
  /** Every token's ROOT value in one mode, with `var(--iox-…)` references resolved against the same
114
118
  * set (recursively; a reference cycle or an unknown token is left as written). */
115
119
  export function resolveTokenValues(
116
120
  tokens: ReadonlyArray<DesignTokenLike> | null | undefined,
117
- mode: Mode = 'light',
121
+ mode: ResolvedMode = LIGHT,
118
122
  ): Record<string, string> {
119
123
  const raw = new Map(usableTokens(tokens).map(t => [t.name, modeValue(t, mode)]));
120
124
  const out: Record<string, string> = {};
@@ -133,6 +137,21 @@ export function resolveTokenValues(
133
137
  return out;
134
138
  }
135
139
 
140
+ /**
141
+ * `value` with every `var(--iox-…)` replaced by that token's resolved root value in `mode` — what
142
+ * the Style panel's Detach writes, so the node keeps exactly what it showed as a literal. An unknown
143
+ * token falls back to its declared fallback, else stays as written.
144
+ */
145
+ export function resolveTokenReferences(
146
+ value: string,
147
+ tokens: ReadonlyArray<DesignTokenLike> | null | undefined,
148
+ mode: ResolvedMode = LIGHT,
149
+ ): string {
150
+ const resolved = resolveTokenValues(tokens, mode);
151
+ return value.replace(TOKEN_REF, (ref, name: string, fallback?: string) =>
152
+ resolved[name] ?? (fallback !== undefined ? fallback.trim() : ref));
153
+ }
154
+
136
155
  /** A scheme value with its `{{token}}` placeholders replaced by resolved root values — or null
137
156
  * when a placeholder names a token the set does not have (the declaration is then omitted). */
138
157
  function substitute(template: string, resolved: Readonly<Record<string, string>>): string | null {
@@ -167,7 +186,7 @@ const hasPlaceholder = (value: string) => value.includes('{{');
167
186
  export function compileSchemeCss(tokens: ReadonlyArray<DesignTokenLike> | null | undefined, scope?: string): string {
168
187
  const rows = usableTokens(tokens);
169
188
  if (!rows.length) return '';
170
- const resolved = resolveTokenValues(rows, 'light');
189
+ const resolved = resolveTokenValues(rows, LIGHT);
171
190
  return nonDefaultSchemes().map(([id, set]) => rule(schemeSelector(id, scope), [
172
191
  ...schemeEntries(set, resolved),
173
192
  ...rows.filter(t => !(t.name in set) && t.value.includes(REFERENCE))
@@ -178,8 +197,8 @@ export function compileSchemeCss(tokens: ReadonlyArray<DesignTokenLike> | null |
178
197
  /** The dark twin of both scheme rules: a re-declared token with a dark value would otherwise pin
179
198
  * the LIGHT value inside the section, and a `{{token}}` swap was resolved from LIGHT values. */
180
199
  function darkSchemeRules(rows: ReadonlyArray<DesignTokenLike>, scope: string | undefined): string[] {
181
- const light = resolveTokenValues(rows, 'light');
182
- const dark = resolveTokenValues(rows, 'dark');
200
+ const light = resolveTokenValues(rows, LIGHT);
201
+ const dark = resolveTokenValues(rows, DARK);
183
202
  const redeclared = rows.filter(t => {
184
203
  const d = darkOf(t);
185
204
  return d !== null && (t.value.includes(REFERENCE) || d.includes(REFERENCE));