@yahoo/uds-create-config 5.60.0 → 5.60.2
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/entities/system/Component.js +2 -1
- package/dist/entities/system/component-module.js +1 -1
- package/dist/entities/system/component-style-set.d.ts +17 -0
- package/dist/entities/system/component-style-set.js +178 -9
- package/dist/framework/layer-styles.d.ts +1 -2
- package/dist/framework/layer-styles.js +19 -4
- package/dist/framework/render-spec.js +9 -2
- package/dist/spec/detach.js +5 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -2501,7 +2501,7 @@ count: z.number().int().min(1).max(50).optional() }).prefault({})
|
|
|
2501
2501
|
}),
|
|
2502
2502
|
readOnly: false,
|
|
2503
2503
|
label: "Set layer styles",
|
|
2504
|
-
description: "Set or clear style properties on a component's layers for the instances a reach covers, the way the component editor's \"Apply to\" does. Each change names a `layer`, a style-prop `prop` and a `value` (`null` clears it). `when` pins variant, boolean or state props (`{ \"variant\": \"secondary\" }`) and the change lands in the rule with exactly that condition; leave it out for every instance. `state` is the state the change is made in (`\"hover\"`): an interaction becomes part of the rule's condition, any other modifier nests as a block in the layer's bag. `modifiers` nest further blocks, outermost first (`[\"dark\"]` for dark mode). Writes one `sub-update/styles` per rule touched, creating a rule its condition has none for.",
|
|
2504
|
+
description: "Set or clear style properties on a component's layers for the instances a reach covers, the way the component editor's \"Apply to\" does. Each change names a `layer`, a style-prop `prop` and a `value` (`null` clears it). `when` pins variant, boolean or state props (`{ \"variant\": \"secondary\" }`) and the change lands in the rule with exactly that condition; leave it out for every instance. `state` is the state the change is made in (`\"hover\"`): an interaction becomes part of the rule's condition, any other modifier nests as a block in the layer's bag. `modifiers` nest further blocks, outermost first (`[\"dark\"]` for dark mode). Writes one `sub-update/styles` per rule touched, creating a rule its condition has none for. A clear also removes the value from a broader rule that gives those instances it, and writes it back on the conditions that rule covers and the clear does not, so only the cleared instances change.",
|
|
2505
2505
|
scope: "item",
|
|
2506
2506
|
title: (entity) => `Set ${entity} layer styles`,
|
|
2507
2507
|
handler: (input, config) => {
|
|
@@ -2518,6 +2518,7 @@ count: z.number().int().min(1).max(50).optional() }).prefault({})
|
|
|
2518
2518
|
})
|
|
2519
2519
|
});
|
|
2520
2520
|
return config.run(styleSetEdits({
|
|
2521
|
+
config,
|
|
2521
2522
|
path,
|
|
2522
2523
|
changes,
|
|
2523
2524
|
isInteraction: (modifier) => isInteractionModifier(config, modifier)
|
|
@@ -17,7 +17,7 @@ import { contentKey } from "../../framework/utils/content-key.js";
|
|
|
17
17
|
* The shared compiler/runtime contract. Bump whenever codegen can produce different module bytes
|
|
18
18
|
* from identical config-visible inputs (shim contracts, entry shape, transforms, and so on).
|
|
19
19
|
*/
|
|
20
|
-
const COMPONENT_MODULE_BUILD_VERSION =
|
|
20
|
+
const COMPONENT_MODULE_BUILD_VERSION = 8;
|
|
21
21
|
/**
|
|
22
22
|
* Generated web runtime inputs shared by every component module. These are intentionally separate
|
|
23
23
|
* from the per-file closure: editing an unrelated component should not invalidate this module, but
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ConfigEdit } from "../../framework/config-op.js";
|
|
2
|
+
import { Config } from "../../framework/Config.js";
|
|
3
|
+
|
|
2
4
|
//#region src/entities/system/component-style-set.d.ts
|
|
3
5
|
/**
|
|
4
6
|
* One style property set or cleared on one layer of a component, and the cells it reaches.
|
|
@@ -17,6 +19,12 @@ interface StyleSetChange {
|
|
|
17
19
|
readonly when?: Readonly<Record<string, string | number | boolean>>;
|
|
18
20
|
readonly state?: string;
|
|
19
21
|
readonly modifiers?: readonly string[];
|
|
22
|
+
/**
|
|
23
|
+
* For a clear: remove only this rule's own entry, and leave the broader rules that give the same
|
|
24
|
+
* instances the value alone. A set over several cells releases the overrides inside its reach this
|
|
25
|
+
* way, since the set itself is what those cells should show.
|
|
26
|
+
*/
|
|
27
|
+
readonly ownRuleOnly?: boolean;
|
|
20
28
|
}
|
|
21
29
|
/**
|
|
22
30
|
* The `sub-update/styles` entries a set of style changes write: one per rule they touch, each a delta
|
|
@@ -25,12 +33,21 @@ interface StyleSetChange {
|
|
|
25
33
|
* A rule's key is its condition, so a change at a condition with no rule yet creates one; `when` is
|
|
26
34
|
* carried on every entry for that reason. A cleared interaction change also clears the address the
|
|
27
35
|
* same change would have had as a nested block, so a value stored that way doesn't outlive the clear.
|
|
36
|
+
*
|
|
37
|
+
* With `config`, a clear also reaches the broader rules that give the cleared cells their value: a
|
|
38
|
+
* clear at `{ variant: 'tertiary', size: 'sm' }` removes the value from the `variant=tertiary` rule
|
|
39
|
+
* that holds it, and writes it back on the conditions that rule covers and the clear doesn't
|
|
40
|
+
* (`size=xs`, `size=md`, `size=lg` under tertiary), so only the cleared cells change. A broader rule
|
|
41
|
+
* whose split couldn't keep every instance it should, including one that leaves a prop unset, is
|
|
42
|
+
* left alone (see {@link complementOf}). A clear marked `ownRuleOnly` never reaches past its own rule.
|
|
28
43
|
*/
|
|
29
44
|
declare function styleSetEdits({
|
|
45
|
+
config,
|
|
30
46
|
path,
|
|
31
47
|
changes,
|
|
32
48
|
isInteraction
|
|
33
49
|
}: {
|
|
50
|
+
config?: Config;
|
|
34
51
|
path: string;
|
|
35
52
|
changes: readonly StyleSetChange[]; /** Whether a state is an interaction a rule can be conditioned on, by modifier leaf. */
|
|
36
53
|
isInteraction: (modifier: string) => boolean;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { canonicalCondition } from "../../framework/utils/refs.js";
|
|
1
|
+
import { canonicalCondition, refLeaf } from "../../framework/utils/refs.js";
|
|
2
2
|
import { reject } from "../../framework/rejection.js";
|
|
3
|
+
import { propValueDomain } from "../../framework/projections.js";
|
|
4
|
+
import { ruleSpecificity } from "../../framework/layer-styles.js";
|
|
3
5
|
//#region src/entities/system/component-style-set.ts
|
|
4
6
|
/** A modifier as a style bag keys it: its leaf with a leading `_`. */
|
|
5
7
|
function bagKey(modifier) {
|
|
@@ -15,6 +17,83 @@ function addressOf({ layer, modifiers, prop }) {
|
|
|
15
17
|
prop
|
|
16
18
|
].join(".");
|
|
17
19
|
}
|
|
20
|
+
/** A condition value as rules compare it: a value ref reads as its leaf. */
|
|
21
|
+
function leafKey(value) {
|
|
22
|
+
return String(refLeaf(value));
|
|
23
|
+
}
|
|
24
|
+
/** Whether every pin of `inner` holds in `outer`. */
|
|
25
|
+
function pinsHold({ inner, outer }) {
|
|
26
|
+
return Object.entries(inner).every(([axis, value]) => axis in outer && leafKey(outer[axis]) === leafKey(value));
|
|
27
|
+
}
|
|
28
|
+
/** Whether two conditions can hold at once: no axis is pinned to different values. */
|
|
29
|
+
function compatible(a, b) {
|
|
30
|
+
return Object.entries(a).every(([axis, value]) => !(axis in b) || leafKey(b[axis]) === leafKey(value));
|
|
31
|
+
}
|
|
32
|
+
/** The `_`-keyed entries of a condition: the states it holds under, which a split never crosses. */
|
|
33
|
+
function statesOf(condition) {
|
|
34
|
+
return canonicalCondition(Object.fromEntries(Object.entries(condition).filter(([axis]) => axis.startsWith("_"))));
|
|
35
|
+
}
|
|
36
|
+
function axisPins(condition) {
|
|
37
|
+
return Object.fromEntries(Object.entries(condition).filter(([axis]) => !axis.startsWith("_")));
|
|
38
|
+
}
|
|
39
|
+
function valueAt({ rule, address }) {
|
|
40
|
+
let at = rule;
|
|
41
|
+
for (const segment of address.split(".")) {
|
|
42
|
+
if (typeof at !== "object" || at === null) return void 0;
|
|
43
|
+
at = at[segment];
|
|
44
|
+
}
|
|
45
|
+
return at;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Every value a variant or boolean prop or state can take, or `undefined` when a split on it can't
|
|
49
|
+
* keep every instance: the values can't be listed, or a variant with no default can be left unset,
|
|
50
|
+
* which no condition on its values matches. A boolean always splits, since an unset one holds its
|
|
51
|
+
* `false` condition (`conditionHolds`).
|
|
52
|
+
*/
|
|
53
|
+
function axisDomains({ config, path }) {
|
|
54
|
+
const body = config.resolve("component", path)?.toJSON();
|
|
55
|
+
return (axis) => {
|
|
56
|
+
const state = body?.states?.[axis];
|
|
57
|
+
const decl = body?.props?.[axis] ?? state;
|
|
58
|
+
if (decl?.type === "boolean") return [true, false];
|
|
59
|
+
const always = body?.defaultProps?.[axis] !== void 0 || state?.default !== void 0 || body?.props?.[axis]?.required === true;
|
|
60
|
+
if (decl?.type !== "variant" || !always) return void 0;
|
|
61
|
+
const leaves = propValueDomain(config, decl)?.leaves;
|
|
62
|
+
return leaves && leaves.length > 0 ? leaves : void 0;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The conditions under `within` that `selection` doesn't reach, as few as an axis-by-axis split
|
|
67
|
+
* gives: each axis a selected condition pins and `within` doesn't is split into its values, and a
|
|
68
|
+
* branch the selection covers whole, or doesn't touch, stops there. `undefined` when an axis to split
|
|
69
|
+
* on can't keep every instance the selection doesn't reach (see {@link axisDomains}).
|
|
70
|
+
*/
|
|
71
|
+
function complementOf({ within, selection, domainOf }) {
|
|
72
|
+
const touching = selection.filter((selected) => compatible(selected, within));
|
|
73
|
+
if (touching.length === 0) return [within];
|
|
74
|
+
if (touching.some((selected) => pinsHold({
|
|
75
|
+
inner: selected,
|
|
76
|
+
outer: within
|
|
77
|
+
}))) return [];
|
|
78
|
+
const axis = touching.flatMap((selected) => Object.keys(selected)).find((candidate) => !(candidate in within));
|
|
79
|
+
if (axis === void 0) return [];
|
|
80
|
+
const domain = domainOf(axis);
|
|
81
|
+
if (!domain) return void 0;
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const value of domain) {
|
|
84
|
+
const rest = complementOf({
|
|
85
|
+
within: {
|
|
86
|
+
...within,
|
|
87
|
+
[axis]: value
|
|
88
|
+
},
|
|
89
|
+
selection: touching,
|
|
90
|
+
domainOf
|
|
91
|
+
});
|
|
92
|
+
if (!rest) return void 0;
|
|
93
|
+
out.push(...rest);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
18
97
|
/**
|
|
19
98
|
* The `sub-update/styles` entries a set of style changes write: one per rule they touch, each a delta
|
|
20
99
|
* of addresses, so nothing a change doesn't name is touched.
|
|
@@ -22,9 +101,17 @@ function addressOf({ layer, modifiers, prop }) {
|
|
|
22
101
|
* A rule's key is its condition, so a change at a condition with no rule yet creates one; `when` is
|
|
23
102
|
* carried on every entry for that reason. A cleared interaction change also clears the address the
|
|
24
103
|
* same change would have had as a nested block, so a value stored that way doesn't outlive the clear.
|
|
104
|
+
*
|
|
105
|
+
* With `config`, a clear also reaches the broader rules that give the cleared cells their value: a
|
|
106
|
+
* clear at `{ variant: 'tertiary', size: 'sm' }` removes the value from the `variant=tertiary` rule
|
|
107
|
+
* that holds it, and writes it back on the conditions that rule covers and the clear doesn't
|
|
108
|
+
* (`size=xs`, `size=md`, `size=lg` under tertiary), so only the cleared cells change. A broader rule
|
|
109
|
+
* whose split couldn't keep every instance it should, including one that leaves a prop unset, is
|
|
110
|
+
* left alone (see {@link complementOf}). A clear marked `ownRuleOnly` never reaches past its own rule.
|
|
25
111
|
*/
|
|
26
|
-
function styleSetEdits({ path, changes, isInteraction }) {
|
|
112
|
+
function styleSetEdits({ config, path, changes, isInteraction }) {
|
|
27
113
|
const rules = /* @__PURE__ */ new Map();
|
|
114
|
+
const reaching = /* @__PURE__ */ new Set();
|
|
28
115
|
const ruleAt = (condition) => {
|
|
29
116
|
const when = Object.keys(condition).length > 0 ? { ...condition } : null;
|
|
30
117
|
const key = canonicalCondition(when);
|
|
@@ -44,14 +131,17 @@ function styleSetEdits({ path, changes, isInteraction }) {
|
|
|
44
131
|
const { layer, prop } = change;
|
|
45
132
|
const clear = change.value === null;
|
|
46
133
|
if (change.state && isInteraction(bagKey(change.state).slice(1))) {
|
|
47
|
-
|
|
134
|
+
const condition = {
|
|
48
135
|
...pins,
|
|
49
136
|
[bagKey(change.state)]: true
|
|
50
|
-
}
|
|
137
|
+
};
|
|
138
|
+
const address = addressOf({
|
|
51
139
|
layer,
|
|
52
140
|
modifiers: nested,
|
|
53
141
|
prop
|
|
54
|
-
})
|
|
142
|
+
});
|
|
143
|
+
ruleAt(condition).delta[address] = change.value;
|
|
144
|
+
if (clear && !change.ownRuleOnly) reaching.add(`${canonicalCondition(condition)}\n${address}`);
|
|
55
145
|
if (clear) ruleAt(pins).delta[addressOf({
|
|
56
146
|
layer,
|
|
57
147
|
modifiers: [change.state, ...nested],
|
|
@@ -59,13 +149,21 @@ function styleSetEdits({ path, changes, isInteraction }) {
|
|
|
59
149
|
})] = null;
|
|
60
150
|
continue;
|
|
61
151
|
}
|
|
62
|
-
const
|
|
63
|
-
ruleAt(pins).delta[addressOf({
|
|
152
|
+
const address = addressOf({
|
|
64
153
|
layer,
|
|
65
|
-
modifiers,
|
|
154
|
+
modifiers: change.state ? [change.state, ...nested] : nested,
|
|
66
155
|
prop
|
|
67
|
-
})
|
|
156
|
+
});
|
|
157
|
+
ruleAt(pins).delta[address] = change.value;
|
|
158
|
+
if (clear && !change.ownRuleOnly) reaching.add(`${canonicalCondition(pins)}\n${address}`);
|
|
68
159
|
}
|
|
160
|
+
if (config) releaseBroaderRules({
|
|
161
|
+
config,
|
|
162
|
+
path,
|
|
163
|
+
rules,
|
|
164
|
+
reaching,
|
|
165
|
+
ruleAt
|
|
166
|
+
});
|
|
69
167
|
return [...rules].filter(([, rule]) => Object.keys(rule.delta).length > 0).map(([key, rule]) => ({
|
|
70
168
|
kind: "component",
|
|
71
169
|
operation: "sub-update",
|
|
@@ -79,6 +177,77 @@ function styleSetEdits({ path, changes, isInteraction }) {
|
|
|
79
177
|
}));
|
|
80
178
|
}
|
|
81
179
|
/**
|
|
180
|
+
* Clears each cleared address from the stored rules broader than the clears that hold it, and writes
|
|
181
|
+
* the value back on the part of each such rule the clears don't cover. Clears of one address under
|
|
182
|
+
* the same states are one selection, so a clear over several cells splits a broader rule once.
|
|
183
|
+
*/
|
|
184
|
+
function releaseBroaderRules({ config, path, rules, reaching, ruleAt }) {
|
|
185
|
+
const stored = (config.resolve("component", path)?.toJSON())?.styles;
|
|
186
|
+
if (!stored) return;
|
|
187
|
+
const selections = /* @__PURE__ */ new Map();
|
|
188
|
+
for (const [key, { when, delta }] of rules) for (const [address, value] of Object.entries(delta)) {
|
|
189
|
+
if (value !== null || !reaching.has(`${key}\n${address}`)) continue;
|
|
190
|
+
const condition = when ?? {};
|
|
191
|
+
const states = statesOf(condition);
|
|
192
|
+
const id = `${address}\n${states}`;
|
|
193
|
+
let selection = selections.get(id);
|
|
194
|
+
if (!selection) {
|
|
195
|
+
selection = {
|
|
196
|
+
address,
|
|
197
|
+
states,
|
|
198
|
+
conditions: []
|
|
199
|
+
};
|
|
200
|
+
selections.set(id, selection);
|
|
201
|
+
}
|
|
202
|
+
selection.conditions.push(axisPins(condition));
|
|
203
|
+
}
|
|
204
|
+
const domainOf = axisDomains({
|
|
205
|
+
config,
|
|
206
|
+
path
|
|
207
|
+
});
|
|
208
|
+
const written = (condition, address) => rules.get(canonicalCondition(condition))?.delta[address] !== void 0;
|
|
209
|
+
const ranked = Object.values(stored).map((rule, index) => ({
|
|
210
|
+
rule,
|
|
211
|
+
when: rule.when ?? {},
|
|
212
|
+
rank: [ruleSpecificity(rule.when), index]
|
|
213
|
+
}));
|
|
214
|
+
const outranks = (a, b) => a.rank[0] > b.rank[0] || a.rank[0] === b.rank[0] && a.rank[1] > b.rank[1];
|
|
215
|
+
for (const { address, states, conditions } of selections.values()) {
|
|
216
|
+
const holders = ranked.filter((entry) => statesOf(entry.when) === states && valueAt({
|
|
217
|
+
rule: entry.rule,
|
|
218
|
+
address
|
|
219
|
+
}) !== void 0);
|
|
220
|
+
for (const holder of holders) {
|
|
221
|
+
const { when } = holder;
|
|
222
|
+
if (written(when, address)) continue;
|
|
223
|
+
const pins = axisPins(when);
|
|
224
|
+
if (!conditions.some((outer) => pinsHold({
|
|
225
|
+
inner: pins,
|
|
226
|
+
outer
|
|
227
|
+
}))) continue;
|
|
228
|
+
const kept = complementOf({
|
|
229
|
+
within: pins,
|
|
230
|
+
selection: [...conditions, ...holders.filter((other) => outranks(other, holder)).map((other) => axisPins(other.when))],
|
|
231
|
+
domainOf
|
|
232
|
+
});
|
|
233
|
+
if (!kept) continue;
|
|
234
|
+
const value = valueAt({
|
|
235
|
+
rule: holder.rule,
|
|
236
|
+
address
|
|
237
|
+
});
|
|
238
|
+
const stateEntries = Object.fromEntries(Object.entries(when).filter(([axis]) => axis.startsWith("_")));
|
|
239
|
+
ruleAt(when).delta[address] = null;
|
|
240
|
+
for (const condition of kept) {
|
|
241
|
+
const at = {
|
|
242
|
+
...condition,
|
|
243
|
+
...stateEntries
|
|
244
|
+
};
|
|
245
|
+
if (!written(at, address)) ruleAt(at).delta[address] = value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
82
251
|
* Refuses a change that names a layer the component doesn't have, a prop the layer can't take, a
|
|
83
252
|
* state or modifier the config doesn't register, or a pin no rule can be conditioned on: only a
|
|
84
253
|
* variant or boolean prop, or a state, conditions a rule.
|
|
@@ -10,8 +10,7 @@ interface StoredRule {
|
|
|
10
10
|
* unconditional `:where()` rules rank lower. Stable sorting preserves authored order among
|
|
11
11
|
* conditions with different numbers of props. */
|
|
12
12
|
declare function compareComponentRules(left: StoredRule, right: StoredRule): number;
|
|
13
|
-
/** Whether every prop a rule names holds the value it asks for
|
|
14
|
-
* condition is canonicalized into the rule's key and a variant value is a string there.
|
|
13
|
+
/** Whether every prop a rule names holds the value it asks for ({@link conditionHolds}).
|
|
15
14
|
*
|
|
16
15
|
* An interaction (`_hover`, see `interaction-conditions`) holds only when the state says it does —
|
|
17
16
|
* `{ _hover: true }`, a surface forcing it — whichever layer the rule names: no prop bag is ever
|
|
@@ -29,14 +29,29 @@ import { resolveAppliedComposites } from "../entities/system/applied-composites.
|
|
|
29
29
|
function compareComponentRules(left, right) {
|
|
30
30
|
return Number(Object.keys(left.when ?? {}).length > 0) - Number(Object.keys(right.when ?? {}).length > 0);
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
32
|
+
/**
|
|
33
|
+
* Whether a prop's value holds a condition's value. Compared as strings, because a condition is
|
|
34
|
+
* canonicalized into the rule's key and a variant value is a string there.
|
|
35
|
+
*
|
|
36
|
+
* A `false` condition also holds for a prop left unset, which renders as off: a rule written for a
|
|
37
|
+
* matrix cell's `loading: false` reaches an instance that never mentions `loading`. The emitted
|
|
38
|
+
* module tests the same way (`loading !== true`).
|
|
39
|
+
*/
|
|
40
|
+
function conditionHolds({ actual, expected }) {
|
|
41
|
+
const leaf = refLeaf(expected);
|
|
42
|
+
if (leaf === false && actual == null) return true;
|
|
43
|
+
return String(actual) === String(leaf);
|
|
44
|
+
}
|
|
45
|
+
/** Whether every prop a rule names holds the value it asks for ({@link conditionHolds}).
|
|
34
46
|
*
|
|
35
47
|
* An interaction (`_hover`, see `interaction-conditions`) holds only when the state says it does —
|
|
36
48
|
* `{ _hover: true }`, a surface forcing it — whichever layer the rule names: no prop bag is ever
|
|
37
49
|
* hovered, so at rest the rule is out. */
|
|
38
50
|
function ruleApplies(when, state) {
|
|
39
|
-
return Object.entries(when ?? {}).every(([prop, value]) => prop.startsWith("_") ? interactionHolds(state, prop, value) :
|
|
51
|
+
return Object.entries(when ?? {}).every(([prop, value]) => prop.startsWith("_") ? interactionHolds(state, prop, value) : conditionHolds({
|
|
52
|
+
actual: state[prop],
|
|
53
|
+
expected: value
|
|
54
|
+
}));
|
|
40
55
|
}
|
|
41
56
|
/** Whether an interaction holds: forced for the whole component (`_hover: true`, a surface's pin), or
|
|
42
57
|
* reported for the layer the condition names (`'_hover@close': true`, a live pointer). */
|
|
@@ -138,4 +153,4 @@ function layerStyles(config, path, props = {}, options = {}) {
|
|
|
138
153
|
return out;
|
|
139
154
|
}
|
|
140
155
|
//#endregion
|
|
141
|
-
export { compareComponentRules, layerStyles, ruleApplies };
|
|
156
|
+
export { compareComponentRules, conditionHolds, layerStyles, ruleApplies, ruleSpecificity };
|
|
@@ -10,6 +10,7 @@ import { forwardRoutes, forwardedLayers, intoName, isValueSlot, renderBindingsOf
|
|
|
10
10
|
import { withRequiredContentHidden } from "../entities/system/required-content-layers.js";
|
|
11
11
|
import { inlineMotionProps, jsMotionPresets } from "../entities/system/motion-presets.js";
|
|
12
12
|
import { graftPreviewElements } from "../spec/preview-elements.js";
|
|
13
|
+
import { conditionHolds } from "./layer-styles.js";
|
|
13
14
|
import { componentLayerClass, componentPropClasses, componentRuleClasses, componentStateAttributes, forcedModifierAttributes, layerJsMotionPresets, layerRoutedProps, splitStyleProps, styleRuleMotionClasses } from "./class-names.js";
|
|
14
15
|
//#region src/framework/render-spec.ts
|
|
15
16
|
/** A repeat source that is one prop binding: `{ "$state": "/stops" }`, settled by the cell alone. */
|
|
@@ -1143,7 +1144,10 @@ function ruleKeysFor(config, path, layer, state, pins) {
|
|
|
1143
1144
|
for (const rule of layerRules(config, path)) {
|
|
1144
1145
|
const layerKeys = rule.keys.get(layer);
|
|
1145
1146
|
if (!layerKeys) continue;
|
|
1146
|
-
if (!rule.props.every(([prop, value]) =>
|
|
1147
|
+
if (!rule.props.every(([prop, value]) => conditionHolds({
|
|
1148
|
+
actual: state[prop],
|
|
1149
|
+
expected: value
|
|
1150
|
+
}))) continue;
|
|
1147
1151
|
if (!rule.modifiers.every(([modifier, value]) => value === true && (pins[modifier] === true || pins[modifier] === "true"))) continue;
|
|
1148
1152
|
for (const key of layerKeys) keys.add(key);
|
|
1149
1153
|
}
|
|
@@ -1163,7 +1167,10 @@ function classesFor(config, path, layer, state, extra) {
|
|
|
1163
1167
|
for (const rule of componentRuleClasses(config, path)) {
|
|
1164
1168
|
if (rule.layer !== layer) continue;
|
|
1165
1169
|
if (rule.composite) continue;
|
|
1166
|
-
if (Object.entries(rule.when).every(([prop, value]) =>
|
|
1170
|
+
if (Object.entries(rule.when).every(([prop, value]) => conditionHolds({
|
|
1171
|
+
actual: state[prop],
|
|
1172
|
+
expected: value
|
|
1173
|
+
}))) parts.push(rule.className);
|
|
1167
1174
|
}
|
|
1168
1175
|
parts.push(...extra);
|
|
1169
1176
|
return parts.filter(Boolean).join(" ");
|
package/dist/spec/detach.js
CHANGED
|
@@ -4,6 +4,7 @@ import { rootLayerOf } from "../entities/system/element.js";
|
|
|
4
4
|
import { authoredValue, declaredPropDefaults } from "../framework/projections.js";
|
|
5
5
|
import { forwardRoutes, isValueSlot } from "../framework/prop-surface.js";
|
|
6
6
|
import { isPreviewElement } from "./preview-elements.js";
|
|
7
|
+
import { conditionHolds } from "../framework/layer-styles.js";
|
|
7
8
|
import { entryKeyOf, resolveVisibility, slotAnatomyPropOf, slotTargetsOf } from "../framework/render-spec.js";
|
|
8
9
|
import { Component } from "../entities/system/Component.js";
|
|
9
10
|
//#region src/spec/detach.ts
|
|
@@ -35,7 +36,10 @@ function anatomyOf(body) {
|
|
|
35
36
|
/** Whether a rule's condition holds under a prop bag, a ref compared by its leaf. */
|
|
36
37
|
function ruleHolds(when, state) {
|
|
37
38
|
if (!when) return true;
|
|
38
|
-
return Object.entries(when).every(([prop, value]) =>
|
|
39
|
+
return Object.entries(when).every(([prop, value]) => conditionHolds({
|
|
40
|
+
actual: refLeaf(state[prop]),
|
|
41
|
+
expected: value
|
|
42
|
+
}));
|
|
39
43
|
}
|
|
40
44
|
/** The rules in the order their bags apply: the unconditional ones, then by how many props a
|
|
41
45
|
* condition pins, so a narrower rule writes over a wider one the way the stylesheet cascades them. */
|