@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/design-core/index.d.ts +122 -36
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +217 -76
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui.mjs +152 -20
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/index.d.ts +67 -4
- package/package.json +1 -1
- package/src/design-core/element-styles.ts +271 -0
- package/src/design-core/flavor.ts +11 -88
- package/src/design-core/public-api.ts +1 -0
- package/src/design-core/token-css.ts +25 -6
- package/styles/iox-forms.css +56 -5
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
|
-
/**
|
|
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
|
@@ -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):
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
/**
|
|
42
|
-
export function
|
|
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
|
|
108
|
-
*
|
|
109
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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,
|
|
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,
|
|
182
|
-
const dark = resolveTokenValues(rows,
|
|
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));
|