@yahoo/uds-create-config 5.61.0 → 5.62.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/react-native-system.d.ts +9 -0
- package/dist/configs/system.js +2 -0
- package/dist/entities/native/NativeSettings.d.ts +9 -0
- package/dist/entities/native/NativeStyleProperty.js +4 -3
- package/dist/entities/system/Settings.d.ts +18 -0
- package/dist/entities/system/Settings.js +35 -16
- package/dist/framework/Config.d.ts +1 -1
- package/dist/framework/Config.js +1 -1
- package/dist/framework/layer-styles.d.ts +11 -1
- package/dist/framework/layer-styles.js +69 -25
- package/dist/framework/prop-surface.js +18 -3
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/framework/utils/color.d.ts +14 -0
- package/dist/framework/utils/color.js +130 -0
- package/dist/framework/utils/css-length.d.ts +12 -0
- package/dist/framework/utils/css-length.js +16 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/lint/context.d.ts +254 -0
- package/dist/lint/context.js +622 -0
- package/dist/lint/edits.d.ts +77 -0
- package/dist/lint/edits.js +121 -0
- package/dist/lint/index.d.ts +5 -0
- package/dist/lint/index.js +5 -0
- package/dist/lint/lint.d.ts +107 -0
- package/dist/lint/lint.js +91 -0
- package/dist/lint/rules.d.ts +337 -0
- package/dist/lint/rules.js +466 -0
- package/dist/migrations/20261027205147_lint_ignore.d.ts +6 -0
- package/dist/migrations/20261027205147_lint_ignore.js +8 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -2
|
@@ -65,6 +65,15 @@ declare const ReactNativeSystemDefinition: ConfigClass<{
|
|
|
65
65
|
off: "off";
|
|
66
66
|
warn: "warn";
|
|
67
67
|
}>>>;
|
|
68
|
+
ignore: _$zod.ZodOptional<_$zod.ZodArray<_$zod.ZodObject<{
|
|
69
|
+
rule: _$zod.ZodString;
|
|
70
|
+
component: _$zod.ZodOptional<_$zod.ZodString>;
|
|
71
|
+
token: _$zod.ZodOptional<_$zod.ZodString>;
|
|
72
|
+
layer: _$zod.ZodOptional<_$zod.ZodString>;
|
|
73
|
+
prop: _$zod.ZodOptional<_$zod.ZodString>;
|
|
74
|
+
when: _$zod.ZodOptional<_$zod.ZodString>;
|
|
75
|
+
reason: _$zod.ZodString;
|
|
76
|
+
}, _$zod_v4_core0.$strict>>>;
|
|
68
77
|
}, _$zod_v4_core0.$strict>>;
|
|
69
78
|
}, "css" | "playground" | "canvas"> & {
|
|
70
79
|
canvas: _$zod.ZodOptional<_$zod.ZodObject<{
|
package/dist/configs/system.js
CHANGED
|
@@ -32,6 +32,7 @@ import { linkTypeMigration } from "../migrations/20261022184639_link_type.js";
|
|
|
32
32
|
import { conditionalDefaultsMigration } from "../migrations/20261023152030_conditional_defaults.js";
|
|
33
33
|
import { timingValueMigration } from "../migrations/20261026125912_timing_value.js";
|
|
34
34
|
import { variantValuesListWithBodiesMigration } from "../migrations/20261027184442_variant_values_list_with_bodies.js";
|
|
35
|
+
import { lintIgnoreMigration } from "../migrations/20261027205147_lint_ignore.js";
|
|
35
36
|
import { CanvasRole } from "../entities/system/CanvasRole.js";
|
|
36
37
|
import { GlobalStyle } from "../entities/system/GlobalStyle.js";
|
|
37
38
|
import { Guidance } from "../entities/system/Guidance.js";
|
|
@@ -197,5 +198,6 @@ if (!registeredSchemaMigrations().has(20261022184639)) registerSchemaMigrations(
|
|
|
197
198
|
if (!registeredSchemaMigrations().has(20261023152030)) registerSchemaMigrations(conditionalDefaultsMigration);
|
|
198
199
|
if (!registeredSchemaMigrations().has(20261026125912)) registerSchemaMigrations(timingValueMigration);
|
|
199
200
|
if (!registeredSchemaMigrations().has(20261027184442)) registerSchemaMigrations(variantValuesListWithBodiesMigration);
|
|
201
|
+
if (!registeredSchemaMigrations().has(20261027205147)) registerSchemaMigrations(lintIgnoreMigration);
|
|
200
202
|
//#endregion
|
|
201
203
|
export { System };
|
|
@@ -54,6 +54,15 @@ declare const NativeSettings: EntityClass<z.ZodObject<Omit<{
|
|
|
54
54
|
off: "off";
|
|
55
55
|
warn: "warn";
|
|
56
56
|
}>>>;
|
|
57
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
58
|
+
rule: z.ZodString;
|
|
59
|
+
component: z.ZodOptional<z.ZodString>;
|
|
60
|
+
token: z.ZodOptional<z.ZodString>;
|
|
61
|
+
layer: z.ZodOptional<z.ZodString>;
|
|
62
|
+
prop: z.ZodOptional<z.ZodString>;
|
|
63
|
+
when: z.ZodOptional<z.ZodString>;
|
|
64
|
+
reason: z.ZodString;
|
|
65
|
+
}, z.core.$strict>>>;
|
|
57
66
|
}, z.core.$strict>>;
|
|
58
67
|
}, "css" | "playground" | "canvas"> & {
|
|
59
68
|
canvas: z.ZodOptional<z.ZodObject<{
|
|
@@ -13,13 +13,14 @@ import { z } from "zod";
|
|
|
13
13
|
* `marginHorizontal`) would be our judgment, hand-maintained — the native catalog authors native
|
|
14
14
|
* names directly. `classPrefix` is removed: it names emitted CSS classes, and native emits none.
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
/** A CSS property as React Native spells it (`background-color` → `backgroundColor`). */
|
|
17
|
+
const rnStyleName = (property) => property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
17
18
|
/** One React Native style key, as RN's own types declare them. */
|
|
18
19
|
const rnStyleKey = z.enum(RN_STYLE_KEYS);
|
|
19
20
|
const NativeStyleProperty = StyleProperty.extend({
|
|
20
21
|
entityName: "NativeStyleProperty",
|
|
21
22
|
fields: {
|
|
22
|
-
properties: z.array(z.string().transform(
|
|
23
|
+
properties: z.array(z.string().transform(rnStyleName).pipe(rnStyleKey)),
|
|
23
24
|
values: z.array(stylePropValueSchema()).check((ctx) => {
|
|
24
25
|
ctx.value.forEach((entry, index) => {
|
|
25
26
|
const bag = entry.value;
|
|
@@ -46,4 +47,4 @@ const NativeStyleProperty = StyleProperty.extend({
|
|
|
46
47
|
}
|
|
47
48
|
});
|
|
48
49
|
//#endregion
|
|
49
|
-
export { NativeStyleProperty, rnStyleKey };
|
|
50
|
+
export { NativeStyleProperty, rnStyleKey, rnStyleName };
|
|
@@ -104,6 +104,15 @@ declare const LintSectionDefinition: SubEntityClass<z.ZodObject<{
|
|
|
104
104
|
off: "off";
|
|
105
105
|
warn: "warn";
|
|
106
106
|
}>>>;
|
|
107
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
108
|
+
rule: z.ZodString;
|
|
109
|
+
component: z.ZodOptional<z.ZodString>;
|
|
110
|
+
token: z.ZodOptional<z.ZodString>;
|
|
111
|
+
layer: z.ZodOptional<z.ZodString>;
|
|
112
|
+
prop: z.ZodOptional<z.ZodString>;
|
|
113
|
+
when: z.ZodOptional<z.ZodString>;
|
|
114
|
+
reason: z.ZodString;
|
|
115
|
+
}, z.core.$strict>>>;
|
|
107
116
|
}, z.core.$strict>>;
|
|
108
117
|
interface LintSectionClass extends Named<typeof LintSectionDefinition> {}
|
|
109
118
|
declare const LintSection: LintSectionClass;
|
|
@@ -150,6 +159,15 @@ declare const SettingsDefinition: EntityClass<z.ZodObject<{
|
|
|
150
159
|
off: "off";
|
|
151
160
|
warn: "warn";
|
|
152
161
|
}>>>;
|
|
162
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
163
|
+
rule: z.ZodString;
|
|
164
|
+
component: z.ZodOptional<z.ZodString>;
|
|
165
|
+
token: z.ZodOptional<z.ZodString>;
|
|
166
|
+
layer: z.ZodOptional<z.ZodString>;
|
|
167
|
+
prop: z.ZodOptional<z.ZodString>;
|
|
168
|
+
when: z.ZodOptional<z.ZodString>;
|
|
169
|
+
reason: z.ZodString;
|
|
170
|
+
}, z.core.$strict>>>;
|
|
153
171
|
}, z.core.$strict>>;
|
|
154
172
|
}, z.core.$strict>, z.ZodObject<Record<never, never>, z.core.$strip>, "settings", {}, {
|
|
155
173
|
/**
|
|
@@ -171,22 +171,41 @@ const PlaygroundSection = defineSubEntity({
|
|
|
171
171
|
singleton: true,
|
|
172
172
|
fields: playgroundFields
|
|
173
173
|
});
|
|
174
|
-
const lintFields = z.strictObject({
|
|
175
|
-
/**
|
|
176
|
-
* How loud each authoring lint rule is, by rule or by group — `{ editability: 'off',
|
|
177
|
-
* 'inline-style-on-layer': 'error' }`. A rule's own entry wins over its group's; a rule named
|
|
178
|
-
* neither way takes its default, a warning for most. A group never turns on a rule that is off by
|
|
179
|
-
* default. Keyed by name rather than an enum, so a rule the build adds later needs no new schema; a
|
|
180
|
-
* name that is no rule or group is reported, not refused.
|
|
181
|
-
*/
|
|
182
|
-
rules: z.record(z.string(), z.enum([
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
])).meta({
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}).optional()
|
|
174
|
+
const lintFields = z.strictObject({
|
|
175
|
+
/**
|
|
176
|
+
* How loud each authoring lint rule is, by rule or by group — `{ editability: 'off',
|
|
177
|
+
* 'inline-style-on-layer': 'error' }`. A rule's own entry wins over its group's; a rule named
|
|
178
|
+
* neither way takes its default, a warning for most. A group never turns on a rule that is off by
|
|
179
|
+
* default. Keyed by name rather than an enum, so a rule the build adds later needs no new schema; a
|
|
180
|
+
* name that is no rule or group is reported, not refused.
|
|
181
|
+
*/
|
|
182
|
+
rules: z.record(z.string(), z.enum([
|
|
183
|
+
"off",
|
|
184
|
+
"warn",
|
|
185
|
+
"error"
|
|
186
|
+
])).meta({
|
|
187
|
+
title: "Rules",
|
|
188
|
+
description: "A severity — `'off'`, `'warn'` or `'error'` — per authoring lint rule or per group of them (`portability`, `editability`, `maintainability`, `compatibility`, `nursery`): `{ editability: 'off' }` for a system only edited in its source, never through Create or the MCP tools. A `nursery` rule runs only when named on its own. A rule's own entry wins over its group's. Unset, most rules warn. An `'error'` makes `uds lint` exit with status 1, so a CI step that runs it fails. One site is quieted with `// uds-lint-ignore <rule>: <why>` on its line or the line above."
|
|
189
|
+
}).optional(),
|
|
190
|
+
/**
|
|
191
|
+
* Findings the system has decided to keep, each with why: Create's "Ignore", and the config-side
|
|
192
|
+
* twin of a `// uds-lint-ignore` comment, for a site Create edits rather than one a line spells. An
|
|
193
|
+
* entry quiets its rule on the component (and the layer, style key and rule it names, when it names
|
|
194
|
+
* them), so a later edit that moves a line doesn't drop it.
|
|
195
|
+
*/
|
|
196
|
+
ignore: z.array(z.strictObject({
|
|
197
|
+
rule: z.string(),
|
|
198
|
+
component: z.string().optional(),
|
|
199
|
+
token: z.string().optional(),
|
|
200
|
+
layer: z.string().optional(),
|
|
201
|
+
prop: z.string().optional(),
|
|
202
|
+
when: z.string().optional(),
|
|
203
|
+
reason: z.string().min(1)
|
|
204
|
+
})).meta({
|
|
205
|
+
title: "Ignored findings",
|
|
206
|
+
description: "Findings kept on purpose, each with its reason: a rule on a component, narrowed to a layer, a style key and the rule it is written in when those are given, or on a token. `uds lint` and Create both leave them out."
|
|
207
|
+
}).optional()
|
|
208
|
+
});
|
|
190
209
|
const LintSection = defineSubEntity({
|
|
191
210
|
name: "lint",
|
|
192
211
|
label: "Lint",
|
|
@@ -74,7 +74,7 @@ interface RecordedSource {
|
|
|
74
74
|
* The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
|
|
75
75
|
* that replaced it (see `detectedWireVersion`).
|
|
76
76
|
*/
|
|
77
|
-
declare const SERIALIZED_CONFIG_VERSION =
|
|
77
|
+
declare const SERIALIZED_CONFIG_VERSION = 20261027205147;
|
|
78
78
|
interface SerializedConfig {
|
|
79
79
|
/** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
|
|
80
80
|
readonly version: number;
|
package/dist/framework/Config.js
CHANGED
|
@@ -6257,7 +6257,7 @@ var Config = class Config {
|
|
|
6257
6257
|
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
6258
6258
|
hydrateFrom(stored, options) {
|
|
6259
6259
|
const claimed = schemaVersionOf(stored);
|
|
6260
|
-
if (claimed >
|
|
6260
|
+
if (claimed > 20261027205147) throw new SchemaVersionTooNew(claimed);
|
|
6261
6261
|
const wire = upgradeSerializedConfig(stored);
|
|
6262
6262
|
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.`);
|
|
6263
6263
|
const leftover = wire.options;
|
|
@@ -26,6 +26,16 @@ interface LayerStyleOptions {
|
|
|
26
26
|
*/
|
|
27
27
|
readonly forced?: readonly string[];
|
|
28
28
|
}
|
|
29
|
+
/** One resolved style value, and the style rule that wrote it, by its key in `styles` (`*`, `size=sm`). */
|
|
30
|
+
interface LayerDeclaration {
|
|
31
|
+
readonly value: unknown;
|
|
32
|
+
readonly rule: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The resolved style bag per layer, for the props an instance was given, with the rule each value
|
|
36
|
+
* came from: what a lint reports at, and writes a fix into.
|
|
37
|
+
*/
|
|
38
|
+
declare function layerDeclarations(config: Config, path: string, props?: Record<string, unknown>, options?: LayerStyleOptions): Record<string, Record<string, LayerDeclaration>>;
|
|
29
39
|
/**
|
|
30
40
|
* The resolved style bag per layer, for the props an instance was given.
|
|
31
41
|
*
|
|
@@ -34,4 +44,4 @@ interface LayerStyleOptions {
|
|
|
34
44
|
*/
|
|
35
45
|
declare function layerStyles(config: Config, path: string, props?: Record<string, unknown>, options?: LayerStyleOptions): Record<string, Record<string, unknown>>;
|
|
36
46
|
//#endregion
|
|
37
|
-
export { LayerStyleOptions, compareComponentRules, layerStyles, ruleApplies };
|
|
47
|
+
export { LayerDeclaration, LayerStyleOptions, compareComponentRules, layerDeclarations, layerStyles, ruleApplies };
|
|
@@ -107,26 +107,21 @@ function ruleSpecificity(when) {
|
|
|
107
107
|
return Number(keys.length > interactions) + interactions;
|
|
108
108
|
}
|
|
109
109
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* Layers with nothing to say are absent rather than empty, so a caller can tell "no styling" from
|
|
113
|
-
* "styled with nothing".
|
|
110
|
+
* A component's rules with each one's declarations already collected, per config and forced
|
|
111
|
+
* modifiers: every cell of a matrix reads the same rules, and only which of them apply changes.
|
|
114
112
|
*/
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const declarations = [];
|
|
128
|
-
Object.values(styles).forEach((rule, index) => {
|
|
129
|
-
if (!ruleApplies(rule.when, state)) return;
|
|
113
|
+
const prepared = /* @__PURE__ */ new WeakMap();
|
|
114
|
+
function preparedRules({ config, path, styles, forced }) {
|
|
115
|
+
let byComponent = prepared.get(config);
|
|
116
|
+
if (!byComponent) {
|
|
117
|
+
byComponent = /* @__PURE__ */ new Map();
|
|
118
|
+
prepared.set(config, byComponent);
|
|
119
|
+
}
|
|
120
|
+
const key = `${path}\n${forced.join(",")}`;
|
|
121
|
+
const hit = byComponent.get(key);
|
|
122
|
+
if (hit) return hit;
|
|
123
|
+
const rules = Object.values(styles).map((rule, index) => {
|
|
124
|
+
const collected = [];
|
|
130
125
|
for (const [layer, bag] of Object.entries(rule.layers ?? {})) collectBag({
|
|
131
126
|
bag: resolvedBag({
|
|
132
127
|
config,
|
|
@@ -134,23 +129,72 @@ function layerStyles(config, path, props = {}, options = {}) {
|
|
|
134
129
|
}),
|
|
135
130
|
forced,
|
|
136
131
|
specificity: ruleSpecificity(rule.when),
|
|
137
|
-
push: (
|
|
132
|
+
push: (name, value, specificity) => collected.push({
|
|
138
133
|
layer,
|
|
139
|
-
key,
|
|
134
|
+
key: name,
|
|
140
135
|
value,
|
|
141
136
|
specificity,
|
|
142
137
|
rule: index,
|
|
143
|
-
order:
|
|
138
|
+
order: collected.length
|
|
144
139
|
})
|
|
145
140
|
});
|
|
141
|
+
return {
|
|
142
|
+
when: rule.when,
|
|
143
|
+
collected
|
|
144
|
+
};
|
|
146
145
|
});
|
|
146
|
+
const answer = {
|
|
147
|
+
keys: Object.keys(styles),
|
|
148
|
+
rules
|
|
149
|
+
};
|
|
150
|
+
byComponent.set(key, answer);
|
|
151
|
+
return answer;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The resolved style bag per layer, for the props an instance was given, with the rule each value
|
|
155
|
+
* came from: what a lint reports at, and writes a fix into.
|
|
156
|
+
*/
|
|
157
|
+
function layerDeclarations(config, path, props = {}, options = {}) {
|
|
158
|
+
const body = config.resolve("component", path)?.toJSON();
|
|
159
|
+
const { styles = {} } = body ?? {};
|
|
160
|
+
const state = {
|
|
161
|
+
...defaultsFor({
|
|
162
|
+
body,
|
|
163
|
+
given: props
|
|
164
|
+
}),
|
|
165
|
+
...props
|
|
166
|
+
};
|
|
167
|
+
const forced = (options.forced ?? []).map((name) => name.replace(/^_/, ""));
|
|
168
|
+
for (const name of forced) state[`_${name}`] = true;
|
|
169
|
+
const { keys, rules } = preparedRules({
|
|
170
|
+
config,
|
|
171
|
+
path,
|
|
172
|
+
styles,
|
|
173
|
+
forced
|
|
174
|
+
});
|
|
175
|
+
const declarations = [];
|
|
176
|
+
for (const { when, collected } of rules) if (ruleApplies(when, state)) declarations.push(...collected);
|
|
147
177
|
declarations.sort((a, b) => a.specificity - b.specificity || a.rule - b.rule || a.order - b.order);
|
|
148
178
|
const out = {};
|
|
149
|
-
for (const { layer, key, value } of declarations) {
|
|
179
|
+
for (const { layer, key, value, rule } of declarations) {
|
|
150
180
|
out[layer] ??= {};
|
|
151
|
-
out[layer][key] =
|
|
181
|
+
out[layer][key] = {
|
|
182
|
+
value,
|
|
183
|
+
rule: keys[rule] ?? "*"
|
|
184
|
+
};
|
|
152
185
|
}
|
|
153
186
|
return out;
|
|
154
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* The resolved style bag per layer, for the props an instance was given.
|
|
190
|
+
*
|
|
191
|
+
* Layers with nothing to say are absent rather than empty, so a caller can tell "no styling" from
|
|
192
|
+
* "styled with nothing".
|
|
193
|
+
*/
|
|
194
|
+
function layerStyles(config, path, props = {}, options = {}) {
|
|
195
|
+
const out = {};
|
|
196
|
+
for (const [layer, bag] of Object.entries(layerDeclarations(config, path, props, options))) out[layer] = Object.fromEntries(Object.entries(bag).map(([key, { value }]) => [key, value]));
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
155
199
|
//#endregion
|
|
156
|
-
export { compareComponentRules, conditionHolds, layerStyles, ruleApplies, ruleSpecificity };
|
|
200
|
+
export { compareComponentRules, conditionHolds, layerDeclarations, layerStyles, ruleApplies, ruleSpecificity };
|
|
@@ -366,14 +366,29 @@ function surfaceProps(config, componentPath, seen = /* @__PURE__ */ new Set()) {
|
|
|
366
366
|
*
|
|
367
367
|
* `seen` guards a forward that routes to a forward that routes back: the chain is data, so nothing
|
|
368
368
|
* stops an author writing a loop, and the surface is read on every panel render.
|
|
369
|
+
*
|
|
370
|
+
* A route that ends on a prop nothing declares (a Text layer's `href`, a tag layer's `aria-label`)
|
|
371
|
+
* still reaches the element at runtime, so it surfaces as a plain string: there is no declaration to
|
|
372
|
+
* say otherwise, and the alternative is a prop with no control at all. An event handler (`onClick`)
|
|
373
|
+
* is never declared on the far end either, and takes a function, so it stays hidden.
|
|
369
374
|
*/
|
|
370
375
|
function boundProp(config, layers, decl, seen) {
|
|
371
376
|
const propName = intoName(decl.into);
|
|
372
|
-
const
|
|
373
|
-
|
|
377
|
+
const element = layers?.[refPath(decl.target) ?? ""];
|
|
378
|
+
const undeclared = /^on[A-Z]/.test(propName) ? void 0 : {
|
|
379
|
+
name: propName,
|
|
380
|
+
kind: "string",
|
|
381
|
+
required: false
|
|
382
|
+
};
|
|
383
|
+
const target = isRef(decl.into) ? pathOf(decl.into) : refPath(element);
|
|
384
|
+
if (!target) return typeof element === "string" ? undeclared : void 0;
|
|
374
385
|
const key = `${target}#${propName}`;
|
|
375
386
|
if (seen.has(key)) return void 0;
|
|
376
|
-
|
|
387
|
+
const far = surfaceProps(config, target, new Set([...seen, key])).find((prop) => prop.name === propName);
|
|
388
|
+
if (far) return far;
|
|
389
|
+
const farBody = Component.get(config, target)?.toJSON();
|
|
390
|
+
if (!farBody) return void 0;
|
|
391
|
+
return propName in resolveComponentProps(config, target) || propName in (farBody.props ?? {}) ? void 0 : undeclared;
|
|
377
392
|
}
|
|
378
393
|
/** One prop by name, or `undefined` when the component doesn't expose it. */
|
|
379
394
|
function surfaceProp(config, componentPath, name) {
|
|
@@ -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 = 20261027205147;
|
|
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 = 20261027205147;
|
|
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 > 20261027205147) 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 ?? 20261027205147;
|
|
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 ?? 20261027205147;
|
|
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 ?? 20261027205147;
|
|
183
183
|
const entries = [];
|
|
184
184
|
const held = [];
|
|
185
185
|
const retired = [];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/framework/utils/color.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* CSS colors as numbers, and the WCAG contrast between two. Nothing here reads a config: it is the
|
|
4
|
+
* arithmetic the lint's contrast check and Create's canvas share.
|
|
5
|
+
*/
|
|
6
|
+
type Rgba = readonly [number, number, number, number];
|
|
7
|
+
/** A CSS color as sRGB 0–1 and alpha, for the forms tokens hold: hex, `rgb()`, `hsl()`, `oklch()`. */
|
|
8
|
+
declare function parseColor(text: string): Rgba | undefined;
|
|
9
|
+
/** WCAG relative luminance of an sRGB 0–1 color. */
|
|
10
|
+
declare function luminance([r, g, b]: Rgba): number;
|
|
11
|
+
/** The WCAG contrast of text over an opaque background, the text's alpha laid over it. */
|
|
12
|
+
declare function contrastRatio(text: Rgba, background: Rgba): number;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { Rgba, contrastRatio, luminance, parseColor };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
//#region src/framework/utils/color.ts
|
|
2
|
+
/** A CSS color as sRGB 0–1 and alpha, for the forms tokens hold: hex, `rgb()`, `hsl()`, `oklch()`. */
|
|
3
|
+
function parseColor(text) {
|
|
4
|
+
const value = text.trim().toLowerCase();
|
|
5
|
+
const hex = /^#([0-9a-f]{3,8})$/.exec(value)?.[1];
|
|
6
|
+
if (hex) {
|
|
7
|
+
const full = hex.length <= 4 ? hex.split("").map((one) => one + one).join("") : hex;
|
|
8
|
+
if (full.length !== 6 && full.length !== 8) return void 0;
|
|
9
|
+
const channel = (at) => Number.parseInt(full.slice(at, at + 2), 16) / 255;
|
|
10
|
+
return [
|
|
11
|
+
channel(0),
|
|
12
|
+
channel(2),
|
|
13
|
+
channel(4),
|
|
14
|
+
full.length === 8 ? channel(6) : 1
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
const fn = /^(rgba?|hsla?|oklch)\((.*)\)$/.exec(value);
|
|
18
|
+
if (!fn) {
|
|
19
|
+
if (value === "white") return [
|
|
20
|
+
1,
|
|
21
|
+
1,
|
|
22
|
+
1,
|
|
23
|
+
1
|
|
24
|
+
];
|
|
25
|
+
if (value === "black") return [
|
|
26
|
+
0,
|
|
27
|
+
0,
|
|
28
|
+
0,
|
|
29
|
+
1
|
|
30
|
+
];
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const [main = "", alphaPart] = (fn[2] ?? "").split("/");
|
|
34
|
+
const parts = main.replace(/,/g, " ").trim().split(/\s+/);
|
|
35
|
+
const commaAlpha = parts.length === 4 ? parts.pop() : void 0;
|
|
36
|
+
const number = (part, scale = 1) => {
|
|
37
|
+
if (part === void 0) return NaN;
|
|
38
|
+
return part.endsWith("%") ? Number(part.slice(0, -1)) / 100 * scale : Number(part.replace(/deg$/, ""));
|
|
39
|
+
};
|
|
40
|
+
const alphaText = (alphaPart ?? commaAlpha)?.trim();
|
|
41
|
+
const alpha = alphaText === void 0 ? 1 : number(alphaText, 1);
|
|
42
|
+
let rgb;
|
|
43
|
+
if (fn[1]?.startsWith("rgb")) rgb = [
|
|
44
|
+
0,
|
|
45
|
+
1,
|
|
46
|
+
2
|
|
47
|
+
].map((at) => number(parts[at], 255) / 255);
|
|
48
|
+
else if (fn[1]?.startsWith("hsl")) {
|
|
49
|
+
const h = (number(parts[0]) % 360 + 360) % 360 / 360;
|
|
50
|
+
const s = number(parts[1], 1);
|
|
51
|
+
const l = number(parts[2], 1);
|
|
52
|
+
const hue = (t) => {
|
|
53
|
+
const p = l < .5 ? l * (1 + s) : l + s - l * s;
|
|
54
|
+
const q = 2 * l - p;
|
|
55
|
+
const x = (t % 1 + 1) % 1;
|
|
56
|
+
if (x < 1 / 6) return q + (p - q) * 6 * x;
|
|
57
|
+
if (x < 1 / 2) return p;
|
|
58
|
+
if (x < 2 / 3) return q + (p - q) * (2 / 3 - x) * 6;
|
|
59
|
+
return q;
|
|
60
|
+
};
|
|
61
|
+
rgb = [
|
|
62
|
+
hue(h + 1 / 3),
|
|
63
|
+
hue(h),
|
|
64
|
+
hue(h - 1 / 3)
|
|
65
|
+
];
|
|
66
|
+
} else {
|
|
67
|
+
const lightness = number(parts[0], 1);
|
|
68
|
+
const chroma = number(parts[1], .4);
|
|
69
|
+
const angle = number(parts[2]) * Math.PI / 180;
|
|
70
|
+
const a = chroma * Math.cos(angle);
|
|
71
|
+
const b = chroma * Math.sin(angle);
|
|
72
|
+
const l_ = (lightness + .3963377774 * a + .2158037573 * b) ** 3;
|
|
73
|
+
const m_ = (lightness - .1055613458 * a - .0638541728 * b) ** 3;
|
|
74
|
+
const s_ = (lightness - .0894841775 * a - 1.291485548 * b) ** 3;
|
|
75
|
+
rgb = [
|
|
76
|
+
4.0767416621 * l_ - 3.3077115913 * m_ + .2309699292 * s_,
|
|
77
|
+
-1.2684380046 * l_ + 2.6097574011 * m_ - .3413193965 * s_,
|
|
78
|
+
-.0041960863 * l_ - .7034186147 * m_ + 1.707614701 * s_
|
|
79
|
+
].map((c) => {
|
|
80
|
+
const clamped = Math.min(1, Math.max(0, c));
|
|
81
|
+
return clamped <= .0031308 ? 12.92 * clamped : 1.055 * clamped ** (1 / 2.4) - .055;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if ([...rgb, alpha].some((one) => Number.isNaN(one))) return void 0;
|
|
85
|
+
return [...rgb, alpha];
|
|
86
|
+
}
|
|
87
|
+
/** WCAG relative luminance of an sRGB 0–1 color. */
|
|
88
|
+
function luminance([r, g, b]) {
|
|
89
|
+
const linear = (c) => c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
|
|
90
|
+
return .2126 * linear(r) + .7152 * linear(g) + .0722 * linear(b);
|
|
91
|
+
}
|
|
92
|
+
/** The WCAG contrast of text over an opaque background, the text's alpha laid over it. */
|
|
93
|
+
function contrastRatio(text, background) {
|
|
94
|
+
const alpha = text[3];
|
|
95
|
+
const over = [
|
|
96
|
+
0,
|
|
97
|
+
1,
|
|
98
|
+
2
|
|
99
|
+
].map((at) => text[at] * alpha + background[at] * (1 - alpha));
|
|
100
|
+
const a = luminance([
|
|
101
|
+
over[0],
|
|
102
|
+
over[1],
|
|
103
|
+
over[2],
|
|
104
|
+
1
|
|
105
|
+
]);
|
|
106
|
+
const b = luminance(background);
|
|
107
|
+
return (Math.max(a, b) + .05) / (Math.min(a, b) + .05);
|
|
108
|
+
}
|
|
109
|
+
/** Hue in degrees and saturation 0–1 of an sRGB color, as HSL reads them. */
|
|
110
|
+
function hueOf([r, g, b]) {
|
|
111
|
+
const max = Math.max(r, g, b);
|
|
112
|
+
const min = Math.min(r, g, b);
|
|
113
|
+
const delta = max - min;
|
|
114
|
+
const lightness = (max + min) / 2;
|
|
115
|
+
if (delta === 0) return {
|
|
116
|
+
hue: 0,
|
|
117
|
+
saturation: 0
|
|
118
|
+
};
|
|
119
|
+
const saturation = delta / (1 - Math.abs(2 * lightness - 1));
|
|
120
|
+
let hue;
|
|
121
|
+
if (max === r) hue = (g - b) / delta % 6;
|
|
122
|
+
else if (max === g) hue = (b - r) / delta + 2;
|
|
123
|
+
else hue = (r - g) / delta + 4;
|
|
124
|
+
return {
|
|
125
|
+
hue: (hue * 60 + 360) % 360,
|
|
126
|
+
saturation
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { contrastRatio, hueOf, luminance, parseColor };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/framework/utils/css-length.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A CSS length or a bare number as written (`12`, `-1.5px`, `.5rem`): its number and unit, or
|
|
4
|
+
* `undefined` for anything else (`50%`, `auto`). The number's pattern matches each string one way, so
|
|
5
|
+
* a long run of digits can't make it backtrack.
|
|
6
|
+
*/
|
|
7
|
+
declare function parseCssLength(text: string): {
|
|
8
|
+
readonly number: number;
|
|
9
|
+
readonly unit: 'px' | 'rem' | '';
|
|
10
|
+
} | undefined;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { parseCssLength };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/framework/utils/css-length.ts
|
|
2
|
+
/**
|
|
3
|
+
* A CSS length or a bare number as written (`12`, `-1.5px`, `.5rem`): its number and unit, or
|
|
4
|
+
* `undefined` for anything else (`50%`, `auto`). The number's pattern matches each string one way, so
|
|
5
|
+
* a long run of digits can't make it backtrack.
|
|
6
|
+
*/
|
|
7
|
+
function parseCssLength(text) {
|
|
8
|
+
const match = /^(-?(?:\d+(?:\.\d+)?|\.\d+))(px|rem)?$/.exec(text.trim());
|
|
9
|
+
if (!match) return void 0;
|
|
10
|
+
return {
|
|
11
|
+
number: Number(match[1]),
|
|
12
|
+
unit: match[2] ?? ""
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { parseCssLength };
|