@yahoo/uds-create-config 5.61.1 → 5.62.1

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.
@@ -0,0 +1,622 @@
1
+ import { isNativeConfig } from "../configs/platform.js";
2
+ import { isRef, kindOf, pathOf } from "../framework/utils/refs.js";
3
+ import { rootLayerOf } from "../entities/system/element.js";
4
+ import { modeAxes } from "../entities/system/Modifier.js";
5
+ import { literalValueOf, resolveTokenValue, resolveTokenValueUnder, stylePropEntries } from "../framework/projections.js";
6
+ import { layerDeclarations } from "../framework/layer-styles.js";
7
+ import { Component } from "../entities/system/Component.js";
8
+ import { rnStyleName } from "../entities/native/NativeStyleProperty.js";
9
+ import { contrastRatio, hueOf, luminance, parseColor } from "../framework/utils/color.js";
10
+ import { parseCssLength } from "../framework/utils/css-length.js";
11
+ import "../index.js";
12
+ //#region src/lint/context.ts
13
+ /**
14
+ * What the lint's config checks read, built once per run: the system's components with their style
15
+ * sites and cells, what each style key's tokens hold, the color modes, and the helpers every check
16
+ * shares (contrast, color parsing, px). Each rule's `check` in `rules.ts` reads from it, so the
17
+ * config is walked and its tokens resolved once however many rules run.
18
+ *
19
+ * Nothing here reads a file: a config check runs wherever a config is loaded, Create's browser
20
+ * included. A finding names where its value is written as the config stores it (component, rule,
21
+ * layer, style key), and the lint over the sources finds the line.
22
+ */
23
+ /**
24
+ * The entry that keeps a finding, if one does: same rule, and every place the entry names (component
25
+ * or token, then layer, style key and rule) the finding's own. An entry naming less reaches more.
26
+ */
27
+ function ignoredBy(ignores, finding) {
28
+ return ignores.find((entry) => entry.rule === finding.rule && (entry.component === void 0 || entry.component === finding.component) && (entry.token === void 0 || entry.token === finding.token) && (entry.layer === void 0 || entry.layer === finding.layer) && (entry.prop === void 0 || entry.prop === finding.prop) && (entry.when === void 0 || entry.when === finding.when));
29
+ }
30
+ const tokenOf = (value) => isRef(value) && kindOf(value) === "token" ? pathOf(value) : void 0;
31
+ const BREAKPOINTS = new Set([
32
+ "base",
33
+ "sm",
34
+ "md",
35
+ "lg",
36
+ "xl",
37
+ "2xl"
38
+ ]);
39
+ /** A value as written, for a message. */
40
+ function written(value) {
41
+ const token = tokenOf(value);
42
+ if (token) return token;
43
+ return typeof value === "string" || typeof value === "number" ? String(value) : JSON.stringify(value);
44
+ }
45
+ /** Every value a component's rules write, with where. */
46
+ function styleSites(styles) {
47
+ const out = [];
48
+ for (const [when, rule] of Object.entries(styles ?? {})) {
49
+ const { when: whenValue, layers } = rule ?? {};
50
+ for (const [layer, bag] of Object.entries(layers ?? {})) {
51
+ const walk = (node, modifiers, prop) => {
52
+ if (prop !== void 0 && (isRef(node) || typeof node !== "object")) {
53
+ out.push({
54
+ when,
55
+ whenValue,
56
+ layer,
57
+ prop,
58
+ modifiers,
59
+ value: node
60
+ });
61
+ return;
62
+ }
63
+ if (!node || typeof node !== "object" || Array.isArray(node)) return;
64
+ for (const [key, value] of Object.entries(node)) if (key.startsWith("_") || BREAKPOINTS.has(key)) walk(value, [...modifiers, key], prop);
65
+ else if (prop === void 0) walk(value, modifiers, key);
66
+ else out.push({
67
+ when,
68
+ whenValue,
69
+ layer,
70
+ prop,
71
+ modifiers,
72
+ value
73
+ });
74
+ };
75
+ walk(bag, []);
76
+ }
77
+ }
78
+ return out;
79
+ }
80
+ /** What the style properties offer, read once per run. */
81
+ var Values = class {
82
+ entries = /* @__PURE__ */ new Map();
83
+ /** Each style property's `arbitrary`: what a bare value or a bracketed one reads as. */
84
+ arbitrary = /* @__PURE__ */ new Map();
85
+ /** The style keys that set a color. */
86
+ colorKeys = /* @__PURE__ */ new Set();
87
+ /** The style keys that set each CSS property, by the property (`background-color` → `bg`). */
88
+ byCss = /* @__PURE__ */ new Map();
89
+ /** Whether the system is native, where a bare number is a length in points. */
90
+ native;
91
+ constructor(config) {
92
+ this.config = config;
93
+ this.native = isNativeConfig(config);
94
+ for (const property of config.list("style-property")) {
95
+ const json = property.toJSON();
96
+ const key = property.path.split("/").pop() ?? property.path;
97
+ this.arbitrary.set(key, json.arbitrary);
98
+ for (const css of json.properties ?? []) {
99
+ const name = rnStyleName(css);
100
+ this.byCss.set(name, [...this.byCss.get(name) ?? [], key]);
101
+ }
102
+ if (!json.opacityOf && (json.properties ?? []).some((css) => /(^|-)color$/.test(css))) this.colorKeys.add(key);
103
+ }
104
+ }
105
+ /** The style keys that set any of `css`: what a check reads for a CSS property, whatever the
106
+ * system calls its style property. */
107
+ keysFor(...css) {
108
+ return [...new Set(css.flatMap((one) => this.byCss.get(rnStyleName(one)) ?? []))];
109
+ }
110
+ /** The entries a style key offers, by the name a rule writes. */
111
+ offered(key) {
112
+ const hit = this.entries.get(key);
113
+ if (hit) return hit;
114
+ const out = /* @__PURE__ */ new Map();
115
+ const property = this.config.list("style-property").find((one) => (one.path.split("/").pop() ?? one.path) === key);
116
+ if (property) for (const { leaf, value } of stylePropEntries(this.config, property.path)) {
117
+ if (out.has(leaf)) continue;
118
+ out.set(leaf, this.config.resolve("token", value) !== void 0 ? { token: value } : { literal: value });
119
+ }
120
+ this.entries.set(key, out);
121
+ return out;
122
+ }
123
+ /** The token a value names, written as a ref or by its name. */
124
+ tokenOf(key, value) {
125
+ return tokenOf(value) ?? (typeof value === "string" ? this.offered(key).get(value)?.token : void 0);
126
+ }
127
+ /** A value as the literal it paints, under a mode's override condition (`null` for the base). */
128
+ literal(key, value, condition = null) {
129
+ const token = this.tokenOf(key, value);
130
+ if (token) return resolveTokenValueUnder(this.config, token, condition);
131
+ if (typeof value === "number") return value;
132
+ if (typeof value === "string") {
133
+ const bracketed = /^\[(.+)\]$/.exec(value)?.[1];
134
+ if (bracketed) return bracketed.replace(/_/g, " ");
135
+ const offered = this.offered(key).get(value);
136
+ if (offered && "literal" in offered) return literalValueOf(this.config, offered.literal) ?? value;
137
+ return value;
138
+ }
139
+ return literalValueOf(this.config, value);
140
+ }
141
+ /** A length in px, or `undefined` for one that isn't (`50%`, `auto`). A rem is read at 16px. */
142
+ px(key, value, condition = null) {
143
+ const literal = this.literal(key, value, condition);
144
+ if (typeof literal === "number") return this.native || this.arbitrary.get(key) === "px" ? literal : void 0;
145
+ if (typeof literal !== "string") return void 0;
146
+ const length = parseCssLength(literal);
147
+ if (!length) return void 0;
148
+ const { number, unit } = length;
149
+ if (unit === "rem") return number * 16;
150
+ if (unit === "px" || number === 0 || unit === "" && this.native) return number;
151
+ return this.arbitrary.get(key) === "px" ? number : void 0;
152
+ }
153
+ };
154
+ /**
155
+ * The color modes a check reads each value under: the mode axis the config declares that its tokens'
156
+ * overrides key on most, its default option first (`condition: null`), then each other option as the
157
+ * override key that names it alone. One `default` mode for a system with no such axis.
158
+ */
159
+ function colorModes(config) {
160
+ const keyed = /* @__PURE__ */ new Map();
161
+ for (const token of config.list("token")) {
162
+ const overrides = token.toJSON().overrides;
163
+ for (const key of Object.keys(overrides ?? {})) keyed.set(key, (keyed.get(key) ?? 0) + 1);
164
+ }
165
+ const axis = modeAxes(config).map((axis) => ({
166
+ axis,
167
+ uses: axis.options.reduce((sum, option) => sum + (keyed.get(option.leaf) ?? 0), 0)
168
+ })).filter(({ uses }) => uses > 0).sort((a, b) => b.uses - a.uses)[0]?.axis;
169
+ if (!axis) return [{
170
+ condition: null,
171
+ label: "default"
172
+ }];
173
+ const chosen = axis.meta.default;
174
+ const base = (chosen && isRef(chosen) ? pathOf(chosen).split("/").pop() : void 0) ?? axis.options.find((option) => !keyed.has(option.leaf))?.leaf ?? axis.options[0]?.leaf ?? "default";
175
+ return [{
176
+ condition: null,
177
+ label: base
178
+ }, ...axis.options.filter((option) => option.leaf !== base).map((option) => ({
179
+ condition: option.leaf,
180
+ label: option.leaf
181
+ }))];
182
+ }
183
+ /** A rule's `when` as prop values: a variant's value ref as its name, anything else as it is. */
184
+ function whenPropsOf(when) {
185
+ if (!when || typeof when !== "object" || Array.isArray(when)) return null;
186
+ return Object.fromEntries(Object.entries(when).flatMap(([prop, value]) => isRef(value) ? [[prop, value.__ref.split("/").pop() ?? value.__ref]] : typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? [[prop, value]] : []));
187
+ }
188
+ /**
189
+ * Each layer's parent, from the anatomy tree: what it sits on and inherits from. `undefined` for a
190
+ * component with no anatomy (one loaded from its source before a build reads the tree), where the
191
+ * nesting isn't known.
192
+ */
193
+ function layerParents(json) {
194
+ if (!json.anatomy) return void 0;
195
+ const [root] = rootLayerOf(json.layers ?? {});
196
+ const layerOf = (node) => {
197
+ const element = json.anatomy?.[node]?.element;
198
+ return isRef(element) && kindOf(element) === "layer" ? pathOf(element) : node;
199
+ };
200
+ const parents = /* @__PURE__ */ new Map();
201
+ for (const [node, { children = [] } = {}] of Object.entries(json.anatomy)) for (const child of children) if (typeof child === "string") parents.set(layerOf(child), layerOf(node));
202
+ for (const layer of Object.keys(json.layers ?? {})) if (layer !== root && !parents.has(layer)) parents.set(layer, root);
203
+ return parents;
204
+ }
205
+ /** Each combination of a component's variants, the rest of its props at their defaults, capped. */
206
+ function cellsOf(json, cap = 120) {
207
+ const variants = Object.entries(json.props ?? {}).flatMap(([name, prop]) => prop?.type === "variant" && prop.value ? [{
208
+ name,
209
+ values: Object.keys(prop.value)
210
+ }] : []);
211
+ let cells = [{
212
+ ...Object.fromEntries(variants.map(({ name, values }) => [name, values[0]])),
213
+ ...json.defaultProps
214
+ }];
215
+ for (const { name, values } of variants) {
216
+ const next = cells.flatMap((cell) => values.map((value) => ({
217
+ ...cell,
218
+ [name]: value
219
+ })));
220
+ if (next.length > cap) break;
221
+ cells = next;
222
+ }
223
+ return cells;
224
+ }
225
+ const cellLabel = (cell, json) => Object.entries(json.props ?? {}).filter(([, prop]) => prop?.type === "variant").map(([name]) => `${name}=${String(cell[name])}`).join(", ");
226
+ /** A scale step's name: a number (`3`, `0.5`, `3/5`) or a size (`md`, `2xl`). */
227
+ const SCALE_STEP = new RegExp(`^(\\d+(\\.\\d+)?(/\\d+)?|${[
228
+ "3xs",
229
+ "xxs",
230
+ "2xs",
231
+ "xs",
232
+ "sm",
233
+ "small",
234
+ "md",
235
+ "medium",
236
+ "lg",
237
+ "large",
238
+ "xl",
239
+ "xxl",
240
+ "2xl",
241
+ "3xl",
242
+ "4xl"
243
+ ].join("|")})$`);
244
+ /** The elements a browser makes pressable itself. */
245
+ const PRESS_TAGS = new Set(["button", "a"]);
246
+ /** A press handler, as React DOM (`onClick`) and React Native (`onPress`) name it. */
247
+ const PRESS_HANDLER = /^on(Click|Press)$/;
248
+ const MIN_CONTRAST = 4.5;
249
+ const TRANSPARENT = /^(transparent|currentcolor|inherit|initial|unset|none)$/i;
250
+ /** What a site's value writes inside brackets (`[180px]` → `180px`), if it's bracketed. */
251
+ const bracketedOf = (site) => typeof site.value === "string" ? /^\[(.+)\]$/.exec(site.value)?.[1] : void 0;
252
+ function configLintContext({ config, rules, own: isOwn }) {
253
+ const own = isOwn ?? ((kind, path) => config.ownerOf(kind, path) === config && !config.borrowedFrom(kind, path));
254
+ const values = new Values(config);
255
+ const modes = colorModes(config);
256
+ const tokenLiterals = /* @__PURE__ */ new Map();
257
+ const tokensFor = (key) => {
258
+ const hit = tokenLiterals.get(key);
259
+ if (hit) return hit;
260
+ const out = [...values.offered(key)].flatMap(([name, entry]) => {
261
+ if (!entry.token) return [];
262
+ const literal = resolveTokenValue(config, entry.token);
263
+ if (literal === void 0) return [];
264
+ const px = values.px(key, `[${String(literal)}]`);
265
+ return [{
266
+ name,
267
+ literal: String(literal).trim().toLowerCase(),
268
+ ...px !== void 0 ? { px } : {}
269
+ }];
270
+ });
271
+ tokenLiterals.set(key, out);
272
+ return out;
273
+ };
274
+ const all = new Map(config.list("component").map(({ path }) => [path, Component.get(config, path)?.toJSON() ?? {}]));
275
+ const components = [...all].filter(([path]) => own("component", path)).map(([path, json]) => ({
276
+ path,
277
+ json,
278
+ sites: styleSites(json.styles),
279
+ cells: cellsOf(json)
280
+ }));
281
+ const answers = /* @__PURE__ */ new Map();
282
+ const rendered = (element, test, depth = 0) => {
283
+ if (!isRef(element) || kindOf(element) !== "component" || depth > 6) return false;
284
+ const path = pathOf(element);
285
+ let known = answers.get(test);
286
+ if (!known) {
287
+ known = /* @__PURE__ */ new Map();
288
+ answers.set(test, known);
289
+ }
290
+ const hit = known.get(path);
291
+ if (hit !== void 0) return hit;
292
+ const json = all.get(path);
293
+ const answer = json !== void 0 && (test(json) || rendered(rootLayerOf(json.layers ?? {})[1], test, depth + 1));
294
+ known.set(path, answer);
295
+ return answer;
296
+ };
297
+ /** Whether a component takes a press handler: declared, or read by its render. */
298
+ const handlesPress = (json) => [...Object.keys(json.props ?? {}), ...json.renderProps ?? []].some((name) => PRESS_HANDLER.test(name));
299
+ /** Whether a component draws an icon: it takes a slot an icon library fills. */
300
+ const drawsIcon = (json) => Object.values(json.props ?? {}).some((prop) => prop?.type === "slot" && isRef(prop.value) && kindOf(prop.value) === "icon");
301
+ const pressable = (json) => {
302
+ if (handlesPress(json)) return true;
303
+ const [, element] = rootLayerOf(json.layers ?? {});
304
+ if (typeof element === "string") return PRESS_TAGS.has(element);
305
+ return rendered(element, handlesPress);
306
+ };
307
+ const colorIn = (key, value, condition) => {
308
+ const literal = values.literal(key, value, condition);
309
+ return typeof literal === "string" ? parseColor(literal) : void 0;
310
+ };
311
+ const family = (a, b) => {
312
+ const x = hueOf(a);
313
+ const y = hueOf(b);
314
+ const gray = (one) => one.saturation < .15;
315
+ if (gray(x) || gray(y)) return gray(x) && gray(y);
316
+ const apart = Math.abs(x.hue - y.hue);
317
+ return Math.min(apart, 360 - apart) <= 30;
318
+ };
319
+ const swatches = /* @__PURE__ */ new Map();
320
+ const swatchesFor = (prop) => {
321
+ const hit = swatches.get(prop);
322
+ if (hit) return hit;
323
+ const out = [];
324
+ for (const [name, entry] of values.offered(prop)) {
325
+ if (!entry.token) continue;
326
+ const ref = { __ref: `token:${entry.token}` };
327
+ const literals = modes.map(({ condition }) => values.literal(prop, ref, condition));
328
+ out.push({
329
+ name,
330
+ token: entry.token,
331
+ colors: literals.map((literal) => typeof literal === "string" ? parseColor(literal) : void 0),
332
+ follows: literals.some((literal, at) => modes[at]?.condition !== null && String(literal) !== String(literals[0]))
333
+ });
334
+ }
335
+ swatches.set(prop, out);
336
+ return out;
337
+ };
338
+ const colors = /* @__PURE__ */ new WeakMap();
339
+ const colorsOf = (site) => {
340
+ let hit = colors.get(site);
341
+ if (!hit) {
342
+ hit = modes.map(({ condition }) => colorIn(site.prop, site.value, condition));
343
+ colors.set(site, hit);
344
+ }
345
+ return hit;
346
+ };
347
+ const pairs = /* @__PURE__ */ new WeakMap();
348
+ const pairsOf = (component) => {
349
+ const hit = pairs.get(component);
350
+ if (hit) return hit;
351
+ const bySite = /* @__PURE__ */ new Map();
352
+ const seen = /* @__PURE__ */ new Map();
353
+ for (const layer of layers(component)) {
354
+ const text = layer.text?.site;
355
+ const background = layer.background?.site;
356
+ if (!text || !background) continue;
357
+ if (JSON.stringify(text.value) === JSON.stringify(background.value)) continue;
358
+ const on = seen.get(text) ?? /* @__PURE__ */ new Set();
359
+ if (on.has(background)) continue;
360
+ on.add(background);
361
+ seen.set(text, on);
362
+ const pair = {
363
+ text,
364
+ background
365
+ };
366
+ for (const site of [text, background]) {
367
+ const list = bySite.get(site) ?? [];
368
+ list.push(pair);
369
+ bySite.set(site, list);
370
+ }
371
+ }
372
+ pairs.set(component, bySite);
373
+ return bySite;
374
+ };
375
+ const keepsOthers = ({ component, changed, swatch, text, background }) => {
376
+ for (const pair of pairsOf(component).get(changed) ?? []) {
377
+ if (pair.text === text && pair.background === background) continue;
378
+ const fgs = colorsOf(pair.text);
379
+ const bgs = colorsOf(pair.background);
380
+ for (let at = 0; at < modes.length; at++) {
381
+ const fg = fgs[at];
382
+ const bg = bgs[at];
383
+ const after = swatch.colors[at];
384
+ if (!fg || !bg || !after || fg[3] === 0 || bg[3] < 1) continue;
385
+ const was = contrastRatio(fg, bg);
386
+ if ((pair.text === changed ? contrastRatio(after, bg) : contrastRatio(fg, after)) < was) return false;
387
+ }
388
+ }
389
+ return true;
390
+ };
391
+ const passingToken = ({ component, change, text, background, minimum }) => {
392
+ const changed = change === "text" ? text : background;
393
+ const textColors = colorsOf(text);
394
+ const backgroundColors = colorsOf(background);
395
+ const before = change === "text" ? textColors : backgroundColors;
396
+ const wantFollows = modes.some(({ condition }) => condition !== null && String(values.literal(changed.prop, changed.value, condition)) !== String(values.literal(changed.prop, changed.value)));
397
+ const was = before[0];
398
+ const textAt = textColors[0];
399
+ const backgroundAt = backgroundColors[0];
400
+ const lighter = textAt && backgroundAt ? luminance(textAt) > luminance(backgroundAt) : void 0;
401
+ let best;
402
+ for (const swatch of swatchesFor(changed.prop)) {
403
+ if (wantFollows && !swatch.follows) continue;
404
+ const base = swatch.colors[0];
405
+ if (!base || was && !family(base, was)) continue;
406
+ if (lighter !== void 0 && textAt && backgroundAt) {
407
+ const fg = change === "text" ? base : textAt;
408
+ const bg = change === "background" ? base : backgroundAt;
409
+ if (luminance(fg) > luminance(bg) !== lighter) continue;
410
+ }
411
+ let distance = 0;
412
+ let passes = true;
413
+ for (let at = 0; at < modes.length; at++) {
414
+ const fg = change === "text" ? swatch.colors[at] : textColors[at];
415
+ const bg = change === "background" ? swatch.colors[at] : backgroundColors[at];
416
+ const after = swatch.colors[at];
417
+ if (!fg || !bg || fg[3] < 1 || bg[3] < 1 || !after) {
418
+ passes = false;
419
+ break;
420
+ }
421
+ const prior = before[at];
422
+ if (prior) distance += (after[0] - prior[0]) ** 2 + (after[1] - prior[1]) ** 2 + (after[2] - prior[2]) ** 2;
423
+ if (contrastRatio(fg, bg) < minimum) {
424
+ passes = false;
425
+ break;
426
+ }
427
+ }
428
+ if (!passes) continue;
429
+ if (!keepsOthers({
430
+ component,
431
+ changed,
432
+ swatch,
433
+ text,
434
+ background
435
+ })) continue;
436
+ if (!best || distance < best.distance) best = {
437
+ name: swatch.name,
438
+ token: swatch.token,
439
+ distance
440
+ };
441
+ }
442
+ return best ? {
443
+ name: best.name,
444
+ token: best.token
445
+ } : void 0;
446
+ };
447
+ const declared = /* @__PURE__ */ new WeakMap();
448
+ const indexed = /* @__PURE__ */ new WeakMap();
449
+ const siteAt = (component, key) => {
450
+ let index = indexed.get(component);
451
+ if (!index) {
452
+ index = /* @__PURE__ */ new Map();
453
+ for (const site of component.sites) {
454
+ if (site.modifiers.length > 0) continue;
455
+ index.set(`${site.when}|${site.layer}|${site.prop}`, site);
456
+ }
457
+ indexed.set(component, index);
458
+ }
459
+ return index.get(key);
460
+ };
461
+ const effective = (component, cell, layer, keys) => {
462
+ let bags = declared.get(cell);
463
+ if (!bags) {
464
+ bags = layerDeclarations(config, component.path, { ...cell });
465
+ declared.set(cell, bags);
466
+ }
467
+ for (const prop of keys) {
468
+ const winner = bags[layer]?.[prop];
469
+ if (!winner) continue;
470
+ const site = siteAt(component, `${winner.rule}|${layer}|${prop}`);
471
+ if (site) return site;
472
+ }
473
+ };
474
+ const resolved = /* @__PURE__ */ new WeakMap();
475
+ const value = (component, site) => {
476
+ const hit = resolved.get(site);
477
+ if (hit) return hit;
478
+ const perMode = (read) => {
479
+ const held = /* @__PURE__ */ new Map();
480
+ return (mode) => {
481
+ const at = mode?.condition ?? null;
482
+ if (!held.has(at)) held.set(at, read(at));
483
+ return held.get(at);
484
+ };
485
+ };
486
+ const made = {
487
+ component,
488
+ site,
489
+ get token() {
490
+ return values.tokenOf(site.prop, site.value);
491
+ },
492
+ bracketed: bracketedOf(site),
493
+ literal: perMode((at) => values.literal(site.prop, site.value, at)),
494
+ color: perMode((at) => colorIn(site.prop, site.value, at)),
495
+ px: perMode((at) => values.px(site.prop, site.value, at))
496
+ };
497
+ resolved.set(site, made);
498
+ return made;
499
+ };
500
+ const ink = values.keysFor("color");
501
+ const fill = values.keysFor("background-color", "background");
502
+ const tall = values.keysFor("height");
503
+ const wide = values.keysFor("width");
504
+ function* layers(component) {
505
+ const { json } = component;
506
+ const names = Object.keys(json.layers ?? {});
507
+ const [root] = rootLayerOf(json.layers ?? {});
508
+ const parents = layerParents(json);
509
+ const pressed = pressable(json);
510
+ const graphics = new Map(names.map((name) => [name, rendered(json.layers?.[name], drawsIcon)]));
511
+ for (const cell of component.cells) {
512
+ const looked = /* @__PURE__ */ new Map();
513
+ const at = (name, keys) => {
514
+ let byName = looked.get(keys);
515
+ if (!byName) {
516
+ byName = /* @__PURE__ */ new Map();
517
+ looked.set(keys, byName);
518
+ }
519
+ const hit = byName.get(name);
520
+ if (hit !== void 0) return hit ?? void 0;
521
+ const site = effective(component, cell, name, keys);
522
+ const found = site ? value(component, site) : void 0;
523
+ byName.set(name, found ?? null);
524
+ return found;
525
+ };
526
+ const once = (read) => {
527
+ let done = false;
528
+ let held;
529
+ return () => {
530
+ if (!done) {
531
+ held = read();
532
+ done = true;
533
+ }
534
+ return held;
535
+ };
536
+ };
537
+ const painted = once(() => names.some((name) => name !== root && at(name, fill) !== void 0));
538
+ const nearest = (name, keys) => {
539
+ if (!parents) {
540
+ if (name === root || keys === fill && painted()) return void 0;
541
+ return at(root, keys);
542
+ }
543
+ for (let up = parents.get(name); up; up = parents.get(up)) {
544
+ const found = at(up, keys);
545
+ if (found) return found;
546
+ }
547
+ };
548
+ const where = cellLabel(cell, json);
549
+ for (const name of names) {
550
+ const ownText = once(() => at(name, ink));
551
+ const ownFill = once(() => at(name, fill));
552
+ const text = once(() => ownText() ?? nearest(name, ink));
553
+ const background = once(() => ownFill() ?? nearest(name, fill));
554
+ yield {
555
+ component,
556
+ cell,
557
+ where,
558
+ name,
559
+ pressable: name === root && pressed,
560
+ graphic: graphics.get(name) ?? false,
561
+ get text() {
562
+ return text();
563
+ },
564
+ get ownText() {
565
+ return ownText() !== void 0;
566
+ },
567
+ get background() {
568
+ return background();
569
+ },
570
+ get ownBackground() {
571
+ return ownFill() !== void 0;
572
+ },
573
+ get height() {
574
+ return at(name, tall);
575
+ },
576
+ get width() {
577
+ return at(name, wide);
578
+ }
579
+ };
580
+ }
581
+ }
582
+ }
583
+ return {
584
+ config,
585
+ values,
586
+ modes,
587
+ own,
588
+ components,
589
+ effective,
590
+ value,
591
+ layers,
592
+ platform: values.native ? {
593
+ native: true,
594
+ minTarget: 44,
595
+ unit: "pt"
596
+ } : {
597
+ native: false,
598
+ minTarget: 24,
599
+ unit: "px"
600
+ },
601
+ runs: (rule) => rules.has(rule),
602
+ tokensFor,
603
+ tokenSpelled: (site) => {
604
+ const bracketed = bracketedOf(site);
605
+ if (!bracketed) return void 0;
606
+ return tokensFor(site.prop).find(({ literal }) => literal === bracketed.trim().toLowerCase());
607
+ },
608
+ pressable,
609
+ passingToken,
610
+ at: (component, site) => ({
611
+ component,
612
+ when: site.when,
613
+ whenProps: whenPropsOf(site.whenValue),
614
+ layer: site.layer,
615
+ prop: site.prop,
616
+ ...site.modifiers.length > 0 ? { modifiers: site.modifiers } : {},
617
+ value: written(site.value)
618
+ })
619
+ };
620
+ }
621
+ //#endregion
622
+ export { MIN_CONTRAST, SCALE_STEP, TRANSPARENT, configLintContext, ignoredBy, written };
@@ -0,0 +1,77 @@
1
+ import { ConfigEdit } from "../framework/config-op.js";
2
+ import { Config } from "../framework/Config.js";
3
+ import { AuthoringLintGroup, AuthoringLintRule, AuthoringLintSeverity } from "./rules.js";
4
+ import { ConfigLintFinding, LintIgnore } from "./context.js";
5
+
6
+ //#region src/lint/edits.d.ts
7
+ /** Where a finding is, as the config stores it, and the value it suggests. */
8
+ interface LintFixSite {
9
+ readonly rule: AuthoringLintRule;
10
+ readonly component?: string;
11
+ readonly token?: string;
12
+ readonly when?: string;
13
+ readonly whenProps?: ConfigLintFinding['whenProps'];
14
+ readonly layer?: string;
15
+ readonly prop?: string;
16
+ readonly modifiers?: readonly string[];
17
+ readonly suggestion?: string;
18
+ /** Where the suggestion is written, when it isn't the finding's own place. */
19
+ readonly target?: ConfigLintFinding['target'];
20
+ /** The linked system the component or token comes from, which changes it there. */
21
+ readonly source?: string;
22
+ }
23
+ /** The system's `settings.lint.rules`. */
24
+ declare const lintSeverities: (config: Config | null) => Readonly<Record<string, AuthoringLintSeverity>>;
25
+ /** The system's `settings.lint.ignore`. */
26
+ declare const lintIgnores: (config: Config | null) => readonly LintIgnore[];
27
+ declare const rulesOf: (group: AuthoringLintGroup) => AuthoringLintRule[];
28
+ /** A group's severity: its rules' when they agree, `mixed` when one is set apart. */
29
+ declare function groupSeverity(group: AuthoringLintGroup, severities: Readonly<Record<string, AuthoringLintSeverity>>): AuthoringLintSeverity | 'mixed';
30
+ /**
31
+ * The settings edit that sets a rule or a group to `off`, `warn` or `error`: its entry in
32
+ * `settings.lint.rules`, or none where the default already says it.
33
+ *
34
+ * - A rule's own entry wins over its group's, so a rule set to what its group gives it drops its own.
35
+ * - A group sets each rule that has no entry of its own. Setting it also clears its rules' own entries,
36
+ * so the group's choice is the whole answer, the way a "select all" is.
37
+ * - An experimental group's rules are off until each is named, so that group sets each rule by name.
38
+ */
39
+ declare function severityEdits({
40
+ config,
41
+ name,
42
+ severity
43
+ }: {
44
+ config: Config;
45
+ name: string;
46
+ severity: AuthoringLintSeverity;
47
+ }): ConfigEdit[];
48
+ /** A finding's entry in `settings.lint.ignore`: as narrow as the finding says where it is. */
49
+ declare function ignoreEntry(site: LintFixSite, reason: string): LintIgnore;
50
+ /** The settings edit that keeps a finding, with why: `uds lint` and Create both leave it out. */
51
+ declare function ignoreEdits({
52
+ config,
53
+ site,
54
+ reason
55
+ }: {
56
+ config: Config;
57
+ site: LintFixSite;
58
+ reason: string;
59
+ }): ConfigEdit[];
60
+ /** The settings edit that puts an ignored finding back. */
61
+ declare function restoreEdits({
62
+ config,
63
+ entry
64
+ }: {
65
+ config: Config;
66
+ entry: LintIgnore;
67
+ }): ConfigEdit[];
68
+ /** Whether a finding has one value to write, which {@link fixEdits} writes. */
69
+ declare const hasFix: (site: LintFixSite) => boolean;
70
+ /**
71
+ * The edit that writes the suggestion of each finding that has one, where the finding's value was: in
72
+ * the same rule, layer and modifiers. Each component's changes go through one `styleSetEdits`, so two
73
+ * fixes on the same rule and layer write together rather than the second replacing the first.
74
+ */
75
+ declare function fixEdits(sites: readonly LintFixSite[]): ConfigEdit[];
76
+ //#endregion
77
+ export { LintFixSite, fixEdits, groupSeverity, hasFix, ignoreEdits, ignoreEntry, lintIgnores, lintSeverities, restoreEdits, rulesOf, severityEdits };