@vectoriox/iox-ui 5.25.0 → 5.27.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.
Files changed (32) hide show
  1. package/design-core/index.d.ts +1153 -191
  2. package/fesm2022/vectoriox-iox-ui-design-core.mjs +1680 -241
  3. package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
  4. package/fesm2022/vectoriox-iox-ui-generation-core.mjs +7018 -0
  5. package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -0
  6. package/generation-core/index.d.ts +1397 -0
  7. package/generation-core/package.json +3 -0
  8. package/package.json +5 -1
  9. package/src/design-core/brand-neutrals.ts +236 -0
  10. package/src/design-core/brand.ts +24 -14
  11. package/src/design-core/colour-eval.ts +117 -0
  12. package/src/design-core/contrast-audit.ts +214 -0
  13. package/src/design-core/design-apply.ts +34 -14
  14. package/src/design-core/design-settings.ts +12 -3
  15. package/src/design-core/design-token.model.ts +26 -0
  16. package/src/design-core/flavor-fixtures.generated.ts +150 -0
  17. package/src/design-core/flavor-spec.generated.ts +1061 -189
  18. package/src/design-core/flavor-tokens.ts +36 -8
  19. package/src/design-core/public-api.ts +3 -0
  20. package/src/design-core/token-css.ts +48 -15
  21. package/src/generation-core/compose-site.ts +474 -0
  22. package/src/generation-core/node-baseline.model.ts +10 -0
  23. package/src/generation-core/node-baselines.generated.ts +4841 -0
  24. package/src/generation-core/patterns.ts +125 -0
  25. package/src/generation-core/public-api.ts +20 -0
  26. package/src/generation-core/serialized-node.ts +91 -0
  27. package/src/generation-core/site-plan.model.ts +136 -0
  28. package/src/generation-core/site-spec.generated.ts +1111 -0
  29. package/src/generation-core/templates.ts +60 -0
  30. package/src/generation-core/validate-site-plan.ts +413 -0
  31. package/styles/iox-design.css +16 -0
  32. package/styles/iox-forms.css +15 -9
