@yahoo/uds-create-config 4.16.0 → 4.18.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/dist/configs/CanvasConfig.d.ts +98 -0
- package/dist/configs/react-native-system.d.ts +75 -8
- package/dist/configs/system.d.ts +82 -12
- package/dist/configs/system.js +5 -1
- package/dist/css/root-font-size.d.ts +16 -0
- package/dist/css/root-font-size.js +97 -0
- package/dist/css/value-type.js +5 -1
- package/dist/entities/native/NativeModifier.d.ts +1 -1
- package/dist/entities/native/NativeSettings.d.ts +1 -0
- package/dist/entities/native/NativeToken.d.ts +4 -3
- package/dist/entities/system/Component.d.ts +50 -0
- package/dist/entities/system/Component.js +8 -1
- package/dist/entities/system/Modifier.d.ts +2 -2
- package/dist/entities/system/Modifier.js +2 -5
- package/dist/entities/system/Settings.d.ts +3 -0
- package/dist/entities/system/Settings.js +12 -1
- package/dist/entities/system/StyleProperty.js +2 -3
- package/dist/entities/system/Token.d.ts +21 -8
- package/dist/entities/system/Token.js +24 -5
- package/dist/entities/system/color.d.ts +4 -1
- package/dist/entities/system/color.js +59 -5
- package/dist/entities/system/composed-values.d.ts +36 -0
- package/dist/entities/system/composed-values.js +52 -0
- package/dist/entities/system/style-bag.d.ts +2 -2
- package/dist/entities/system/style-bag.js +3 -5
- package/dist/entities/system/token-index.js +1 -1
- package/dist/entities/system/token-values.d.ts +138 -0
- package/dist/entities/system/token-values.js +154 -0
- package/dist/entities/system/transition.d.ts +35 -0
- package/dist/entities/system/transition.js +57 -0
- package/dist/framework/Config.d.ts +1 -1
- package/dist/framework/Config.js +1 -1
- package/dist/framework/defineEntity.d.ts +1 -1
- package/dist/framework/defineEntity.js +2 -5
- package/dist/framework/projections.js +2 -3
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/framework/utils/identifier.d.ts +8 -0
- package/dist/framework/utils/identifier.js +62 -0
- package/dist/framework/utils/refs.d.ts +7 -1
- package/dist/framework/utils/refs.js +10 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.js +10 -5
- package/dist/migrations/2.0.0/raw-css.js +1 -1
- package/dist/migrations/2.0.0/v1-artifact.d.ts +42 -7
- package/dist/migrations/2.0.0/v1-artifact.js +1 -1
- package/dist/migrations/20260923144958_transition_token_value.d.ts +13 -0
- package/dist/migrations/20260923144958_transition_token_value.js +10 -0
- package/dist/migrations/20260923160425_render_token_refs.d.ts +14 -0
- package/dist/migrations/20260923160425_render_token_refs.js +10 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Transition } from "./transition.js";
|
|
2
|
+
import { DerivedColor, Gradient } from "./color.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/entities/system/composed-values.d.ts
|
|
6
|
+
type ComposedValue = DerivedColor | Gradient | Transition;
|
|
7
|
+
/** How the write engine spells a stored value: the helper's name and its positional arguments. */
|
|
8
|
+
interface ComposedValueAuthoring {
|
|
9
|
+
readonly helper: string;
|
|
10
|
+
readonly args: readonly unknown[];
|
|
11
|
+
}
|
|
12
|
+
interface ComposedValueForm<T extends ComposedValue = ComposedValue> {
|
|
13
|
+
readonly schema: z.ZodType<T>;
|
|
14
|
+
readonly is: (value: unknown) => value is T;
|
|
15
|
+
/** The value as CSS, with `render` resolving each part it holds. */
|
|
16
|
+
readonly render: (value: T, render: (part: unknown) => string) => string;
|
|
17
|
+
/**
|
|
18
|
+
* The helper call that builds this value, or `undefined` for a value no helper produces (a radial
|
|
19
|
+
* gradient, a `darken` in an unexpected space). Such a value is written as the object it is.
|
|
20
|
+
*/
|
|
21
|
+
readonly authoring: (value: T) => ComposedValueAuthoring | undefined;
|
|
22
|
+
}
|
|
23
|
+
declare const composedValueForms: readonly ComposedValueForm[];
|
|
24
|
+
/** The forms' schemas, in registry order, for a value union to spread. */
|
|
25
|
+
declare const composedValueSchemas: readonly z.ZodType<ComposedValue>[];
|
|
26
|
+
/** The form a value takes, or `undefined` for anything that is not composed. A string or a number
|
|
27
|
+
* answers before the forms are asked, since every form is an object. */
|
|
28
|
+
declare function composedValueFormOf(value: unknown): ComposedValueForm | undefined;
|
|
29
|
+
/** Whether a value is one of the composed forms. */
|
|
30
|
+
declare function isComposedValue(value: unknown): value is ComposedValue;
|
|
31
|
+
/** A composed value as CSS, with `render` resolving each part it holds. */
|
|
32
|
+
declare function renderComposedValue(value: ComposedValue, render: (part: unknown) => string): string;
|
|
33
|
+
/** The helper call that authors a composed value, or `undefined` when none does. */
|
|
34
|
+
declare function composedValueAuthoring(value: unknown): ComposedValueAuthoring | undefined;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { ComposedValue, ComposedValueAuthoring, ComposedValueForm, composedValueAuthoring, composedValueFormOf, composedValueForms, composedValueSchemas, isComposedValue, renderComposedValue };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { derivedColor, derivedColorAuthoring, gradient, gradientAuthoring, isDerivedColor, isGradient, renderDerivedColor, renderGradient } from "./color.js";
|
|
2
|
+
import { isTransition, renderTransition, transitionAuthoring, transitionValue } from "./transition.js";
|
|
3
|
+
//#region src/entities/system/composed-values.ts
|
|
4
|
+
/** A form written against its own type, widened to the union the registry holds. `render` and
|
|
5
|
+
* `authoring` take the narrower type, which the union's signature cannot express without this. */
|
|
6
|
+
function form(spec) {
|
|
7
|
+
return spec;
|
|
8
|
+
}
|
|
9
|
+
const composedValueForms = [
|
|
10
|
+
form({
|
|
11
|
+
schema: derivedColor,
|
|
12
|
+
is: isDerivedColor,
|
|
13
|
+
render: renderDerivedColor,
|
|
14
|
+
authoring: derivedColorAuthoring
|
|
15
|
+
}),
|
|
16
|
+
form({
|
|
17
|
+
schema: gradient,
|
|
18
|
+
is: isGradient,
|
|
19
|
+
render: renderGradient,
|
|
20
|
+
authoring: gradientAuthoring
|
|
21
|
+
}),
|
|
22
|
+
form({
|
|
23
|
+
schema: transitionValue,
|
|
24
|
+
is: isTransition,
|
|
25
|
+
render: renderTransition,
|
|
26
|
+
authoring: transitionAuthoring
|
|
27
|
+
})
|
|
28
|
+
];
|
|
29
|
+
/** The forms' schemas, in registry order, for a value union to spread. */
|
|
30
|
+
const composedValueSchemas = composedValueForms.map((entry) => entry.schema);
|
|
31
|
+
/** The form a value takes, or `undefined` for anything that is not composed. A string or a number
|
|
32
|
+
* answers before the forms are asked, since every form is an object. */
|
|
33
|
+
function composedValueFormOf(value) {
|
|
34
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
35
|
+
return composedValueForms.find((entry) => entry.is(value));
|
|
36
|
+
}
|
|
37
|
+
/** Whether a value is one of the composed forms. */
|
|
38
|
+
function isComposedValue(value) {
|
|
39
|
+
return composedValueFormOf(value) !== void 0;
|
|
40
|
+
}
|
|
41
|
+
/** A composed value as CSS, with `render` resolving each part it holds. */
|
|
42
|
+
function renderComposedValue(value, render) {
|
|
43
|
+
const entry = composedValueFormOf(value);
|
|
44
|
+
if (!entry) throw new Error("not a composed value");
|
|
45
|
+
return entry.render(value, render);
|
|
46
|
+
}
|
|
47
|
+
/** The helper call that authors a composed value, or `undefined` when none does. */
|
|
48
|
+
function composedValueAuthoring(value) {
|
|
49
|
+
return composedValueFormOf(value)?.authoring(value);
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { composedValueAuthoring, composedValueFormOf, composedValueForms, composedValueSchemas, isComposedValue, renderComposedValue };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ComposedValue } from "./composed-values.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region src/entities/system/style-bag.d.ts
|
|
@@ -13,7 +13,7 @@ import { z } from "zod";
|
|
|
13
13
|
*/
|
|
14
14
|
type StyleValue = string | number | boolean | {
|
|
15
15
|
readonly __ref: string;
|
|
16
|
-
} |
|
|
16
|
+
} | ComposedValue | {
|
|
17
17
|
readonly [key: string]: StyleValue;
|
|
18
18
|
};
|
|
19
19
|
//#endregion
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isExtendedRef, isRef, ref, refSchema, splitRef } from "../../framework/utils/refs.js";
|
|
2
2
|
import { defineSubEntity } from "../../framework/defineSubEntity.js";
|
|
3
|
-
import {
|
|
3
|
+
import { composedValueSchemas } from "./composed-values.js";
|
|
4
4
|
import { isCssProperty, kebabCase } from "../../css/values.js";
|
|
5
5
|
import { valueSchemaOf } from "../../framework/value-domain.js";
|
|
6
6
|
import { glyphLeafOf, glyphPackageRef } from "./glyph-identity.js";
|
|
@@ -14,8 +14,7 @@ const styleValue = z.lazy(() => z.union([
|
|
|
14
14
|
z.number(),
|
|
15
15
|
z.boolean(),
|
|
16
16
|
refSchema,
|
|
17
|
-
|
|
18
|
-
gradient,
|
|
17
|
+
...composedValueSchemas,
|
|
19
18
|
glyphPackageRef,
|
|
20
19
|
z.record(z.string(), styleValue)
|
|
21
20
|
]));
|
|
@@ -25,8 +24,7 @@ const anatomyPropValue = z.lazy(() => z.union([
|
|
|
25
24
|
z.boolean(),
|
|
26
25
|
z.null(),
|
|
27
26
|
refSchema,
|
|
28
|
-
|
|
29
|
-
gradient,
|
|
27
|
+
...composedValueSchemas,
|
|
30
28
|
z.array(z.union([
|
|
31
29
|
z.string(),
|
|
32
30
|
z.number(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isRef, splitRef } from "../../framework/utils/refs.js";
|
|
2
|
-
import { resolveTokenValueUnder } from "../../framework/projections.js";
|
|
3
2
|
import { resolveValueType } from "../../css/value-type.js";
|
|
3
|
+
import { resolveTokenValueUnder } from "../../framework/projections.js";
|
|
4
4
|
import { matchEntities, searchEntities } from "../../framework/entity-search.js";
|
|
5
5
|
//#region src/entities/system/token-index.ts
|
|
6
6
|
/**
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Ref } from "../../framework/utils/refs.js";
|
|
2
|
+
import { Config } from "../../framework/Config.js";
|
|
3
|
+
import { RootFontSize } from "../../css/root-font-size.js";
|
|
4
|
+
|
|
5
|
+
//#region src/entities/system/token-values.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* A resolved value and the numbers a JS caller reads off it. `px` is present for a length in `px`,
|
|
8
|
+
* for a unitless number, and for a length in `rem` when the page's root font size fixes one; `ms` for
|
|
9
|
+
* a time in `s` or `ms`. Anything else — a color, a font stack, a percentage — is its CSS string only.
|
|
10
|
+
*/
|
|
11
|
+
interface TokenValueEntry {
|
|
12
|
+
readonly css: string;
|
|
13
|
+
readonly px?: number;
|
|
14
|
+
readonly ms?: number;
|
|
15
|
+
}
|
|
16
|
+
/** What a token resolved to, or why it did not. */
|
|
17
|
+
type TokenValueState = /** One value, the same under every mode and state. */{
|
|
18
|
+
readonly kind: 'value';
|
|
19
|
+
readonly value: TokenValueEntry;
|
|
20
|
+
}
|
|
21
|
+
/** A modifier decides it — an override on the token or on one it aliases — so it is only ever a
|
|
22
|
+
* `var()` on the page. */
|
|
23
|
+
| {
|
|
24
|
+
readonly kind: 'conditional';
|
|
25
|
+
}
|
|
26
|
+
/** Nothing to resolve to: an alias chain that never ends, a linked source this config did not
|
|
27
|
+
* reach, or a value that is not one scalar. */
|
|
28
|
+
| {
|
|
29
|
+
readonly kind: 'unresolved';
|
|
30
|
+
};
|
|
31
|
+
/** One token as the runtime module carries it. */
|
|
32
|
+
interface TokenValueRow {
|
|
33
|
+
readonly path: string;
|
|
34
|
+
/** The first path segment, which is the group a reader is addressed by. */
|
|
35
|
+
readonly group: string;
|
|
36
|
+
/** The rest of the path — `2.5` for `spacing/2.5`, `blue/3` for `spectrum/blue/3`. A path with no
|
|
37
|
+
* slash is its own group and alias. */
|
|
38
|
+
readonly alias: string;
|
|
39
|
+
/** The custom property the stylesheet declares it as, spelled by the config that owns it. */
|
|
40
|
+
readonly variable: string;
|
|
41
|
+
readonly state: TokenValueState;
|
|
42
|
+
}
|
|
43
|
+
/** The root font size of the page this config's sheet is for, as stated or the browser's own. A
|
|
44
|
+
* stored value already passed the setting's own check, so it parses. */
|
|
45
|
+
declare function rootFontSize(config: Config): RootFontSize;
|
|
46
|
+
/**
|
|
47
|
+
* Every token this config lists, in list order, as {@link TokenValueRow}s.
|
|
48
|
+
*
|
|
49
|
+
* Keyed on the token and settings slices, plus the attached sources every memo keys on: a token edit
|
|
50
|
+
* or a `rem` change recomputes, a component edit does not. A source's values reach here through the
|
|
51
|
+
* closure the artifact recorded whether or not the source is attached, so the two answer the same.
|
|
52
|
+
*/
|
|
53
|
+
declare const tokenValueRows: (config: Config) => readonly TokenValueRow[];
|
|
54
|
+
/**
|
|
55
|
+
* One token's row by ref or path, qualified or not. A qualified target is read in the source it
|
|
56
|
+
* names, so the row spells that source's variable and value; an unqualified one is this config's,
|
|
57
|
+
* including a row this config derived from a link.
|
|
58
|
+
*/
|
|
59
|
+
declare function tokenValueRow({
|
|
60
|
+
config,
|
|
61
|
+
target
|
|
62
|
+
}: {
|
|
63
|
+
config: Config;
|
|
64
|
+
target: string | Ref;
|
|
65
|
+
}): TokenValueRow | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Whether a `token:` target names something this config can answer for — an item, qualified or not,
|
|
68
|
+
* or a group. A group is what a read whose path a prop completes is an edge to.
|
|
69
|
+
*/
|
|
70
|
+
declare function tokenExists({
|
|
71
|
+
config,
|
|
72
|
+
target
|
|
73
|
+
}: {
|
|
74
|
+
config: Config;
|
|
75
|
+
target: string | Ref;
|
|
76
|
+
}): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Token path → what the build resolved it to, the augmentation the emitted `uds-env.d.ts` fills:
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* declare module '@yahoo/uds-create-config' {
|
|
82
|
+
* interface RegisteredTokenValues {
|
|
83
|
+
* 'spacing/1': { css: '0.25rem'; px: 4 };
|
|
84
|
+
* 'motion/fast': { css: '200ms'; ms: 200 };
|
|
85
|
+
* 'color/brand': {};
|
|
86
|
+
* }
|
|
87
|
+
* }
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* Every token is a key, so `var()` types against all of them; `css`, `px` and `ms` are present only
|
|
91
|
+
* where the token resolves to one value, which is what makes `px('brand')` on a colour an error.
|
|
92
|
+
*/
|
|
93
|
+
interface RegisteredTokenValues {}
|
|
94
|
+
/**
|
|
95
|
+
* One token group's readers. `var` is available for every token; the others only for the tokens the
|
|
96
|
+
* build resolved to one value, so a mode-dependent token is a type error everywhere but `var`.
|
|
97
|
+
*/
|
|
98
|
+
interface TokenGroupReader<All extends string = string, Static extends string = string, Px extends string = string, Ms extends string = string> {
|
|
99
|
+
/** `var(--uds-spacing-2_5)` — the reference the stylesheet's own rules use, so it follows modes. */
|
|
100
|
+
var(alias: All): string;
|
|
101
|
+
/** The resolved CSS value, `'0.625rem'`. */
|
|
102
|
+
value(alias: Static): string;
|
|
103
|
+
/** The value as CSS pixels: `rem` against the page's root font size (`settings.css.rem`), `px`
|
|
104
|
+
* as written, a unitless number as itself. */
|
|
105
|
+
px(alias: Px): number;
|
|
106
|
+
/** The value in milliseconds, for a duration or a delay. */
|
|
107
|
+
ms(alias: Ms): number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The readers a token ref offers, by what the build resolved that path to. A path the augmentation
|
|
111
|
+
* knows narrows to the readers its value supports, so `.px()` on a colour is a compile error; a path it
|
|
112
|
+
* does not know — before a build, or a template a prop fills in — keeps all four.
|
|
113
|
+
*/
|
|
114
|
+
type TokenReaders<P extends string> = P extends keyof RegisteredTokenValues ? ReadersOf<RegisteredTokenValues[P]> : ReadersOf<{
|
|
115
|
+
css: string;
|
|
116
|
+
px: number;
|
|
117
|
+
ms: number;
|
|
118
|
+
}>;
|
|
119
|
+
type ReadersOf<V> = {
|
|
120
|
+
/** `var(--uds-spacing-2_5)` — the reference the stylesheet's own rules use, so it follows modes. */var(): string;
|
|
121
|
+
} & (V extends {
|
|
122
|
+
css: string;
|
|
123
|
+
} ? {
|
|
124
|
+
value(): string;
|
|
125
|
+
} : unknown) & (V extends {
|
|
126
|
+
px: number;
|
|
127
|
+
} ? {
|
|
128
|
+
px(): number;
|
|
129
|
+
} : unknown) & (V extends {
|
|
130
|
+
ms: number;
|
|
131
|
+
} ? {
|
|
132
|
+
ms(): number;
|
|
133
|
+
} : unknown);
|
|
134
|
+
/** The reader names, which is what the build recognizes on a `Token.ref(…)`. */
|
|
135
|
+
declare const TOKEN_READERS: readonly ["var", "value", "px", "ms"];
|
|
136
|
+
type TokenReader = (typeof TOKEN_READERS)[number];
|
|
137
|
+
//#endregion
|
|
138
|
+
export { RegisteredTokenValues, TOKEN_READERS, TokenGroupReader, TokenReader, TokenReaders, TokenValueEntry, TokenValueRow, TokenValueState, rootFontSize, tokenExists, tokenValueRow, tokenValueRows };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { isRef, kindOf, pathOf, ref, splitRef } from "../../framework/utils/refs.js";
|
|
2
|
+
import { parseRootFontSize } from "../../css/root-font-size.js";
|
|
3
|
+
import { CSS_DEFAULTS, Settings } from "./Settings.js";
|
|
4
|
+
import { createSliceMemo } from "../../framework/memo.js";
|
|
5
|
+
import { cssVar, resolveTokenValue } from "../../framework/projections.js";
|
|
6
|
+
//#region src/entities/system/token-values.ts
|
|
7
|
+
/**
|
|
8
|
+
* A token as a VALUE a module can compute with.
|
|
9
|
+
*
|
|
10
|
+
* The stylesheet carries every token as a custom property, which is all a style prop needs. A render
|
|
11
|
+
* that hands a token to something outside the stylesheet — a third-party prop that wants a number, a
|
|
12
|
+
* layout measured in JS — needs the value itself, and the config is the only place it can come from
|
|
13
|
+
* without a hand-written table that drifts. This is that answer, computed once per build: a render
|
|
14
|
+
* reads it as `Token.ref('spacing/2.5').px()`, which the build resolves in place, and the emitted
|
|
15
|
+
* `components/_tokens.ts` module carries the tables a read through a prop needs. The readers are typed
|
|
16
|
+
* per path off {@link RegisteredTokenValues}, which the emitted `uds-env.d.ts` fills in.
|
|
17
|
+
*
|
|
18
|
+
* A token has a value only when nothing changes it: a token with an override, or aliasing one, is
|
|
19
|
+
* whatever the viewer's mode or state says on the page, so it is only ever reachable as its `var()`.
|
|
20
|
+
*/
|
|
21
|
+
const NUMERIC = /^(-?(?:\d+(?:\.\d*)?|\.\d+))(px|rem|ms|s)?$/;
|
|
22
|
+
function tokenValueEntry({ value, rem }) {
|
|
23
|
+
if (typeof value === "number") return {
|
|
24
|
+
css: String(value),
|
|
25
|
+
px: value
|
|
26
|
+
};
|
|
27
|
+
const css = value.trim();
|
|
28
|
+
const match = NUMERIC.exec(css);
|
|
29
|
+
if (!match) return { css };
|
|
30
|
+
const amount = Number(match[1]);
|
|
31
|
+
switch (match[2]) {
|
|
32
|
+
case void 0:
|
|
33
|
+
case "px": return {
|
|
34
|
+
css,
|
|
35
|
+
px: amount
|
|
36
|
+
};
|
|
37
|
+
case "rem": return rem === void 0 ? { css } : {
|
|
38
|
+
css,
|
|
39
|
+
px: amount * rem
|
|
40
|
+
};
|
|
41
|
+
case "ms": return {
|
|
42
|
+
css,
|
|
43
|
+
ms: amount
|
|
44
|
+
};
|
|
45
|
+
case "s": return {
|
|
46
|
+
css,
|
|
47
|
+
ms: amount * 1e3
|
|
48
|
+
};
|
|
49
|
+
default: return { css };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** The root font size of the page this config's sheet is for, as stated or the browser's own. A
|
|
53
|
+
* stored value already passed the setting's own check, so it parses. */
|
|
54
|
+
function rootFontSize(config) {
|
|
55
|
+
const css = Settings.get(config)?.css?.rem ?? CSS_DEFAULTS.rem;
|
|
56
|
+
return parseRootFontSize(css) ?? {
|
|
57
|
+
css,
|
|
58
|
+
px: void 0
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Every token this config lists, in list order, as {@link TokenValueRow}s.
|
|
63
|
+
*
|
|
64
|
+
* Keyed on the token and settings slices, plus the attached sources every memo keys on: a token edit
|
|
65
|
+
* or a `rem` change recomputes, a component edit does not. A source's values reach here through the
|
|
66
|
+
* closure the artifact recorded whether or not the source is attached, so the two answer the same.
|
|
67
|
+
*/
|
|
68
|
+
const tokenValueRows = createSliceMemo({
|
|
69
|
+
kinds: ["token", "settings"],
|
|
70
|
+
compute: (config) => {
|
|
71
|
+
const rem = rootFontSize(config).px;
|
|
72
|
+
return config.list("token").map((token) => {
|
|
73
|
+
const { path } = token;
|
|
74
|
+
const slash = path.indexOf("/");
|
|
75
|
+
const owner = config.configFor(ref("token", path)) ?? config;
|
|
76
|
+
return {
|
|
77
|
+
path,
|
|
78
|
+
group: slash > 0 ? path.slice(0, slash) : path,
|
|
79
|
+
alias: slash > 0 ? path.slice(slash + 1) : path,
|
|
80
|
+
variable: cssVar(owner, "token", path),
|
|
81
|
+
state: stateOf({
|
|
82
|
+
config,
|
|
83
|
+
path,
|
|
84
|
+
rem
|
|
85
|
+
})
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
function stateOf({ config, path, rem }) {
|
|
91
|
+
if (modeDecides(config, path)) return { kind: "conditional" };
|
|
92
|
+
const resolved = resolveTokenValue(config, path);
|
|
93
|
+
if (resolved === void 0) return { kind: "unresolved" };
|
|
94
|
+
return {
|
|
95
|
+
kind: "value",
|
|
96
|
+
value: tokenValueEntry({
|
|
97
|
+
value: resolved,
|
|
98
|
+
rem
|
|
99
|
+
})
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Path → its row, over the same memo as {@link tokenValueRows}. */
|
|
103
|
+
const tokenValueIndex = createSliceMemo({
|
|
104
|
+
kinds: ["token", "settings"],
|
|
105
|
+
compute: (config) => new Map(tokenValueRows(config).map((row) => [row.path, row]))
|
|
106
|
+
});
|
|
107
|
+
/** A bare path or a ref string, as a `token:` ref string. */
|
|
108
|
+
function tokenTarget(target) {
|
|
109
|
+
return typeof target === "string" && !target.includes(":") ? `token:${target}` : target;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* One token's row by ref or path, qualified or not. A qualified target is read in the source it
|
|
113
|
+
* names, so the row spells that source's variable and value; an unqualified one is this config's,
|
|
114
|
+
* including a row this config derived from a link.
|
|
115
|
+
*/
|
|
116
|
+
function tokenValueRow({ config, target }) {
|
|
117
|
+
const { path, source } = splitRef(tokenTarget(target));
|
|
118
|
+
const owner = source === void 0 ? config : config.sourceFor(source);
|
|
119
|
+
return owner ? tokenValueIndex(owner).get(path) : void 0;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Whether a `token:` target names something this config can answer for — an item, qualified or not,
|
|
123
|
+
* or a group. A group is what a read whose path a prop completes is an edge to.
|
|
124
|
+
*/
|
|
125
|
+
function tokenExists({ config, target }) {
|
|
126
|
+
const { path, qualifiedPath } = splitRef(tokenTarget(target));
|
|
127
|
+
if (config.resolve("token", qualifiedPath)) return true;
|
|
128
|
+
return config.groups("token").some((group) => group.path === path);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Whether a token's value depends on a modifier: it carries an override itself, or it aliases a token
|
|
132
|
+
* that does, however many hops away. Chased through the config each alias was authored in, the way
|
|
133
|
+
* `resolveTokenValue` chases the value.
|
|
134
|
+
*/
|
|
135
|
+
function modeDecides(config, path, seen = /* @__PURE__ */ new Set()) {
|
|
136
|
+
const body = config.resolve("token", path)?.toJSON();
|
|
137
|
+
if (!body) return false;
|
|
138
|
+
if (body.overrides && Object.keys(body.overrides).length > 0) return true;
|
|
139
|
+
const { value } = body;
|
|
140
|
+
if (!isRef(value) || kindOf(value) !== "token") return false;
|
|
141
|
+
if (seen.has(value.__ref)) return false;
|
|
142
|
+
seen.add(value.__ref);
|
|
143
|
+
const source = config.configFor(value);
|
|
144
|
+
return source ? modeDecides(source, pathOf(value), seen) : false;
|
|
145
|
+
}
|
|
146
|
+
/** The reader names, which is what the build recognizes on a `Token.ref(…)`. */
|
|
147
|
+
const TOKEN_READERS = [
|
|
148
|
+
"var",
|
|
149
|
+
"value",
|
|
150
|
+
"px",
|
|
151
|
+
"ms"
|
|
152
|
+
];
|
|
153
|
+
//#endregion
|
|
154
|
+
export { TOKEN_READERS, rootFontSize, tokenExists, tokenValueRow, tokenValueRows };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Ref } from "../../framework/utils/refs.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/entities/system/transition.d.ts
|
|
5
|
+
/** A timing part of a transition: a literal (`'150ms ease-out'`) or a ref to the entity that names it,
|
|
6
|
+
* a token or a motion role. */
|
|
7
|
+
type TimingValue = string | Ref;
|
|
8
|
+
interface TransitionSegment {
|
|
9
|
+
/** The CSS properties this segment animates. Each renders as its own `<property> <timing>` entry. */
|
|
10
|
+
readonly properties: readonly string[];
|
|
11
|
+
/** The duration and easing, together, as a ref to the entity that names them or a literal. */
|
|
12
|
+
readonly timing: TimingValue;
|
|
13
|
+
readonly delay?: TimingValue;
|
|
14
|
+
}
|
|
15
|
+
interface Transition {
|
|
16
|
+
readonly transition: readonly TransitionSegment[];
|
|
17
|
+
}
|
|
18
|
+
/** Whether a value is a composed transition — the test the renderers branch on. Every segment must
|
|
19
|
+
* carry its properties and timing, so a record that merely holds a list under `transition` is not
|
|
20
|
+
* one. */
|
|
21
|
+
declare function isTransition(value: unknown): value is Transition;
|
|
22
|
+
/**
|
|
23
|
+
* `transition({ properties: ['translate', 'scale'], timing: Token.ref('motion/expressive') })`.
|
|
24
|
+
* Several segments compose one transition whose properties run on different roles.
|
|
25
|
+
*/
|
|
26
|
+
declare function transition(...segments: readonly TransitionSegment[]): Transition;
|
|
27
|
+
/**
|
|
28
|
+
* A transition as CSS — `background-color var(--uds-motion-color), box-shadow var(--uds-motion-color)`.
|
|
29
|
+
*
|
|
30
|
+
* `render` resolves the timing parts. It is passed in rather than imported because resolving a ref
|
|
31
|
+
* to a `var()` is the projection layer's job and this module is the value's shape.
|
|
32
|
+
*/
|
|
33
|
+
declare function renderTransition(value: Transition, render: (part: TimingValue) => string): string;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { TimingValue, Transition, TransitionSegment, isTransition, renderTransition, transition };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { refSchema } from "../../framework/utils/refs.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/entities/system/transition.ts
|
|
4
|
+
/**
|
|
5
|
+
* A CSS transition composed from the properties it animates and the timing they run on —
|
|
6
|
+
* `transition({ properties: ['background-color', 'box-shadow'], timing: Token.ref('motion/color') })`.
|
|
7
|
+
*
|
|
8
|
+
* A design system names its timing once, as a motion role (`motion/color` is "150ms linear"), and a
|
|
9
|
+
* transition is that role applied to some properties. Spelled as one string the role is copied into
|
|
10
|
+
* the transition and stops following the token: retune the duration and the transition keeps the old
|
|
11
|
+
* one. Held as parts, the role is a ref, so it renders as a `var()` and the dependency graph sees it.
|
|
12
|
+
*
|
|
13
|
+
* Usable wherever a style value is — a token's value, a style bag — for the same reason a gradient
|
|
14
|
+
* is: it is a value, not its own kind.
|
|
15
|
+
*/
|
|
16
|
+
const timingValue = z.union([z.string(), refSchema]);
|
|
17
|
+
const transitionValue = z.strictObject({ transition: z.array(z.strictObject({
|
|
18
|
+
properties: z.array(z.string()).min(1),
|
|
19
|
+
timing: timingValue,
|
|
20
|
+
delay: timingValue.optional()
|
|
21
|
+
})).min(1) });
|
|
22
|
+
/** Whether a value is a composed transition — the test the renderers branch on. Every segment must
|
|
23
|
+
* carry its properties and timing, so a record that merely holds a list under `transition` is not
|
|
24
|
+
* one. */
|
|
25
|
+
function isTransition(value) {
|
|
26
|
+
if (typeof value !== "object" || value === null) return false;
|
|
27
|
+
const segments = value.transition;
|
|
28
|
+
return Array.isArray(segments) && segments.every((segment) => typeof segment === "object" && segment !== null && Array.isArray(segment.properties) && "timing" in segment);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `transition({ properties: ['translate', 'scale'], timing: Token.ref('motion/expressive') })`.
|
|
32
|
+
* Several segments compose one transition whose properties run on different roles.
|
|
33
|
+
*/
|
|
34
|
+
function transition(...segments) {
|
|
35
|
+
return { transition: segments };
|
|
36
|
+
}
|
|
37
|
+
/** The `transition(...)` call that authors a stored value: its segments are the arguments. */
|
|
38
|
+
function transitionAuthoring(value) {
|
|
39
|
+
return {
|
|
40
|
+
helper: "transition",
|
|
41
|
+
args: value.transition
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A transition as CSS — `background-color var(--uds-motion-color), box-shadow var(--uds-motion-color)`.
|
|
46
|
+
*
|
|
47
|
+
* `render` resolves the timing parts. It is passed in rather than imported because resolving a ref
|
|
48
|
+
* to a `var()` is the projection layer's job and this module is the value's shape.
|
|
49
|
+
*/
|
|
50
|
+
function renderTransition(value, render) {
|
|
51
|
+
return value.transition.flatMap((segment) => {
|
|
52
|
+
const timing = [segment.timing, segment.delay].filter((part) => part !== void 0).map(render);
|
|
53
|
+
return segment.properties.map((property) => [property, ...timing].join(" "));
|
|
54
|
+
}).join(", ");
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { isTransition, renderTransition, transition, transitionAuthoring, transitionValue };
|
|
@@ -72,7 +72,7 @@ interface RecordedSource {
|
|
|
72
72
|
* The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
|
|
73
73
|
* that replaced it (see `detectedWireVersion`).
|
|
74
74
|
*/
|
|
75
|
-
declare const SERIALIZED_CONFIG_VERSION =
|
|
75
|
+
declare const SERIALIZED_CONFIG_VERSION = 20260923160425;
|
|
76
76
|
interface SerializedConfig {
|
|
77
77
|
/** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
|
|
78
78
|
readonly version: number;
|
package/dist/framework/Config.js
CHANGED
|
@@ -4836,7 +4836,7 @@ var Config = class Config {
|
|
|
4836
4836
|
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
4837
4837
|
hydrateFrom(stored, options) {
|
|
4838
4838
|
const claimed = schemaVersionOf(stored);
|
|
4839
|
-
if (claimed >
|
|
4839
|
+
if (claimed > 20260923160425) throw new SchemaVersionTooNew(claimed);
|
|
4840
4840
|
const wire = upgradeSerializedConfig(stored);
|
|
4841
4841
|
if (!Array.isArray(wire.ownedKinds) || typeof wire.items !== "object" || wire.items === null) throw new ConfigFormatError(`Config "${stored.name ?? "system-config"}" cannot be hydrated: this is not a serialized config-v2 config (no "ownedKinds"/"items"). A pre-cutover build artifact converts through the registered cutover migration — import the config type's module (e.g. \`configs/system\`) before hydrating, or port the repo once with \`uds migrate\`.`);
|
|
4842
4842
|
const leftover = wire.options;
|
|
@@ -355,4 +355,4 @@ type OpInput<E extends {
|
|
|
355
355
|
input: infer I extends z.ZodTypeAny;
|
|
356
356
|
} ? z.input<I> : never;
|
|
357
357
|
//#endregion
|
|
358
|
-
export { EntityClass, GroupComputedInput, ItemOf, OpInput, defineEntity };
|
|
358
|
+
export { EntityClass, GroupComputedInput, ItemOf, KindRef, OpInput, defineEntity };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRef, kindOf, pathOf, ref, refSchema, sourceOf } from "./utils/refs.js";
|
|
1
|
+
import { isRef, kindOf, pathOf, ref, refSchema, refTo, sourceOf } from "./utils/refs.js";
|
|
2
2
|
import { CONFIG_OPERATION_KIND } from "./config-operation-kind.js";
|
|
3
3
|
import { resolveFields } from "./schemas.js";
|
|
4
4
|
import { buildKind } from "./registry.js";
|
|
@@ -129,10 +129,7 @@ function defineEntity(spec) {
|
|
|
129
129
|
authoredValue: spec.authoredValue,
|
|
130
130
|
invariants: spec.invariants
|
|
131
131
|
});
|
|
132
|
-
const schema =
|
|
133
|
-
ref: true,
|
|
134
|
-
refKind: spec.kind
|
|
135
|
-
});
|
|
132
|
+
const schema = refTo(spec.kind);
|
|
136
133
|
/**
|
|
137
134
|
* The path a caller named — from a path, a ref object, or a ref string (`component:Box`, which is
|
|
138
135
|
* what a spec element's `type` is, so a caller reading one needn't take it apart first).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isRef, kindOf, memberOf, pathOf, ref, refLeaf, sourceOf, splitRef } from "./utils/refs.js";
|
|
2
2
|
import { forwardedPropName, forwardsOf } from "../entities/system/element.js";
|
|
3
3
|
import { classPrefixOf, varPrefixOf } from "../entities/system/Settings.js";
|
|
4
|
-
import {
|
|
4
|
+
import { isComposedValue, renderComposedValue } from "../entities/system/composed-values.js";
|
|
5
5
|
import { fontFamilyStack } from "../entities/system/Font.js";
|
|
6
6
|
import { listedIn } from "./utils/enumerated.js";
|
|
7
7
|
import { modifierCategory } from "../entities/system/Modifier.js";
|
|
@@ -488,8 +488,7 @@ function resolveTokenValueUnder(config, path, condition, seen = /* @__PURE__ */
|
|
|
488
488
|
* ordinary call — it is `config`'s own prefix. */
|
|
489
489
|
function renderStyleValue(config, value, varPrefix) {
|
|
490
490
|
if (Array.isArray(value)) return value.map((part) => renderStyleValue(config, part, varPrefix)).join(" ");
|
|
491
|
-
if (
|
|
492
|
-
if (isGradient(value)) return renderGradient(value, (color) => renderStyleValue(config, color, varPrefix));
|
|
491
|
+
if (isComposedValue(value)) return renderComposedValue(value, (part) => renderStyleValue(config, part, varPrefix));
|
|
493
492
|
if (isRef(value)) {
|
|
494
493
|
const source = config.configFor(value);
|
|
495
494
|
if (!source) return "";
|
|
@@ -15,7 +15,7 @@ import { Patch, SerializedConfig } from "./Config.js";
|
|
|
15
15
|
* sets this constant to its own older timestamp; the merge ref then carries a registered migration
|
|
16
16
|
* newer than the constant, and the assertion fails until the author re-mints.
|
|
17
17
|
*/
|
|
18
|
-
declare const CURRENT_SCHEMA_VERSION =
|
|
18
|
+
declare const CURRENT_SCHEMA_VERSION = 20260923160425;
|
|
19
19
|
/** What the walkers need to see of an envelope — loose on purpose, so a typed `SerializedConfig`
|
|
20
20
|
* and a raw old-shape record both flow in without casts. */
|
|
21
21
|
interface VersionStamped {
|
|
@@ -13,7 +13,7 @@ import { ConfigFormatError } from "./rejection.js";
|
|
|
13
13
|
* sets this constant to its own older timestamp; the merge ref then carries a registered migration
|
|
14
14
|
* newer than the constant, and the assertion fails until the author re-mints.
|
|
15
15
|
*/
|
|
16
|
-
const CURRENT_SCHEMA_VERSION =
|
|
16
|
+
const CURRENT_SCHEMA_VERSION = 20260923160425;
|
|
17
17
|
/** The mint that moved `options` into the `settings` kind — restated here, rather than imported
|
|
18
18
|
* from the migration that owns it, because that migration imports this module. */
|
|
19
19
|
const SETTINGS_AS_KIND_VERSION = 20260908171448;
|
|
@@ -45,7 +45,7 @@ function registerSchemaMigrations(...migrations) {
|
|
|
45
45
|
for (const migration of migrations) {
|
|
46
46
|
if (migration.version !== 1 && !isMintTimestamp(migration.version)) throw new Error(`Schema migration version ${migration.version} is not a mint timestamp — mint one with \`date +%Y%m%d%H%M%S\`, like a supabase migration.`);
|
|
47
47
|
if (REGISTERED.has(migration.version)) throw new Error(`A schema migration at version ${migration.version} is already registered.`);
|
|
48
|
-
if (migration.version >
|
|
48
|
+
if (migration.version > 20260923160425) throw new Error(`Schema migration ${migration.version} is newer than CURRENT_SCHEMA_VERSION (${CURRENT_SCHEMA_VERSION}) — a stale branch merged, or the mint forgot to bump the constant. Re-mint: set CURRENT_SCHEMA_VERSION to the newest migration version.`);
|
|
49
49
|
REGISTERED.set(migration.version, migration);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -102,7 +102,7 @@ function pendingFor(version, target, options) {
|
|
|
102
102
|
* Already-current input passes through untouched — same reference, so hot paths pay nothing.
|
|
103
103
|
*/
|
|
104
104
|
function upgradeSerializedConfig(json, options) {
|
|
105
|
-
const target = options?.target ??
|
|
105
|
+
const target = options?.target ?? 20260923160425;
|
|
106
106
|
const version = detectedWireVersion(json);
|
|
107
107
|
if (version > target) throw new SchemaVersionTooNew(version, target);
|
|
108
108
|
const raw = json;
|
|
@@ -160,7 +160,7 @@ function upgradedSources(json, options) {
|
|
|
160
160
|
* meaning under the current schema and replay skips it — reported by the caller, not swallowed here.
|
|
161
161
|
*/
|
|
162
162
|
function upgradePatch(patch, writtenAt, options) {
|
|
163
|
-
const target = options?.target ??
|
|
163
|
+
const target = options?.target ?? 20260923160425;
|
|
164
164
|
if (writtenAt > target) throw new SchemaVersionTooNew(writtenAt, target);
|
|
165
165
|
let current = patch;
|
|
166
166
|
for (const migration of pendingFor(writtenAt, target, options)) {
|
|
@@ -179,7 +179,7 @@ function upgradePatch(patch, writtenAt, options) {
|
|
|
179
179
|
* stamp would cause.
|
|
180
180
|
*/
|
|
181
181
|
function upgradeDraftEntries(draft, options) {
|
|
182
|
-
const target = options?.target ??
|
|
182
|
+
const target = options?.target ?? 20260923160425;
|
|
183
183
|
const entries = [];
|
|
184
184
|
const held = [];
|
|
185
185
|
const retired = [];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/framework/utils/identifier.d.ts
|
|
2
|
+
/** What may be written as one JavaScript identifier — a prop name, an export, a binding. */
|
|
3
|
+
/** Whether `name` has the shape of an identifier. A reserved word has the shape and still cannot be
|
|
4
|
+
* bound; {@link isReservedWord} is that question. */
|
|
5
|
+
declare function isIdentifier(name: string): boolean;
|
|
6
|
+
declare function isReservedWord(name: string): boolean;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { isIdentifier, isReservedWord };
|