@vectoriox/iox-ui 5.10.0 → 5.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectoriox/iox-ui",
3
- "version": "5.10.0",
3
+ "version": "5.11.0",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=20.0.0",
6
6
  "@angular/core": ">=20.0.0",
@@ -226,13 +226,18 @@ export function deriveBrandTokens(seed: string, pack: Pick<FlavorPack, 'accentHu
226
226
  /** WCAG 2 AA for body text — the gate every quartet pair is held to. */
227
227
  export const WCAG_AA_TEXT = 4.5;
228
228
 
229
- export const ACCENT_TOKEN_NAMES = ['accent', 'accent-ink', 'accent-hover', 'accent-text'] as const;
229
+ export const ACCENT_TOKEN_NAMES = [
230
+ 'accent', 'accent-ink', 'accent-hover', 'accent-text', 'inverse-accent', 'inverse-accent-ink',
231
+ ] as const;
230
232
  export type AccentTokenName = (typeof ACCENT_TOKEN_NAMES)[number];
231
233
 
232
- /** The page backgrounds the accent must read on, light and (optionally) dark. */
234
+ /** The backgrounds the accent must read on, light and (optionally) dark. Without an inverse
235
+ * background the inverse pair is not derived — the pack's own stays. */
233
236
  export interface AccentBackgrounds {
234
237
  bg: string;
235
238
  bgDark?: string;
239
+ inverseBg?: string;
240
+ inverseBgDark?: string;
236
241
  }
237
242
 
238
243
  const RAMP_ORDER = RAMP_STEPS.map(s => s.step);
@@ -252,7 +257,13 @@ const bestInkHex = (bg: string) =>
252
257
  * - dark: the first of brand-300/200/400 an ink clears 4.5:1 on, the same hover rule, and
253
258
  * `accent-text` = the first light step that clears 4.5:1 on `bgDark`
254
259
  *
255
- * Every pair (ink/accent, ink/hover, text/bg) is asserted ≥ 4.5:1 in `brand.spec.ts`.
260
+ * - `inverse-accent` = the first step from 500 toward the end that reads on `inverseBg` clearing
261
+ * 4.5:1 on it; `inverse-accent-ink` = the higher-contrast of `brandInk` and the OPPOSITE ramp end
262
+ * (the spec's `brandSeed` rule). A step whose best ink still misses 4.5:1 is passed over, so the
263
+ * pair is a guarantee rather than a hope. Dark: the same against `inverseBgDark`.
264
+ *
265
+ * Every pair (ink/accent, ink/hover, text/bg, inverse ink/inverse accent, inverse accent/inverse
266
+ * bg) is asserted ≥ 4.5:1 across packs and seeds in `flavor-tokens.spec.ts`.
256
267
  */
257
268
  export function deriveAccentTokens(
258
269
  seed: string,
@@ -289,6 +300,24 @@ export function deriveAccentTokens(
289
300
  };
290
301
  })() : null;
291
302
 
303
+ const inverseOn = (inverseBg: string) => {
304
+ const toward = wcagContrastRatio(step(RAMP_ORDER[0]), inverseBg) >= wcagContrastRatio(step(RAMP_ORDER[RAMP_ORDER.length - 1]), inverseBg)
305
+ ? -1 : 1;
306
+ const walk = RAMP_ORDER.filter(n => (toward < 0 ? n <= 500 : n >= 500));
307
+ if (toward < 0) walk.reverse();
308
+ const rampEnd = step(toward < 0 ? RAMP_ORDER[RAMP_ORDER.length - 1] : RAMP_ORDER[0]);
309
+ const inkOn = (fill: string) =>
310
+ (wcagContrastRatio(ink, fill) >= wcagContrastRatio(rampEnd, fill) ? ink : rampEnd);
311
+ for (const n of walk) {
312
+ const fill = step(n);
313
+ if (passesText(fill, inverseBg) && passesText(inkOn(fill), fill)) return { accent: fill, ink: inkOn(fill) };
314
+ }
315
+ const fill = step(walk[walk.length - 1]);
316
+ return { accent: fill, ink: passesText(inkOn(fill), fill) ? inkOn(fill) : bestInkHex(fill) };
317
+ };
318
+ const inverse = against.inverseBg ? inverseOn(against.inverseBg) : null;
319
+ const inverseDark = against.inverseBg && against.inverseBgDark ? inverseOn(against.inverseBgDark) : null;
320
+
292
321
  const row = (name: AccentTokenName, value: string, darkValue?: string): FlavorTokenDef => ({
293
322
  name, value, type: 'color', group: 'colour', tier: 'semantic',
294
323
  ...(darkValue ? { values: { dark: darkValue } } : {}),
@@ -298,5 +327,9 @@ export function deriveAccentTokens(
298
327
  row('accent-ink', light.ink, dark?.ink),
299
328
  row('accent-hover', light.hover, dark?.hover),
300
329
  row('accent-text', light.text, dark?.text),
330
+ ...(inverse ? [
331
+ row('inverse-accent', inverse.accent, inverseDark?.accent),
332
+ row('inverse-accent-ink', inverse.ink, inverseDark?.ink),
333
+ ] : []),
301
334
  ];
302
335
  }
@@ -1,15 +1,17 @@
1
- import { FLAVOR_SPEC } from './flavor-spec.generated';
1
+ import {
2
+ FLAVOR_SPEC_VERSION, SPEC_AXES, SPEC_FLAVOR_PACKS, SPEC_FONT_ROLES, SPEC_SCHEMES, SPEC_TIER_IDS, SPEC_TOKEN_TYPES,
3
+ } from './flavor-spec.generated';
2
4
 
3
5
  /**
4
6
  * The design-token vocabulary (design-system plan §3a/§3f). Every list here is READ from the
5
7
  * generated spec, so the contract has one source: iox-ai-guidance `flavor-spec.v1.json`.
6
8
  */
7
9
 
8
- export type DesignTokenType = (typeof FLAVOR_SPEC.tokenTypes)[number];
9
- export const DESIGN_TOKEN_TYPES: ReadonlyArray<DesignTokenType> = FLAVOR_SPEC.tokenTypes;
10
+ export type DesignTokenType = (typeof SPEC_TOKEN_TYPES)[number];
11
+ export const DESIGN_TOKEN_TYPES: ReadonlyArray<DesignTokenType> = SPEC_TOKEN_TYPES;
10
12
 
11
- export type DesignTokenTier = keyof typeof FLAVOR_SPEC.tiers;
12
- export const DESIGN_TOKEN_TIERS = Object.keys(FLAVOR_SPEC.tiers) as ReadonlyArray<DesignTokenTier>;
13
+ export type DesignTokenTier = (typeof SPEC_TIER_IDS)[number];
14
+ export const DESIGN_TOKEN_TIERS: ReadonlyArray<DesignTokenTier> = SPEC_TIER_IDS;
13
15
 
14
16
  /** The spec carries groups per row, not as a list — `design-token.model.spec` pins this list
15
17
  * to exactly the set of groups the spec's rows use. */
@@ -35,12 +37,12 @@ export interface StoredDesignTokenRow extends DesignTokenRow {
35
37
  _id?: string;
36
38
  }
37
39
 
38
- export type ShapeId = keyof typeof FLAVOR_SPEC.axes.shapes;
39
- export type DensityId = keyof typeof FLAVOR_SPEC.axes.densities;
40
- export type MotionId = keyof typeof FLAVOR_SPEC.axes.motions;
41
- export type DesignMode = (typeof FLAVOR_SPEC.axes.modes)[number];
42
- export type SchemeId = keyof typeof FLAVOR_SPEC.schemes;
43
- export type FontRoleToken = (typeof FLAVOR_SPEC.fontRoles)[number]['token'];
40
+ export type ShapeId = keyof typeof SPEC_AXES.shapes;
41
+ export type DensityId = keyof typeof SPEC_AXES.densities;
42
+ export type MotionId = keyof typeof SPEC_AXES.motions;
43
+ export type DesignMode = (typeof SPEC_AXES.modes)[number];
44
+ export type SchemeId = keyof typeof SPEC_SCHEMES;
45
+ export type FontRoleToken = (typeof SPEC_FONT_ROLES)[number]['token'];
44
46
 
45
47
  /** Everything a studio chose about its look — the `settings.design` row, typed. */
46
48
  export interface DesignChoices {
@@ -97,9 +99,7 @@ export interface FlavorPackV2 {
97
99
 
98
100
  /** The spec's flavors, typed. Assignment here is the compile-time proof that the generated
99
101
  * data fits `FlavorPackV2`. */
100
- export const SPEC_FLAVORS: ReadonlyArray<FlavorPackV2> = FLAVOR_SPEC.flavors;
101
-
102
- export const FLAVOR_SPEC_VERSION: number = FLAVOR_SPEC.version;
102
+ export const SPEC_FLAVORS: ReadonlyArray<FlavorPackV2> = SPEC_FLAVOR_PACKS;
103
103
 
104
104
  /** The stamp seeded rows carry, so an upgrade can tell the machine's rows from the author's. */
105
105
  export function flavorSourceStamp(flavorId: string, version: number = FLAVOR_SPEC_VERSION): string {