@@ -0,0 +1,125 @@
1
+ import { SPEC_SITE_PATTERNS } from './site-spec.generated';
2
+
3
+ /**
4
+ * The pattern library (contract `site.patterns`): structure and element styles only — never a colour,
5
+ * size or font. Each node is written in the contract's short vocabulary:
6
+ *
7
+ * `t` component type · `es` element style · `scheme` section colour scheme · `tag` a Container's html
8
+ * tag · `level` a Heading's level · `lay` layout-only styleProps · `resp` per-breakpoint styleProps ·
9
+ * `aspect` an Image's aspect ratio · `slot` author-editable text, filled from the plan's section slots ·
10
+ * `fromBusiness` text from the plan's business · `text` the pattern's own words · `ph` a publish-blocking
11
+ * placeholder (a FACT key, `copy` or `image`) · `bind` a binding path on the repeater item (`item.*`,
12
+ * `page.*`), the route item (`route.*`) or its next sibling (`next.*`) · `src` what a Repeater iterates
13
+ * (`$binding`, `$navPages`, `route.<array>`) · `href` a page route (`$page.route` = the nav item's) ·
14
+ * `param` the value of the route's `:param` · `optional` a part a page may leave out (`without`) ·
15
+ * `preset` a Form preset · `control` / `label` / `required` a Field's · `c` children.
16
+ */
17
+ export interface PatternNode {
18
+ readonly t: string;
19
+ readonly es?: string;
20
+ readonly scheme?: string;
21
+ readonly tag?: string;
22
+ readonly level?: string;
23
+ readonly lay?: Readonly<Record<string, string>>;
24
+ readonly resp?: Readonly<Record<string, Readonly<Record<string, string>>>>;
25
+ readonly aspect?: string;
26
+ readonly slot?: string;
27
+ readonly fromBusiness?: string;
28
+ readonly text?: string;
29
+ readonly ph?: string;
30
+ readonly bind?: string;
31
+ readonly src?: string;
32
+ readonly href?: string;
33
+ readonly param?: string;
34
+ readonly optional?: string;
35
+ readonly preset?: string;
36
+ readonly control?: string;
37
+ readonly label?: string;
38
+ readonly required?: boolean;
39
+ readonly c?: ReadonlyArray<PatternNode>;
40
+ }
41
+
42
+ export enum PatternGlobalPosition {
43
+ Before = 'before',
44
+ After = 'after',
45
+ }
46
+
47
+ export interface SitePattern {
48
+ readonly family: string;
49
+ readonly label: string;
50
+ /** Set on a header/footer pattern: it renders on every page, before or after the page. */
51
+ readonly global?: `${PatternGlobalPosition}`;
52
+ readonly node: PatternNode;
53
+ }
54
+
55
+ export const SITE_PATTERNS: Readonly<Record<string, SitePattern>> = SPEC_SITE_PATTERNS;
56
+
57
+ /** The pattern placeholder kinds that are not FACT keys. */
58
+ export enum PatternPlaceholder {
59
+ Copy = 'copy',
60
+ Image = 'image',
61
+ }
62
+
63
+ /** The composer directives a pattern writes where a plan supplies the value. */
64
+ export enum PatternDirective {
65
+ /** A Repeater's source: the section's `binding`. */
66
+ Binding = '$binding',
67
+ /** A Repeater's source: the plan's pages that are in the navigation. */
68
+ NavPages = '$navPages',
69
+ /** A Link's route: the nav item's route. */
70
+ PageRoute = '$page.route',
71
+ }
72
+
73
+ /** The scopes a `bind` / `param` / `src` path starts with. */
74
+ export enum BindScope {
75
+ /** The nearest Repeater's row. */
76
+ Item = 'item',
77
+ /** The nearest Repeater's row, when that Repeater iterates the navigation's pages. */
78
+ Page = 'page',
79
+ /** The page's route item (`routeSource`). */
80
+ Route = 'route',
81
+ /** The route item's next sibling (the plan's `$siblings.next` source of the route item). */
82
+ Next = 'next',
83
+ }
84
+
85
+ export function patternById(id: string | null | undefined): SitePattern | undefined {
86
+ return id && Object.prototype.hasOwnProperty.call(SITE_PATTERNS, id) ? SITE_PATTERNS[id] : undefined;
87
+ }
88
+
89
+ function walk(node: PatternNode, visit: (n: PatternNode) => void): void {
90
+ visit(node);
91
+ for (const child of node.c ?? []) walk(child, visit);
92
+ }
93
+
94
+ /** Every node of a pattern, depth first. */
95
+ export function patternNodes(pattern: SitePattern): PatternNode[] {
96
+ const out: PatternNode[] = [];
97
+ walk(pattern.node, n => out.push(n));
98
+ return out;
99
+ }
100
+
101
+ /** The slot names a plan section may fill. */
102
+ export function patternSlots(pattern: SitePattern): string[] {
103
+ return [...new Set(patternNodes(pattern).map(n => n.slot).filter((s): s is string => !!s))];
104
+ }
105
+
106
+ /** The optional parts a plan section may leave out. */
107
+ export function patternOptionalParts(pattern: SitePattern): string[] {
108
+ return [...new Set(patternNodes(pattern).map(n => n.optional).filter((s): s is string => !!s))];
109
+ }
110
+
111
+ /** True when the pattern has a Repeater iterating the section's `binding`. */
112
+ export function patternNeedsBinding(pattern: SitePattern): boolean {
113
+ return patternNodes(pattern).some(n => n.src === PatternDirective.Binding);
114
+ }
115
+
116
+ /** The bind scopes a pattern reads (`route`, `next`, …) — what its page must provide. */
117
+ export function patternScopes(pattern: SitePattern): Set<string> {
118
+ const scopes = new Set<string>();
119
+ for (const n of patternNodes(pattern)) {
120
+ for (const path of [n.bind, n.param, n.src]) {
121
+ if (path && !path.startsWith('$')) scopes.add(path.split('.')[0]);
122
+ }
123
+ }
124
+ return scopes;
125
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @vectoriox/iox-ui/generation-core — the shared, FRAMEWORK-FREE site-plan core (design-system plan
3
+ * §6b, W1): the Site Plan shape + its validator, the pattern library and the Studio template (both
4
+ * generated 1:1 from iox-ai-guidance `flavor-spec.v1.json`), and `composeSite` — a plan in, the
5
+ * schemas, sample items, data sources, pages, routes and header/footer globals out, as serialized
6
+ * layout JSON. The website wizard (W2–W4) and the AI agent drive the SAME composer.
7
+ * Pure: no transport, no clock, no randomness. Consumed by iox-cms (the server-side site build), the
8
+ * builder and the engine, so nothing here may import Angular.
9
+ * ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, tsconfig paths —
10
+ * or `nest start` dies with ERR_REQUIRE_ESM while jest and build both pass.
11
+ */
12
+ export * from './site-spec.generated';
13
+ export * from './node-baseline.model';
14
+ export * from './node-baselines.generated';
15
+ export * from './site-plan.model';
16
+ export * from './patterns';
17
+ export * from './templates';
18
+ export * from './serialized-node';
19
+ export * from './validate-site-plan';
20
+ export * from './compose-site';
@@ -0,0 +1,91 @@
1
+ import { applyElementStyle, defaultElementStyleFor, elementStyleById, NEW_NODE_STYLE_DEFAULTS, ownedTraitsOf } from '@vectoriox/iox-ui/design-core';
2
+
3
+ import { NODE_BASELINES } from './node-baselines.generated';
4
+ import { PatternGlobalPosition } from './patterns';
5
+
6
+ /**
7
+ * A node exactly as a saved page layout stores it (the builder serializer's shape): flat `props` and
8
+ * a FULL `styleProps` (self-describing — render-parity.md, task 10), the element style's pseudo-states,
9
+ * sparse per-breakpoint overrides and the bindings. What the composer emits; what the builder reloads
10
+ * and the engine renders, unchanged.
11
+ */
12
+ export interface SerializedBinding {
13
+ prop: string;
14
+ source: string;
15
+ path: string;
16
+ }
17
+
18
+ export type SerializedResponsive = Partial<Record<string, { styleProps?: Record<string, unknown> }>>;
19
+
20
+ export interface SerializedNode {
21
+ id: string;
22
+ type: string;
23
+ label?: string;
24
+ props: Record<string, unknown>;
25
+ styleProps: Record<string, unknown>;
26
+ stateStyles?: Partial<Record<string, Record<string, unknown>>>;
27
+ responsive?: SerializedResponsive;
28
+ elementStyle?: string;
29
+ bindings: SerializedBinding[];
30
+ interactions: unknown[];
31
+ isGlobal?: boolean;
32
+ globalPosition?: `${PatternGlobalPosition}`;
33
+ children?: SerializedNode[];
34
+ }
35
+
36
+ /** A pattern the composer cannot stamp as the builder would (an unregistered type, a trait the type has
37
+ * no slot for, a style that does not apply). The plan is validated first, so this means the CONTRACT is
38
+ * wrong — never something to work around. */
39
+ export class ComposeError extends Error {}
40
+
41
+ const clone = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;
42
+
43
+ /**
44
+ * A NEW node of `type`, as the builder creates one (ComponentRegistryService.createConfig, serialized):
45
+ * the type's constructor defaults, its text colour released to `inherit` (NEW_NODE_STYLE_DEFAULTS), then
46
+ * ONE element style — `elementStyle` when given, else the type's default for its props (a Heading
47
+ * follows its level). `props` are applied before the style is chosen.
48
+ */
49
+ export function createSerializedNode(id: string, type: string, props: Record<string, unknown> = {}, elementStyle?: string | null): SerializedNode {
50
+ const baseline = NODE_BASELINES[type];
51
+ if (!baseline) throw new ComposeError(`"${type}" is not a registered component type`);
52
+ const node: SerializedNode = {
53
+ id,
54
+ type,
55
+ props: { ...clone(baseline.props), ...props },
56
+ styleProps: clone(baseline.styleProps) as Record<string, unknown>,
57
+ bindings: [],
58
+ interactions: [],
59
+ };
60
+ for (const [name, value] of Object.entries(NEW_NODE_STYLE_DEFAULTS)) if (name in node.styleProps) node.styleProps[name] = value;
61
+ const styleId = elementStyle ?? defaultElementStyleFor(type, node.props);
62
+ if (styleId) {
63
+ const style = elementStyleById(styleId);
64
+ if (!style?.appliesTo.includes(type)) throw new ComposeError(`element style "${styleId}" does not apply to ${type}`);
65
+ const missing = ownedTraitsOf(style).filter(o => !o.state && !baseline.styleTraits.includes(o.trait));
66
+ if (missing.length) throw new ComposeError(`${type} has no slot for ${styleId}'s ${missing.map(o => o.trait).join(', ')}`);
67
+ applyElementStyle(node, styleId);
68
+ }
69
+ if (baseline.container) node.children = [];
70
+ return node;
71
+ }
72
+
73
+ /** Refuse a style trait the type has no slot for — the builder would silently drop it. */
74
+ export function assertStyleSlots(type: string, names: ReadonlyArray<string>): void {
75
+ const slots = NODE_BASELINES[type]?.styleTraits ?? [];
76
+ for (const name of names) if (!slots.includes(name)) throw new ComposeError(`${type} has no style trait "${name}"`);
77
+ }
78
+
79
+ /** Write style traits onto a node (each one a slot the type has). */
80
+ export function setStyleProps(node: SerializedNode, values: Readonly<Record<string, unknown>>): void {
81
+ assertStyleSlots(node.type, Object.keys(values));
82
+ for (const [name, value] of Object.entries(values)) node.styleProps[name] = value;
83
+ }
84
+
85
+ /** Every node of a tree, depth first. */
86
+ export function walkSerialized(nodes: ReadonlyArray<SerializedNode>, visit: (node: SerializedNode, ancestors: SerializedNode[]) => void, ancestors: SerializedNode[] = []): void {
87
+ for (const node of nodes) {
88
+ visit(node, ancestors);
89
+ if (node.children?.length) walkSerialized(node.children, visit, [...ancestors, node]);
90
+ }
91
+ }
@@ -0,0 +1,136 @@
1
+ import type { DesignChoices } from '@vectoriox/iox-ui/design-core';
2
+
3
+ import { SPEC_SITE_FACTS } from './site-spec.generated';
4
+
5
+ /**
6
+ * The Site Plan (contract `site.planShape`) — the ONE document the website wizard writes
7
+ * (`source.kind: template`) and the AI agent will write (`source.kind: ai`). One validator, one
8
+ * composer: everything the wizard proves is proven for the agent.
9
+ */
10
+
11
+ export const SITE_PLAN_VERSION = '1';
12
+
13
+ export enum SitePlanSourceKind {
14
+ Template = 'template',
15
+ Ai = 'ai',
16
+ }
17
+
18
+ export type SitePlanSource =
19
+ | { kind: `${SitePlanSourceKind.Template}`; templateId: string; templateVersion: number }
20
+ | { kind: `${SitePlanSourceKind.Ai}`; runId: string };
21
+
22
+ /** A fact a customer could be held to. Only ever the customer's own words; blank → a placeholder. */
23
+ export type FactKey = keyof typeof SPEC_SITE_FACTS;
24
+ export const FACT_KEYS = Object.keys(SPEC_SITE_FACTS) as ReadonlyArray<FactKey>;
25
+ /** The label a missing fact shows, verbatim from the contract. */
26
+ export const SITE_FACT_LABELS: Readonly<Record<FactKey, string>> = SPEC_SITE_FACTS;
27
+
28
+ export interface SiteBusiness {
29
+ readonly name: string;
30
+ readonly tagline?: string;
31
+ readonly facts?: Readonly<Partial<Record<FactKey, string>>>;
32
+ }
33
+
34
+ export interface ContentFieldPlan {
35
+ readonly name: string;
36
+ readonly type: string;
37
+ readonly controller: string;
38
+ readonly required?: boolean;
39
+ readonly note?: string;
40
+ }
41
+
42
+ /** How the content type's sample items are seeded. String values are templates: `{n}` is the item's
43
+ * 1-based number, `{sampleImage:n}` / `{sampleImage:n+k}` a bundled sample image (resolved by the build). */
44
+ export interface ContentSeedPlan {
45
+ readonly count: number;
46
+ readonly sample: boolean;
47
+ readonly isPublished: boolean;
48
+ readonly [field: string]: unknown;
49
+ }
50
+
51
+ export interface ContentTypePlan {
52
+ readonly name: string;
53
+ readonly label: string;
54
+ readonly previewTextField?: string;
55
+ readonly previewImageField?: string;
56
+ readonly fields: ReadonlyArray<ContentFieldPlan>;
57
+ readonly seed?: ContentSeedPlan;
58
+ }
59
+
60
+ export enum PlanDataSourceType {
61
+ Cms = 'cms',
62
+ Relative = 'relative',
63
+ }
64
+
65
+ export enum PlanDataSourceMode {
66
+ Query = 'query',
67
+ Single = 'single',
68
+ }
69
+
70
+ /** The relative operations a plan may name, and the shipped `RelativeSourceSpec` each one is. */
71
+ export enum RelativeOp {
72
+ SiblingsNext = '$siblings.next',
73
+ SiblingsPrev = '$siblings.prev',
74
+ }
75
+
76
+ export interface CmsDataSourcePlan {
77
+ readonly alias: string;
78
+ readonly type: `${PlanDataSourceType.Cms}`;
79
+ readonly mode: `${PlanDataSourceMode}`;
80
+ readonly contentType: string;
81
+ readonly limit?: number;
82
+ readonly filterByRouteParam?: { readonly field: string; readonly routeParam: string };
83
+ }
84
+
85
+ export interface RelativeDataSourcePlan {
86
+ readonly alias: string;
87
+ readonly type: `${PlanDataSourceType.Relative}`;
88
+ /** The subject: the alias of the current item. */
89
+ readonly of: string;
90
+ readonly op: `${RelativeOp}`;
91
+ /** The collection the subject is located within. */
92
+ readonly within: string;
93
+ }
94
+
95
+ export type DataSourcePlan = CmsDataSourcePlan | RelativeDataSourcePlan;
96
+
97
+ export interface PageSectionPlan {
98
+ readonly pattern: string;
99
+ readonly slots?: Readonly<Record<string, string>>;
100
+ /** The list source alias a pattern's `$binding` repeater iterates. */
101
+ readonly binding?: string;
102
+ /** Optional pattern parts (`optional: <id>`) to leave out. */
103
+ readonly without?: ReadonlyArray<string>;
104
+ }
105
+
106
+ export interface PagePlan {
107
+ readonly key: string;
108
+ readonly name: string;
109
+ readonly route: string;
110
+ readonly isHome?: boolean;
111
+ /** Carried by templates (the wizard's Pages step); not part of the documented plan shape. */
112
+ readonly required?: boolean;
113
+ /** In the navigation (`$navPages`). Carried by templates; not part of the documented plan shape. */
114
+ readonly inNav?: boolean;
115
+ /** A template page: the content type it renders one item of. */
116
+ readonly template?: string;
117
+ /** The single source bound to the route item (`route.*`). */
118
+ readonly routeSource?: string;
119
+ readonly sections: ReadonlyArray<PageSectionPlan>;
120
+ }
121
+
122
+ export enum GlobalSlot {
123
+ Header = 'header',
124
+ Footer = 'footer',
125
+ }
126
+
127
+ export interface SitePlan {
128
+ readonly version: typeof SITE_PLAN_VERSION;
129
+ readonly source: SitePlanSource;
130
+ readonly business: SiteBusiness;
131
+ readonly design: Readonly<DesignChoices>;
132
+ readonly contentTypes: ReadonlyArray<ContentTypePlan>;
133
+ readonly dataSources: ReadonlyArray<DataSourcePlan>;
134
+ readonly pages: ReadonlyArray<PagePlan>;
135
+ readonly globals: Readonly<Record<`${GlobalSlot}`, string>>;
136
+ }