@vectoriox/iox-ui 5.32.1 → 5.34.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/composite-core/index.d.ts +4 -1
- package/design-core/index.d.ts +59 -5
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs +9 -8
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +740 -604
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +25 -51
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui.mjs +42 -1
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/index.d.ts +16 -1
- package/package.json +1 -1
- package/src/composite-core/badge.ts +4 -3
- package/src/composite-core/card.ts +4 -4
- package/src/design-core/contrast-audit.ts +50 -1
- package/src/design-core/flavor-spec.generated.ts +20 -0
- package/src/design-core/node-classes.ts +49 -1
- package/src/design-core/token-css.ts +39 -11
- package/src/generation-core/node-baselines.generated.ts +25 -51
package/index.d.ts
CHANGED
|
@@ -1592,6 +1592,21 @@ interface VirtualTraitGroup {
|
|
|
1592
1592
|
* lines with an ellipsis. It composes into the four declarations a line clamp takes; anything else
|
|
1593
1593
|
* (`none`, empty, 0) clamps nothing. */
|
|
1594
1594
|
declare const MAX_LINES_KEY = "maxLines";
|
|
1595
|
+
/** The Text Direction trait: `ltr` / `rtl` is the author's choice, unset (or empty) follows the
|
|
1596
|
+
* parent — `direction` is inherited, so a node with no direction of its own follows an RTL section
|
|
1597
|
+
* or `<html dir="rtl">` (vectoriox/iox-ui#14). */
|
|
1598
|
+
declare const TEXT_DIRECTION_KEY = "textDirection";
|
|
1599
|
+
/** Where the trait used to live. Until iox-ui#14 its default `ltr` was serialised onto EVERY node,
|
|
1600
|
+
* so a stored `direction: ltr` in a node's own styleProps records no choice and compiles nothing;
|
|
1601
|
+
* a stored `rtl` was always chosen and still compiles. In a sparse layer (a state, a breakpoint)
|
|
1602
|
+
* only an edit is stored, so there `ltr` too is the author's. */
|
|
1603
|
+
declare const LEGACY_DIRECTION_KEY = "direction";
|
|
1604
|
+
/**
|
|
1605
|
+
* A saved node's own style map with its legacy `direction` moved onto the Text Direction trait —
|
|
1606
|
+
* `rtl` carried over, the serialised default `ltr` released — so the builder's panel shows what the
|
|
1607
|
+
* node renders. Pure; returns the map unchanged (same reference) when there is nothing to move.
|
|
1608
|
+
*/
|
|
1609
|
+
declare function migrateLegacyDirection(styleProps: Record<string, any>): Record<string, any>;
|
|
1595
1610
|
declare function isGradientPaint(value: unknown): boolean;
|
|
1596
1611
|
/** The whole-value backdrop trait (DS2). A plain key — it is copied, not stripped — but it feeds
|
|
1597
1612
|
* the backdrop group's output, so a breakpoint layer that sets it must count as touching it. */
|
|
@@ -3137,5 +3152,5 @@ declare class GalleryLoop {
|
|
|
3137
3152
|
private emit;
|
|
3138
3153
|
}
|
|
3139
3154
|
|
|
3140
|
-
export { A11yAttribute, A11yTrait, ACCESSIBLE_NAME_TYPES, AOSService, ARIA_CURRENT_PAGE, BACKDROP_TWIN_KEY, BACKDROP_WHOLE_KEY, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, COMPONENT_STATE_SELECTOR, CONTAINER_HTML_TAGS, CONTEXT_ACTION_TYPES, CURRENT_LINK_TYPES, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ComponentState, ContainerComponent, ContextStore, DECORATIVE_TYPES, 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, DIALOG_CLOSE_ABOVE_ATTRIBUTE, DIALOG_NODE_TYPE, DeclarationContext, ENTRANCE_ACTION_TYPES, EXIT_ACTION_TYPES, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, HOVER_DEFAULT_TRANSITION, HOVER_STATES, INTERACTION_HIDDEN_ATTRIBUTE, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxDialogComponent, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxSubmitButtonComponent, IoxUiModule, LEGACY_HOVER_TRANSITION, LinkedContainerComponent, MANAGED_A11Y_ATTRIBUTES, MAX_LINES_KEY, PAGE_STYLE_TOKENS, PAGE_TOKEN_FALLBACKS, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, PageStyleField, PageStyleSource, REDUCED_MOTION_QUERY, RESERVED_ALIAS_PREFIX, STATE_PSEUDO_CLASS, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_A11Y_OPTIONS, SWIPER_RESPONSIVE_TRAITS, SectionComponent, SwiperModuleName, TRANSITION_FEELS, TextBlockComponent, VALIDATION_STATES, VALIDATION_STATE_CLASS, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, VisibilityActionType, activeActions, applyBindingPipes, applyFeel, breakpointDef, breakpointForWidth, breakpointQueryFor, buildDataSourceFilter, buildInteractionKeyframes, buildPartSelector, buildRelatedFilter, buildSwiperBreakpoints, buildTargetedStateSelector, buildTransitionTimeline, cascadeChain, clearInputOverride, clearOverride, closeAboveMediaQuery, collectContextSlotKeys, collectReferencedAliases, compileDeclarations, compilePageStyle, componentStatesFor, composeVirtualTraits, computeRelative, containerHtmlTag, contextActionEffect, contextSlotKeys, countOverrides, dataSourcePlanToRequest, declaredInputNames, decomposeBackdropFilter, defaultHoverTransition, defaultMessageResolver, deriveSeoFromContent, describePageStyle, describeSeoFields, dialogCloseAboveMinWidth, effectiveActionType, effectiveTransitionId, entrancePreStateStyle, excludeSelf, feelFor, finalKeyframe, getByPath, getSeoValue, hasHoverState, hasOwnTransition, initialSlotValues, isActionEnabled, isContextAction, isCurrentLink, isExternalOrFragment, isGradientPaint, isInputOverriddenAt, isOverriddenAt, isReservedAlias, isValidSlotKey, isWholeBackdropSet, keyframePropertyName, keyframeStyleEntries, legacyEnterToTransition, matchRouteParams, mediaQueryFor, mergeSwiperBreakpoints, motionAllowed, nearestCommonAncestorId, needsAccessibleName, nodeA11yAttributes, nodeCssId, nodeTraitValue, nodeUsesContext, normalizeAppPath, normalizeCollectionResult, normalizeGalleryImage, normalizeIconClass, parentBreakpoint, parseUrlList, partClassName, planDataSource, planResponsiveRules, prefersReducedMotion, referencedDataSources, referencedDataSourcesDeep, renderDefaultDeclarations, resetPageStyleField, resetSeoField, resolveBoundValue, resolveContextValue, resolveDerivedInto, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolvePageTransition, resolvePath, resolveRelativeInto, resolveRepeaterItems, resolveSeo, resolveSiblings, resolveSingleItemId, resolveStateStyles, resolveStyleProps, resolveToggleVisibility, resolveValueChain, rewriteViewportUnits, safeSetInput, serializeA11yAttributes, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, stateNamesIn, statePseudoClass, stateSelectorSuffix, stripSuppressedProps, swiperAutoplayAllowed, swiperModuleNames, syncNodeA11yAttributes, variableAliases, variableItemsFrom, withContextSlots, withDefaultHoverTransition, wrapInBreakpointQuery, wrapInMediaQuery };
|
|
3155
|
+
export { A11yAttribute, A11yTrait, ACCESSIBLE_NAME_TYPES, AOSService, ARIA_CURRENT_PAGE, BACKDROP_TWIN_KEY, BACKDROP_WHOLE_KEY, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, COMPONENT_STATE_SELECTOR, CONTAINER_HTML_TAGS, CONTEXT_ACTION_TYPES, CURRENT_LINK_TYPES, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ComponentState, ContainerComponent, ContextStore, DECORATIVE_TYPES, 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, DIALOG_CLOSE_ABOVE_ATTRIBUTE, DIALOG_NODE_TYPE, DeclarationContext, ENTRANCE_ACTION_TYPES, EXIT_ACTION_TYPES, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, HOVER_DEFAULT_TRANSITION, HOVER_STATES, INTERACTION_HIDDEN_ATTRIBUTE, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxDialogComponent, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxSubmitButtonComponent, IoxUiModule, LEGACY_DIRECTION_KEY, LEGACY_HOVER_TRANSITION, LinkedContainerComponent, MANAGED_A11Y_ATTRIBUTES, MAX_LINES_KEY, PAGE_STYLE_TOKENS, PAGE_TOKEN_FALLBACKS, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, PageStyleField, PageStyleSource, REDUCED_MOTION_QUERY, RESERVED_ALIAS_PREFIX, STATE_PSEUDO_CLASS, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_A11Y_OPTIONS, SWIPER_RESPONSIVE_TRAITS, SectionComponent, SwiperModuleName, TEXT_DIRECTION_KEY, TRANSITION_FEELS, TextBlockComponent, VALIDATION_STATES, VALIDATION_STATE_CLASS, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, VisibilityActionType, activeActions, applyBindingPipes, applyFeel, breakpointDef, breakpointForWidth, breakpointQueryFor, buildDataSourceFilter, buildInteractionKeyframes, buildPartSelector, buildRelatedFilter, buildSwiperBreakpoints, buildTargetedStateSelector, buildTransitionTimeline, cascadeChain, clearInputOverride, clearOverride, closeAboveMediaQuery, collectContextSlotKeys, collectReferencedAliases, compileDeclarations, compilePageStyle, componentStatesFor, composeVirtualTraits, computeRelative, containerHtmlTag, contextActionEffect, contextSlotKeys, countOverrides, dataSourcePlanToRequest, declaredInputNames, decomposeBackdropFilter, defaultHoverTransition, defaultMessageResolver, deriveSeoFromContent, describePageStyle, describeSeoFields, dialogCloseAboveMinWidth, effectiveActionType, effectiveTransitionId, entrancePreStateStyle, excludeSelf, feelFor, finalKeyframe, getByPath, getSeoValue, hasHoverState, hasOwnTransition, initialSlotValues, isActionEnabled, isContextAction, isCurrentLink, isExternalOrFragment, isGradientPaint, isInputOverriddenAt, isOverriddenAt, isReservedAlias, isValidSlotKey, isWholeBackdropSet, keyframePropertyName, keyframeStyleEntries, legacyEnterToTransition, matchRouteParams, mediaQueryFor, mergeSwiperBreakpoints, migrateLegacyDirection, motionAllowed, nearestCommonAncestorId, needsAccessibleName, nodeA11yAttributes, nodeCssId, nodeTraitValue, nodeUsesContext, normalizeAppPath, normalizeCollectionResult, normalizeGalleryImage, normalizeIconClass, parentBreakpoint, parseUrlList, partClassName, planDataSource, planResponsiveRules, prefersReducedMotion, referencedDataSources, referencedDataSourcesDeep, renderDefaultDeclarations, resetPageStyleField, resetSeoField, resolveBoundValue, resolveContextValue, resolveDerivedInto, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolvePageTransition, resolvePath, resolveRelativeInto, resolveRepeaterItems, resolveSeo, resolveSiblings, resolveSingleItemId, resolveStateStyles, resolveStyleProps, resolveToggleVisibility, resolveValueChain, rewriteViewportUnits, safeSetInput, serializeA11yAttributes, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, stateNamesIn, statePseudoClass, stateSelectorSuffix, stripSuppressedProps, swiperAutoplayAllowed, swiperModuleNames, syncNodeA11yAttributes, variableAliases, variableItemsFrom, withContextSlots, withDefaultHoverTransition, wrapInBreakpointQuery, wrapInMediaQuery };
|
|
3141
3156
|
export type { A11yNodeSource, BoundValueSpec, BreakpointDef, BuildRelatedFilterOptions, ContextActionEffect, ContextActionLike, ContextItemRef, ContextSlotDefault, ContextValueSpec, ContextWriteScope, CurrentLinkOptions, DataSourceDomain, DataSourceEndpoints, DataSourceMode, DataSourcePlan, DataSourceQueryFilter, DataSourceRequest, DataSourceRequestSpec, DataSourceRouteFilter, DataSourceSpec, FormMessageResolver, GalleryBoundSource, GalleryDurations, GalleryEffect, GalleryEffectDescriptor, GalleryImage, GalleryLoopOptions, GalleryLoopScheduler, GalleryLoopState, GallerySlideState, IBuilderEventEmitter, InputMap, InteractionKeyframe, IoxContextSlot, IoxPipe, MatchMediaHost, NodeA11yContext, OverrideBreakpoint, PageStyleFieldState, PageStyleOptions, 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, SwiperModuleFlags, ToggleableAction, TransitionFeel, TransitionPhase, TransitionTimelineInput, ValueSource, VirtualTraitGroup };
|
package/package.json
CHANGED
|
@@ -66,8 +66,9 @@ const PILL_BASE: Readonly<Record<string, string>> = {
|
|
|
66
66
|
};
|
|
67
67
|
const PILL_ICON: Readonly<Record<string, string>> = { fontSize: '1em' };
|
|
68
68
|
const PILL_WORDS: Readonly<Record<string, string>> = { width: 'auto', minHeight: 'auto', padding: '0px 0px 0px 0px', margin: '0px 0px 0px 0px' };
|
|
69
|
-
/** The pill's words
|
|
70
|
-
|
|
69
|
+
/** The pill's words wear the pill's OWN label style (Card v2.1): its type from the badge tokens, its leading,
|
|
70
|
+
* tracking, case and ink inherited from the pill — never the button's label style. */
|
|
71
|
+
export const PILL_LABEL_STYLE = 'badge-label';
|
|
71
72
|
|
|
72
73
|
export interface PillOptions {
|
|
73
74
|
text?: string;
|
|
@@ -81,7 +82,7 @@ function buildPill(create: CompositeNodeFactory, elementStyle: string, text: str
|
|
|
81
82
|
writeStyles(pill, PILL_BASE);
|
|
82
83
|
const glyph = create({ key: `${key}-icon`, type: 'Icon', props: { iconClass: icon }, elementStyle: 'icon' });
|
|
83
84
|
writeStyles(glyph, PILL_ICON);
|
|
84
|
-
const words = create({ key: `${key}-text`, type: 'Text', props: { content: text, tag: 'span' }, elementStyle:
|
|
85
|
+
const words = create({ key: `${key}-text`, type: 'Text', props: { content: text, tag: 'span' }, elementStyle: PILL_LABEL_STYLE });
|
|
85
86
|
writeStyles(words, PILL_WORDS);
|
|
86
87
|
pill.children = [glyph, words];
|
|
87
88
|
return pill;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { elementStyleById, LinkType, switchElementStyle } from '@vectoriox/iox-ui/design-core';
|
|
1
|
+
import { COLOR_SCHEME_TRAIT, elementStyleById, LinkType, setColorScheme, switchElementStyle } from '@vectoriox/iox-ui/design-core';
|
|
2
2
|
|
|
3
3
|
import { BadgeTone, buildBadge } from './badge';
|
|
4
4
|
import {
|
|
@@ -119,7 +119,6 @@ enum Scheme {
|
|
|
119
119
|
Inverse = 'inverse',
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
const COLOR_SCHEME = 'colorScheme';
|
|
123
122
|
const LINK_TYPE = 'linkType';
|
|
124
123
|
const SAVE_ICON = 'ph ph-bookmark-simple';
|
|
125
124
|
|
|
@@ -336,8 +335,9 @@ function placeBadge(card: CompositeNode, badge: CompositeNode, onMedia: boolean)
|
|
|
336
335
|
writeStyles(badge, { position: overMedia ? 'absolute' : 'static' });
|
|
337
336
|
}
|
|
338
337
|
|
|
338
|
+
/** Through design-core's ONE scheme writer — the same setting Section colours edits everywhere. */
|
|
339
339
|
function writeScheme(node: CompositeNode, scheme: Scheme): void {
|
|
340
|
-
|
|
340
|
+
setColorScheme(node, scheme);
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
/** Write one role's layout values from a spec (base + layers). */
|
|
@@ -424,7 +424,7 @@ function matches(card: CompositeNode, spec: CardLayoutSpec): boolean {
|
|
|
424
424
|
const content = findByRole(card, CardRole.Content);
|
|
425
425
|
if (content) {
|
|
426
426
|
if (content.elementStyle !== spec.contentStyle || !holds(content, spec.content)) return false;
|
|
427
|
-
if (!same(readProp(content,
|
|
427
|
+
if (!same(readProp(content, COLOR_SCHEME_TRAIT) ?? Scheme.Default, spec.contentScheme)) return false;
|
|
428
428
|
}
|
|
429
429
|
const title = findByRole(card, CardRole.Title);
|
|
430
430
|
if (title && !holds(title, spec.title)) return false;
|
|
@@ -3,6 +3,7 @@ import { WCAG_AA_UI } from './brand-neutrals';
|
|
|
3
3
|
import { compositeOver, paintColours, parseCssColour, quantizeRgb, Rgba, ringColour } from './colour-eval';
|
|
4
4
|
import { ElementStyle, ELEMENT_STYLES } from './element-styles';
|
|
5
5
|
import { SPEC_CONTRAST_AUDIT, SPEC_INK_SCOPES, SPEC_SCHEMES } from './flavor-spec.generated';
|
|
6
|
+
import { SCHEME_INK } from './node-classes';
|
|
6
7
|
import { DesignTokenLike, ResolvedMode, resolveSchemeTokenValues } from './token-css';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -19,7 +20,10 @@ import { DesignTokenLike, ResolvedMode, resolveSchemeTokenValues } from './token
|
|
|
19
20
|
* its focus ring; the form parts the field baseline paints (`contrastAudit.formParts`); the form's
|
|
20
21
|
* Success / Error notices, in every scheme and container;
|
|
21
22
|
* - a Badge's or Chip's icon through the same `inkScopes`, and text over an image (Card v2's "Image behind
|
|
22
|
-
* text"): the block's ink over its scrim composited on the WORST-CASE image (`contrastAudit.scrims`)
|
|
23
|
+
* text"): the block's ink over its scrim composited on the WORST-CASE image (`contrastAudit.scrims`);
|
|
24
|
+
* - a container that WEARS a scheme inside a section of any scheme (a Featured card on a plain page): the
|
|
25
|
+
* text it holds inherits the scheme's ink (`SCHEME_INK`, iox-ui#15) on the inner scheme's fill, composited
|
|
26
|
+
* over the OUTER section — the pairing the token pairs cannot see.
|
|
23
27
|
*
|
|
24
28
|
* WCAG 2: 4.5:1 for text, 3:1 for glyphs, UI boundaries and focus indicators (1.4.11), judged on 8-bit
|
|
25
29
|
* colours. The same audit gates the contract (`colour.mjs auditContrast`, every flavor × tint × a seed
|
|
@@ -65,6 +69,8 @@ const GLYPH_GROUP = 'Media';
|
|
|
65
69
|
const FORMS_GROUP = 'Forms';
|
|
66
70
|
const DEFAULT_SCHEME = 'default';
|
|
67
71
|
const SECTION = 'section';
|
|
72
|
+
/** The element a scheme'd container's running text is reported as (iox-ui#15). */
|
|
73
|
+
export const INHERITED_TEXT = 'inherited text';
|
|
68
74
|
const FIELD = 'field';
|
|
69
75
|
const WHITE: Rgba = { r: 1, g: 1, b: 1, a: 1 };
|
|
70
76
|
const SINGLE_REF = /^var\(--iox-([\w-]+)\)$/;
|
|
@@ -187,10 +193,53 @@ export function auditDesignContrast(rows: ReadonlyArray<DesignTokenLike>, mode:
|
|
|
187
193
|
}
|
|
188
194
|
}
|
|
189
195
|
}
|
|
196
|
+
findings.push(...schemedContainerFindings(rows, mode, section, containers.filter((c): c is ElementStyle => !!c)));
|
|
190
197
|
for (const scrim of SCRIMS) findings.push(...scrimFindings(rows, mode, scrim, byId.get(scrim.style)));
|
|
191
198
|
return findings;
|
|
192
199
|
}
|
|
193
200
|
|
|
201
|
+
/** What a section in `values` paints under its content: its own fill over the page, or the page. */
|
|
202
|
+
function sectionGround(values: Values): Rgba[] {
|
|
203
|
+
const pageInk = parseCssColour(values['text']);
|
|
204
|
+
const page = paintColours(values['page-bg'], pageInk);
|
|
205
|
+
const sectionPaint = paintColours(values['section-bg'], pageInk);
|
|
206
|
+
return opaque(sectionPaint) ? sectionPaint.map(c => (c.a < 1 ? compositeOver(c, page[0] ?? WHITE) : c)) : page;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* A container that wears a NON-default scheme, inside a section of every scheme (vectoriox/iox-ui#15 — the
|
|
211
|
+
* Featured card is a Card in Brand on a Default page). Its tokens are its own scheme's laid over the outer
|
|
212
|
+
* section's (what it does not set, it inherits); a translucent fill composites over the OUTER section; the running text it holds (`body`, `lead`, a label: `color: inherit`)
|
|
213
|
+
* reads the scheme's ink, which is what the scheme-ink rule and `withSchemeInk` make CSS compute.
|
|
214
|
+
*/
|
|
215
|
+
function schemedContainerFindings(rows: ReadonlyArray<DesignTokenLike>, mode: ResolvedMode, section: StyleMap,
|
|
216
|
+
containers: ReadonlyArray<ElementStyle>): ContrastFinding[] {
|
|
217
|
+
const out: ContrastFinding[] = [];
|
|
218
|
+
for (const outer of SCHEME_IDS) {
|
|
219
|
+
const ground = sectionGround(resolveSchemeTokenValues(rows, mode, outer));
|
|
220
|
+
for (const inner of SCHEME_IDS.filter(id => id !== DEFAULT_SCHEME)) {
|
|
221
|
+
const values = resolveSchemeTokenValues(rows, mode, [outer, inner]);
|
|
222
|
+
const schemeInk = parseCssColour(resolveIn(values, SCHEME_INK), parseCssColour(resolveIn(values, section['color'])));
|
|
223
|
+
for (const box of containers) {
|
|
224
|
+
const style = ownStyle(box);
|
|
225
|
+
const bgs = overAll(paintColours(resolveIn(values, style['backgroundColor']), schemeInk), ground);
|
|
226
|
+
const ink = style['color'] && style['color'] !== 'inherit' ? parseCssColour(resolveIn(values, style['color']), schemeInk) : schemeInk;
|
|
227
|
+
if (!ink) continue;
|
|
228
|
+
for (const g of bgs) {
|
|
229
|
+
const bg = quantizeRgb(g);
|
|
230
|
+
const f = quantizeRgb(ink.a < 1 ? compositeOver(ink, bg) : ink);
|
|
231
|
+
const ratio = wcagContrastRatio(f, bg);
|
|
232
|
+
out.push({
|
|
233
|
+
scheme: inner, container: `${box.id} in a ${outer} section`, element: INHERITED_TEXT, part: 'text',
|
|
234
|
+
fg: toHexColor(f), bg: toHexColor(bg), ratio: Math.round(ratio * 100) / 100, min: WCAG_AA_TEXT, pass: ratio >= WCAG_AA_TEXT - 1e-9,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
|
|
194
243
|
/** Text over an image: the block's ink — and every token its ink scope pins to it — over the scrim
|
|
195
244
|
* composited on the WORST-CASE image, in the scheme the block wears. Never over the section: the image
|
|
196
245
|
* covers it. */
|
|
@@ -1348,6 +1348,7 @@ export const SPEC_SCHEMES = {
|
|
|
1348
1348
|
"set": {
|
|
1349
1349
|
"section-bg": "{{accent}}",
|
|
1350
1350
|
"bg": "{{accent}}",
|
|
1351
|
+
"nav-bg": "{{accent}}",
|
|
1351
1352
|
"surface": "{{accent-hover}}",
|
|
1352
1353
|
"surface-2": "{{accent-hover}}",
|
|
1353
1354
|
"surface-glass": "{{accent-hover}}",
|
|
@@ -1371,6 +1372,8 @@ export const SPEC_SCHEMES = {
|
|
|
1371
1372
|
}
|
|
1372
1373
|
} as const;
|
|
1373
1374
|
|
|
1375
|
+
export const SPEC_SCHEME_INK = "var(--iox-text)" as const;
|
|
1376
|
+
|
|
1374
1377
|
export const SPEC_A11Y = {
|
|
1375
1378
|
"reducedMotion": {
|
|
1376
1379
|
"dur-fast": "0ms",
|
|
@@ -2012,6 +2015,23 @@ export const SPEC_ELEMENT_STYLES = [
|
|
|
2012
2015
|
"fontWeight": "var(--iox-weight-label)"
|
|
2013
2016
|
}
|
|
2014
2017
|
},
|
|
2018
|
+
{
|
|
2019
|
+
"id": "badge-label",
|
|
2020
|
+
"label": "Badge label",
|
|
2021
|
+
"group": "Content blocks",
|
|
2022
|
+
"appliesTo": [
|
|
2023
|
+
"Text"
|
|
2024
|
+
],
|
|
2025
|
+
"base": {
|
|
2026
|
+
"fontFamily": "var(--iox-font-label)",
|
|
2027
|
+
"fontSize": "var(--iox-badge-size)",
|
|
2028
|
+
"fontWeight": "var(--iox-weight-label)",
|
|
2029
|
+
"lineHeight": "inherit",
|
|
2030
|
+
"letterSpacing": "inherit",
|
|
2031
|
+
"textTransform": "inherit",
|
|
2032
|
+
"color": "inherit"
|
|
2033
|
+
}
|
|
2034
|
+
},
|
|
2015
2035
|
{
|
|
2016
2036
|
"id": "media-scrim",
|
|
2017
2037
|
"label": "Text over image",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ELEMENT_STYLE_MARKER_CLASS, elementStyleClass } from './element-styles';
|
|
2
|
-
import { SPEC_SCHEMES } from './flavor-spec.generated';
|
|
2
|
+
import { SPEC_SCHEME_INK, SPEC_SCHEMES } from './flavor-spec.generated';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The design-system classes a node renders, in BOTH renderers (builder canvas, engine component
|
|
@@ -104,6 +104,54 @@ export function colorSchemeOf(node: DesignClassSource | null | undefined): strin
|
|
|
104
104
|
return typeof trait === 'string' ? trait : null;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* The ink a section colour scheme gives everything inside it (vectoriox/iox-ui#15). A scheme re-declares
|
|
109
|
+
* tokens only, so without this a Container wearing one kept the colour it INHERITED from the section around
|
|
110
|
+
* it: a Featured (Brand) card's body text stayed the page's dark ink on the Brand fill, 2.54:1. Two halves,
|
|
111
|
+
* one rule: `compileSchemeCss` gives every `iox-scheme-*` element `color: var(--iox-text)` (the node rule,
|
|
112
|
+
* emitted after it at the same specificity, still wins), and {@link withSchemeInk} reads a scheme'd node's
|
|
113
|
+
* own `inherit` — what the card / box / panel element styles write for "no colour of my own" — as this ink.
|
|
114
|
+
* The value is the contract's `schemeInk`.
|
|
115
|
+
*/
|
|
116
|
+
export const SCHEME_INK: string = SPEC_SCHEME_INK;
|
|
117
|
+
|
|
118
|
+
/** The values that mean "no colour of my own" — `color` is inherited, so these equal no declaration. */
|
|
119
|
+
const INHERITED_INK: ReadonlySet<string> = new Set(['inherit', 'unset']);
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A node's style map as its renderer compiles it under a section colour scheme: a NON-default scheme turns
|
|
123
|
+
* an inherited `color` into {@link SCHEME_INK}; anything else — no scheme, an author's own colour, a token —
|
|
124
|
+
* is returned untouched (the same object). Every render path calls it on every rule it compiles for the
|
|
125
|
+
* node (base, states, breakpoint layers): the builder's StyleRegistryService and both engine paths.
|
|
126
|
+
*/
|
|
127
|
+
export function withSchemeInk<T extends Record<string, unknown>>(styles: T, scheme: unknown): T {
|
|
128
|
+
if (!styles || !colorSchemeClass(scheme)) return styles;
|
|
129
|
+
const color = styles['color'];
|
|
130
|
+
if (typeof color !== 'string' || !INHERITED_INK.has(color.trim().toLowerCase())) return styles;
|
|
131
|
+
return { ...styles, color: SCHEME_INK };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Write a node's section colour scheme — THE one writer, so every control that offers Section colours (the
|
|
136
|
+
* Style panel's Element style block, Card ▸ Card) edits the same setting: the `colorScheme` content trait on
|
|
137
|
+
* a builder node, `props.colorScheme` on a saved one. False when nothing changed: an unknown id, the value it
|
|
138
|
+
* already holds, or a builder node with no such trait (a type that offers no scheme).
|
|
139
|
+
*/
|
|
140
|
+
export function setColorScheme(
|
|
141
|
+
node: { props?: Record<string, unknown> | null; traits?: Array<{ name?: string; default?: unknown }> | null } | null | undefined,
|
|
142
|
+
id: string,
|
|
143
|
+
): boolean {
|
|
144
|
+
if (!node || !SCHEME_IDS.has(id) || (colorSchemeOf(node) ?? ColorScheme.Default) === id) return false;
|
|
145
|
+
if (Array.isArray(node.traits)) {
|
|
146
|
+
const trait = node.traits.find(t => t?.name === COLOR_SCHEME_TRAIT);
|
|
147
|
+
if (!trait) return false;
|
|
148
|
+
trait.default = id;
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
node.props = { ...(node.props ?? {}), [COLOR_SCHEME_TRAIT]: id };
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
107
155
|
/** True when the node wears a known element style — the marker is on it. */
|
|
108
156
|
export function hasElementStyleMarker(node: { elementStyle?: string | null } | null | undefined): boolean {
|
|
109
157
|
return elementStyleClass(node?.elementStyle) !== null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DesignMode } from './design-token.model';
|
|
2
2
|
import { SPEC_A11Y, SPEC_SCHEMES } from './flavor-spec.generated';
|
|
3
|
+
import { SCHEME_INK } from './node-classes';
|
|
3
4
|
import { specTokenNames } from './token-names';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -10,7 +11,8 @@ import { specTokenNames } from './token-names';
|
|
|
10
11
|
*
|
|
11
12
|
* Block order (design-system plan §3e, T8):
|
|
12
13
|
* 1. base `:root { --iox-*: … }`
|
|
13
|
-
* 2. schemes `.iox-scheme-<id> { overrides; every var()-referencing token re-declared }
|
|
14
|
+
* 2. schemes `.iox-scheme-<id> { overrides; every var()-referencing token re-declared }`, then the
|
|
15
|
+
* ONE scheme-ink rule `.iox-scheme-subtle, … { color: var(--iox-text) }` (iox-ui#15)
|
|
14
16
|
* 3. reduced motion `@media (prefers-reduced-motion: reduce)`
|
|
15
17
|
* 4. reduced transparency `@media (prefers-reduced-transparency: reduce)`
|
|
16
18
|
* 5. dark — LAST `@media (prefers-color-scheme: dark)`
|
|
@@ -167,19 +169,29 @@ export function resolveTokenValues(
|
|
|
167
169
|
* Every token's value INSIDE a section wearing `schemeId`, in one mode — what CSS computes there: the
|
|
168
170
|
* scheme's overrides (`{{token}}` / `{{~token}}` placeholders substituted from the root), every other
|
|
169
171
|
* row as declared, and `var()` references resolved within the section (the §3c re-declaration rule
|
|
170
|
-
* makes that the section's own resolution). `default` (or an unknown id) is the root.
|
|
172
|
+
* makes that the section's own resolution). `default` (or an unknown id) is the root. A CHAIN
|
|
173
|
+
* (`['inverse', 'accent']`, outermost first) is an element wearing the last scheme inside elements wearing
|
|
174
|
+
* the ones before it: a token the inner scheme does not set is the outer one's (custom properties inherit).
|
|
171
175
|
*/
|
|
172
176
|
export function resolveSchemeTokenValues(
|
|
173
177
|
tokens: ReadonlyArray<DesignTokenLike> | null | undefined,
|
|
174
178
|
mode: ResolvedMode,
|
|
175
|
-
schemeId: string
|
|
179
|
+
schemeId: string | ReadonlyArray<string>,
|
|
176
180
|
): Record<string, string> {
|
|
177
181
|
const rows = usableTokens(tokens);
|
|
178
|
-
const
|
|
179
|
-
|
|
182
|
+
const sets = (typeof schemeId === 'string' ? [schemeId] : schemeId)
|
|
183
|
+
.map(id => (id === DEFAULT_SCHEME ? undefined : (SPEC_SCHEMES as Readonly<Record<string, { set: Readonly<Record<string, string>> }>>)[id]?.set));
|
|
184
|
+
if (!sets.some(Boolean)) return resolveTokenValues(rows, mode);
|
|
180
185
|
const root = resolveTokenValues(rows, mode);
|
|
181
186
|
const other = resolveTokenValues(rows, mode === DARK ? LIGHT : DARK);
|
|
182
|
-
|
|
187
|
+
// Nested (`[outer, inner]` — a Featured card in an Inverse section): each scheme block re-declares the
|
|
188
|
+
// rows that reference a token, so at every level those are restored before the block's own set.
|
|
189
|
+
const overrides = new Map<string, string>();
|
|
190
|
+
for (const set of sets) {
|
|
191
|
+
if (!set) continue;
|
|
192
|
+
for (const t of rows) if (modeValue(t, mode).includes(REFERENCE)) overrides.delete(t.name);
|
|
193
|
+
for (const [name, value] of schemeEntries(set, root, other)) overrides.set(name, value);
|
|
194
|
+
}
|
|
183
195
|
return resolveTokenValues(rows.map(t => ({ name: t.name, value: overrides.get(t.name) ?? modeValue(t, mode) })), LIGHT);
|
|
184
196
|
}
|
|
185
197
|
|
|
@@ -243,11 +255,27 @@ export function compileSchemeCss(
|
|
|
243
255
|
if (!rows.length) return '';
|
|
244
256
|
const resolved = resolveTokenValues(rows, LIGHT);
|
|
245
257
|
const other = opposite ?? resolveTokenValues(rows, DARK);
|
|
246
|
-
return
|
|
247
|
-
...
|
|
248
|
-
|
|
249
|
-
.
|
|
250
|
-
|
|
258
|
+
return [
|
|
259
|
+
...nonDefaultSchemes().map(([id, set]) => rule(schemeSelector(id, scope), [
|
|
260
|
+
...schemeEntries(set, resolved, other),
|
|
261
|
+
...rows.filter(t => !(t.name in set) && t.value.includes(REFERENCE))
|
|
262
|
+
.map((t): [string, string] => [t.name, t.value]),
|
|
263
|
+
])),
|
|
264
|
+
schemeInkRule(scope),
|
|
265
|
+
].filter(Boolean).join('\n');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Every element wearing a scheme takes the scheme's ink (vectoriox/iox-ui#15): the tokens re-resolve inside
|
|
270
|
+
* it, but `color` is inherited as a COMPUTED value, so without this a scheme'd Container's running text kept
|
|
271
|
+
* the outer section's ink. One rule, specificity (0,1,0) in every renderer — a builder scope is wrapped in
|
|
272
|
+
* `:where()` — emitted with the token CSS, BEFORE every node rule: a node's own colour (same specificity,
|
|
273
|
+
* later) still wins. A node's `color: inherit` is read as this ink by `withSchemeInk`.
|
|
274
|
+
*/
|
|
275
|
+
function schemeInkRule(scope?: string): string {
|
|
276
|
+
const where = scope ? `:where(${scope}) ` : '';
|
|
277
|
+
const selectors = nonDefaultSchemes().map(([id]) => `${where}.iox-scheme-${id}`);
|
|
278
|
+
return selectors.length ? `${selectors.join(', ')} {\n color: ${SCHEME_INK};\n}` : '';
|
|
251
279
|
}
|
|
252
280
|
|
|
253
281
|
/** The dark twin of both scheme rules: a re-declared token with a dark value would otherwise pin
|