@yahoo/uds-create-config 4.37.0 → 4.37.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/configs/system.js +2 -0
- package/dist/entities/system/Component.d.ts +31 -2
- package/dist/entities/system/Component.js +10 -1
- package/dist/entities/system/component-module.d.ts +8 -3
- package/dist/entities/system/component-module.js +10 -6
- package/dist/entities/system/defineComponent.d.ts +3 -0
- package/dist/framework/Config.d.ts +1 -1
- package/dist/framework/Config.js +1 -1
- package/dist/framework/class-names.d.ts +46 -7
- package/dist/framework/class-names.js +137 -24
- package/dist/framework/render-spec.js +1 -0
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/migrations/20260928215509_composite_prop_layer.d.ts +12 -0
- package/dist/migrations/20260928215509_composite_prop_layer.js +8 -0
- package/dist/renderer/componentEntry.d.ts +18 -15
- package/dist/renderer/componentEntry.js +15 -6
- package/dist/renderer/computed.d.ts +3 -2
- package/dist/renderer/computed.js +3 -2
- package/dist/renderer/index.d.ts +2 -2
- package/dist/renderer/index.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
package/dist/configs/system.js
CHANGED
|
@@ -43,6 +43,7 @@ import { globalStyleDerivedColorsMigration } from "../migrations/20260923163604_
|
|
|
43
43
|
import { slotCodeFormsMigration } from "../migrations/20260924044539_slot_code_forms.js";
|
|
44
44
|
import { canvasRoleSlotMigration } from "../migrations/20260925143319_canvas_role_slot.js";
|
|
45
45
|
import { linkOnLinkedMigration } from "../migrations/20260928180801_link_on_linked.js";
|
|
46
|
+
import { compositePropLayerMigration } from "../migrations/20260928215509_composite_prop_layer.js";
|
|
46
47
|
const System = defineConfig({
|
|
47
48
|
name: WEB_CONFIG_NAME,
|
|
48
49
|
description: "A design system: the values, primitives and compositions an app is built from. A ref is written `{ \"__ref\": \"<kind>:<path>\" }`, so a token is `{ \"__ref\": \"token:<group>/<name>\" }` and a token group is `{ \"__ref\": \"token:<group>\" }`. Build it in this order. Tokens first, in groups by purpose (colors, spacing, radii, type sizes), since everything else names them. Then style properties, each with `values` holding one ref to the token group whose members it accepts (`{ \"__ref\": \"token:color\" }`), so a prop takes token names rather than raw CSS; add a string keyword only for a value no token holds. A style rule's bag holds a prop's values (`\"padding\": \"4\"`, `\"bg\": \"brand\"`), never raw CSS. Then a few primitive components, one layer each, whose props are style properties (a box for layout and surface, a text for type, a pressable for interaction). Then every other component composed from those primitives: its layers name a primitive or an element, its anatomy places them (a node renders a layer, a tag or `Slot` where a slot prop's content lands; `children` is the edge list; `visible` is `{ \"$state\": \"<prop>\" }`; a prop shown in a nested layer is a `forward` route, and `$state` never sits in a node's props), and its style rules set style-property values per layer, keyed by prop values when a variant changes them. Modes (a dark mode is `{ type: \"selector\", value: \".dark\" }`) change token values through a token's `overrides`, so a component never restyles for a mode; modifiers (hover, focus, disabled) name a state, and a style rule applies under one as a `_hover` block inside a layer bag. Composites bundle style values that recur. Last, register canvas roles: the canvas draws with them, one entity per tool (`frame` for the layout primitive, `text` for the text primitive, `image`) whose entries name the components that tool inserts, and a tool whose role is unregistered is disabled. Give each component a `preview` entry under `*` so it shows content, and never write a CSS property name where a style-property name belongs.",
|
|
@@ -97,5 +98,6 @@ if (!registeredSchemaMigrations().has(20260923163604)) registerSchemaMigrations(
|
|
|
97
98
|
if (!registeredSchemaMigrations().has(20260924044539)) registerSchemaMigrations(slotCodeFormsMigration);
|
|
98
99
|
if (!registeredSchemaMigrations().has(20260925143319)) registerSchemaMigrations(canvasRoleSlotMigration);
|
|
99
100
|
if (!registeredSchemaMigrations().has(20260928180801)) registerSchemaMigrations(linkOnLinkedMigration);
|
|
101
|
+
if (!registeredSchemaMigrations().has(20260928215509)) registerSchemaMigrations(compositePropLayerMigration);
|
|
100
102
|
//#endregion
|
|
101
103
|
export { System };
|
|
@@ -176,17 +176,46 @@ declare const propShapeDefinition: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
176
176
|
}, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodObject<{
|
|
177
177
|
__ref: z.ZodString;
|
|
178
178
|
}, z.core.$strip>]>>]>>>;
|
|
179
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
179
|
+
}, z.core.$strict>, z.ZodPipe<z.ZodObject<{
|
|
180
180
|
type: z.ZodLiteral<"composite">;
|
|
181
181
|
value: z.ZodObject<{
|
|
182
182
|
__ref: z.ZodString;
|
|
183
183
|
}, z.core.$strip>;
|
|
184
|
+
layer: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
185
|
+
__ref: z.ZodString;
|
|
186
|
+
}, z.core.$strip>]>>;
|
|
184
187
|
when: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodObject<{
|
|
185
188
|
__ref: z.ZodString;
|
|
186
189
|
}, z.core.$strip>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodObject<{
|
|
187
190
|
__ref: z.ZodString;
|
|
188
191
|
}, z.core.$strip>]>>]>>>;
|
|
189
|
-
}, z.core.$strict>, z.
|
|
192
|
+
}, z.core.$strict>, z.ZodTransform<{
|
|
193
|
+
type: "composite";
|
|
194
|
+
value: {
|
|
195
|
+
__ref: string;
|
|
196
|
+
};
|
|
197
|
+
layer?: string | {
|
|
198
|
+
__ref: string;
|
|
199
|
+
} | undefined;
|
|
200
|
+
when?: Record<string, string | number | boolean | {
|
|
201
|
+
__ref: string;
|
|
202
|
+
} | (string | number | boolean | {
|
|
203
|
+
__ref: string;
|
|
204
|
+
})[]> | undefined;
|
|
205
|
+
}, {
|
|
206
|
+
type: "composite";
|
|
207
|
+
value: {
|
|
208
|
+
__ref: string;
|
|
209
|
+
};
|
|
210
|
+
layer?: string | {
|
|
211
|
+
__ref: string;
|
|
212
|
+
} | undefined;
|
|
213
|
+
when?: Record<string, string | number | boolean | {
|
|
214
|
+
__ref: string;
|
|
215
|
+
} | (string | number | boolean | {
|
|
216
|
+
__ref: string;
|
|
217
|
+
})[]> | undefined;
|
|
218
|
+
}>>, z.ZodObject<{
|
|
190
219
|
type: z.ZodLiteral<"motion">;
|
|
191
220
|
value: z.ZodDefault<z.ZodObject<{
|
|
192
221
|
__ref: z.ZodString;
|
|
@@ -360,6 +360,14 @@ function routedTarget(decl) {
|
|
|
360
360
|
into: decl.target.slice(separator + 1)
|
|
361
361
|
};
|
|
362
362
|
}
|
|
363
|
+
/** A composite prop's authored `layer` name as the layer ref it is stored as — see {@link routedTarget}
|
|
364
|
+
* for why the coercion lives in the schema. */
|
|
365
|
+
function layerAsRef(decl) {
|
|
366
|
+
return typeof decl.layer === "string" ? {
|
|
367
|
+
...decl,
|
|
368
|
+
layer: layerRef(decl.layer)
|
|
369
|
+
} : decl;
|
|
370
|
+
}
|
|
363
371
|
/** A prop that applies only under a condition on its siblings — the record a style rule's `when`
|
|
364
372
|
* holds. Open here, where no owner is known; `propSchemaFor` narrows it to the owner's props. */
|
|
365
373
|
const conditionLeaf = z.union([
|
|
@@ -415,8 +423,9 @@ function propShapeWith(when) {
|
|
|
415
423
|
z.strictObject({
|
|
416
424
|
type: z.literal("composite"),
|
|
417
425
|
value: refSchema,
|
|
426
|
+
layer: z.union([z.string().min(1), refSchema]).optional(),
|
|
418
427
|
when
|
|
419
|
-
}),
|
|
428
|
+
}).transform(layerAsRef),
|
|
420
429
|
z.strictObject({
|
|
421
430
|
type: z.literal("motion"),
|
|
422
431
|
value: refSchema.default({ __ref: "motion:" }),
|
|
@@ -15,9 +15,14 @@ interface ComponentModuleSource {
|
|
|
15
15
|
* authored name is `Component.export`, and the two differ for a grouped registration under
|
|
16
16
|
* another leaf and for a draft rename the source file has not caught up with. */
|
|
17
17
|
readonly exportName: string;
|
|
18
|
-
/** The ctx-shaped registry export emitted beside the component, `<leaf>Entry`. */
|
|
19
|
-
readonly entryExportName: string;
|
|
20
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* The static an emitted module stamps a component's registry-entry facts on: what
|
|
21
|
+
* `makeComponentEntry` takes beyond the component (`childless`, `computed`). A static and not an
|
|
22
|
+
* export, because React Fast Refresh treats a module exporting anything but components as a
|
|
23
|
+
* full-reload boundary — an `__udsEntries` export made every component edit reload the page.
|
|
24
|
+
*/
|
|
25
|
+
declare const COMPONENT_ENTRY_STATIC = "__udsEntry";
|
|
21
26
|
/** One component export an authored import can resolve through the runtime registry. */
|
|
22
27
|
interface ComponentRegistryImportBinding {
|
|
23
28
|
readonly componentRef: string;
|
|
@@ -63,4 +68,4 @@ declare function componentModuleKey(config: Config, filePath: string): string |
|
|
|
63
68
|
*/
|
|
64
69
|
declare function componentModuleSource(config: Config, componentPathOrRef: string | Ref): ComponentModuleSource | undefined;
|
|
65
70
|
//#endregion
|
|
66
|
-
export { ComponentModuleSource, ComponentRegistryImport, ComponentRegistryImportBinding, componentModuleFilePaths, componentModuleKey, componentModuleSource, componentRegistryImports };
|
|
71
|
+
export { COMPONENT_ENTRY_STATIC, ComponentModuleSource, ComponentRegistryImport, ComponentRegistryImportBinding, componentModuleFilePaths, componentModuleKey, componentModuleSource, componentRegistryImports };
|
|
@@ -28,9 +28,14 @@ const WEB_RUNTIME_KINDS = [
|
|
|
28
28
|
"motion",
|
|
29
29
|
"composite"
|
|
30
30
|
];
|
|
31
|
-
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
/**
|
|
32
|
+
* The static an emitted module stamps a component's registry-entry facts on: what
|
|
33
|
+
* `makeComponentEntry` takes beyond the component (`childless`, `computed`). A static and not an
|
|
34
|
+
* export, because React Fast Refresh treats a module exporting anything but components as a
|
|
35
|
+
* full-reload boundary — an `__udsEntries` export made every component edit reload the page.
|
|
36
|
+
*/
|
|
37
|
+
const COMPONENT_ENTRY_STATIC = "__udsEntry";
|
|
38
|
+
/** The name an emitted module exports a component under: its path's leaf. */
|
|
34
39
|
function componentModuleExportName(path) {
|
|
35
40
|
return path.split("/").pop() ?? path;
|
|
36
41
|
}
|
|
@@ -258,9 +263,8 @@ function componentModuleSource(config, componentPathOrRef) {
|
|
|
258
263
|
componentRef: Component.ref(component.path).__ref,
|
|
259
264
|
filePath: ref.path,
|
|
260
265
|
moduleKey,
|
|
261
|
-
exportName: forward
|
|
262
|
-
entryExportName: `${forward}Entry`
|
|
266
|
+
exportName: forward
|
|
263
267
|
};
|
|
264
268
|
}
|
|
265
269
|
//#endregion
|
|
266
|
-
export { componentModuleFilePaths, componentModuleKey, componentModuleSource, componentRegistryImports };
|
|
270
|
+
export { COMPONENT_ENTRY_STATIC, componentModuleFilePaths, componentModuleKey, componentModuleSource, componentRegistryImports };
|
|
@@ -189,6 +189,9 @@ type PropDecl<Path extends string = string> = SlotDecl | ForwardDecl<Path> | {
|
|
|
189
189
|
} | {
|
|
190
190
|
type: 'composite';
|
|
191
191
|
value: Ref$1;
|
|
192
|
+
/** The layer the chosen member styles; the root by default. Authored as its name, stored as a
|
|
193
|
+
* layer ref. */
|
|
194
|
+
layer?: string | Ref$1;
|
|
192
195
|
required?: boolean;
|
|
193
196
|
when?: PropCondition;
|
|
194
197
|
} | {
|
|
@@ -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 = 20260928215509;
|
|
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
|
@@ -5135,7 +5135,7 @@ var Config = class Config {
|
|
|
5135
5135
|
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
5136
5136
|
hydrateFrom(stored, options) {
|
|
5137
5137
|
const claimed = schemaVersionOf(stored);
|
|
5138
|
-
if (claimed >
|
|
5138
|
+
if (claimed > 20260928215509) throw new SchemaVersionTooNew(claimed);
|
|
5139
5139
|
const wire = upgradeSerializedConfig(stored);
|
|
5140
5140
|
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\`.`);
|
|
5141
5141
|
const leftover = wire.options;
|
|
@@ -84,6 +84,9 @@ interface ComponentRuleClass {
|
|
|
84
84
|
* emitted module tests it with `=== true`. */
|
|
85
85
|
readonly when: Record<string, unknown>;
|
|
86
86
|
readonly className: string;
|
|
87
|
+
/** Set when the class comes from a composite PROP rather than a style rule: the member whose bag
|
|
88
|
+
* the class paints. See {@link compositePropRuleClasses}. */
|
|
89
|
+
readonly composite?: string;
|
|
87
90
|
}
|
|
88
91
|
/**
|
|
89
92
|
* Every conditional class a component's style rules produce.
|
|
@@ -137,23 +140,59 @@ declare function componentMotionClasses(config: Config, path: string): Component
|
|
|
137
140
|
* defaults, because a value the caller didn't set has to read the default — the same rule
|
|
138
141
|
* {@link layerStyles} follows.
|
|
139
142
|
*
|
|
140
|
-
* Keyed by layer
|
|
141
|
-
*
|
|
143
|
+
* Keyed by layer: a prop lands on the layer it names (the root by default), or on the layer that
|
|
144
|
+
* forwards it. Each member contributes two classes, the composite's own and the component's rule
|
|
145
|
+
* class for it; {@link componentRuleClasses} lists the second too, for the module and the sheet, so
|
|
146
|
+
* a projection reading both skips its `composite` entries.
|
|
142
147
|
*/
|
|
143
148
|
declare function componentPropClasses(config: Config, path: string, state?: Record<string, unknown>): Record<string, string[]>;
|
|
149
|
+
/** One composite an instance's props pick, and where it lands. */
|
|
150
|
+
interface PickedComposite {
|
|
151
|
+
readonly prop: string;
|
|
152
|
+
readonly layer: string;
|
|
153
|
+
/** The member's path (`textVariant/title1/emphasized`). */
|
|
154
|
+
readonly member: string;
|
|
155
|
+
/** The value as a caller writes it, the member's path under its group (`title1/emphasized`). */
|
|
156
|
+
readonly value: string;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The composites an instance's props pick, resolved over the component's defaults: each one beats
|
|
160
|
+
* the component's own rules on its layer (see {@link compositePropRuleClasses}).
|
|
161
|
+
*
|
|
162
|
+
* A projection that paints a component's rule declarations as props, as the component editor's cells
|
|
163
|
+
* do, needs this to hold that precedence: a rule's `fontSize` painted as a prop would otherwise beat
|
|
164
|
+
* the member a caller picked, where the stylesheet has the member win.
|
|
165
|
+
*/
|
|
166
|
+
declare function componentPropComposites(config: Config, path: string, state?: Record<string, unknown>): PickedComposite[];
|
|
167
|
+
/**
|
|
168
|
+
* A style rule's bag with each composite it names written out in place: the member's styles first,
|
|
169
|
+
* the bag's own keys over them, and the composite's marker (`--<prefix>-composite-<group>`), so
|
|
170
|
+
* `_on<Group><Member>` still finds it.
|
|
171
|
+
*
|
|
172
|
+
* A composite a component's OWN rule names is part of that layer's defaults, the same as a
|
|
173
|
+
* `fontSize` beside it: the layer's element beats it with its variants, and a key the bag spells
|
|
174
|
+
* next to it overrides it (`{ textVariant: 'ui6', lineHeight: 'tight' }`). Forwarded to the element
|
|
175
|
+
* as a prop instead, it arrived as a caller's pick, and a caller's pick beats the element's
|
|
176
|
+
* variants (see {@link compositePropRuleClasses}), so FormInput's own `ui6` beat the `size="xs"`
|
|
177
|
+
* it renders its Input at. Only a composite passed as a prop at a call site takes that route.
|
|
178
|
+
*
|
|
179
|
+
* `element` is the layer's element. A key is a composite when the element declares it as one, so a
|
|
180
|
+
* plain-tag layer (which declares nothing) and a key naming no member are left as they are.
|
|
181
|
+
*/
|
|
182
|
+
declare function inlineRuleComposites(config: Config, element: unknown, bag: Record<string, unknown>): Record<string, unknown>;
|
|
144
183
|
/**
|
|
145
184
|
* The props a style rule's bag sends to each layer's ELEMENT rather than to the stylesheet, for one
|
|
146
185
|
* instance. The same split the emitted module makes when it builds a layer's routed bundle.
|
|
147
186
|
*
|
|
148
187
|
* What makes a bag key CSS is that a style property claims it: that value lives in the stylesheet
|
|
149
188
|
* under the rule's condition class. Everything else in the bag is a prop of the layer's element and
|
|
150
|
-
* travels to it as one, whether or not the element declares it:
|
|
151
|
-
* `Text`-backed layer, which the receiver resolves to its own class), the receiver's own variant or
|
|
189
|
+
* travels to it as one, whether or not the element declares it: the receiver's own variant or
|
|
152
190
|
* scalar prop (`size: 'sm'` on an Icon layer), or an attribute of a plain element (`as: 'div'`,
|
|
153
191
|
* `type: 'button'`, `aria-hidden`), or a value the rule hands a receiver's forward or slot
|
|
154
192
|
* (`children: 'AI'` on a badge layer, `icon: 'XIcon'` on a button layer), which the emitted module
|
|
155
|
-
* routes the same way. A style property under another name
|
|
156
|
-
*
|
|
193
|
+
* routes the same way. A style property under another name, a composite (written into the rule's
|
|
194
|
+
* declarations, {@link inlineRuleComposites}) and motion stay out, since the stylesheet and the
|
|
195
|
+
* motion classes already carry them.
|
|
157
196
|
*
|
|
158
197
|
* Forwarding rather than resolving is what makes a layer's styling live-editable at all: a class only
|
|
159
198
|
* exists once a build has emitted the stylesheet, so an edit that makes a NEW style rule paints
|
|
@@ -258,4 +297,4 @@ declare function stylePropClasses(config: Config, path: string): Map<string, str
|
|
|
258
297
|
*/
|
|
259
298
|
declare function modifierUtilitiesUsed(config: Config): Set<string>;
|
|
260
299
|
//#endregion
|
|
261
|
-
export { ComponentRuleClass, SplitStyleProps, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, isForceModeProp, kebabComponent, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, stylePropClassBase, stylePropClassName, stylePropClasses, styleRuleMotionClasses };
|
|
300
|
+
export { ComponentRuleClass, PickedComposite, SplitStyleProps, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, stylePropClassBase, stylePropClassName, stylePropClasses, styleRuleMotionClasses };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isRef, kindOf, memberOf, pathOf, refLeaf } from "./utils/refs.js";
|
|
2
2
|
import { forwardsOf, rootLayerOf } from "../entities/system/element.js";
|
|
3
3
|
import { createSliceMemo } from "./memo.js";
|
|
4
|
-
import { classPrefixOf } from "../entities/system/Settings.js";
|
|
4
|
+
import { classPrefixOf, varPrefixOf } from "../entities/system/Settings.js";
|
|
5
5
|
import { storedPreview } from "./preview-store.js";
|
|
6
6
|
import { authoredValue, className, negatedCssValue, renderStyleValue, resolveComponentProps, stylePropLeafForToken, stylePropValueLeaves, stylePropertyPathFor } from "./projections.js";
|
|
7
7
|
import { ruleApplies } from "./layer-styles.js";
|
|
@@ -156,7 +156,11 @@ function componentRuleClasses(config, path) {
|
|
|
156
156
|
* that slice is part of the key.
|
|
157
157
|
*/
|
|
158
158
|
const ruleClassCache = createSliceMemo({
|
|
159
|
-
kinds: [
|
|
159
|
+
kinds: [
|
|
160
|
+
"component",
|
|
161
|
+
"composite",
|
|
162
|
+
"settings"
|
|
163
|
+
],
|
|
160
164
|
compute: (_config) => /* @__PURE__ */ new Map()
|
|
161
165
|
});
|
|
162
166
|
function computeRuleClasses(config, path) {
|
|
@@ -178,6 +182,44 @@ function computeRuleClasses(config, path) {
|
|
|
178
182
|
});
|
|
179
183
|
}
|
|
180
184
|
}
|
|
185
|
+
out.push(...compositePropRuleClasses(config, path));
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The classes a component's composite PROPS apply: one per member of the bound group, conditional on
|
|
190
|
+
* the prop naming that member — `variant: 'body1'` → `uds-text-root-variant-body1` — on the layer the
|
|
191
|
+
* prop names (the root by default).
|
|
192
|
+
*
|
|
193
|
+
* A composite a caller picks is an explicit choice, not a default the layer starts from, so it has to
|
|
194
|
+
* win over the component's own type. The shared composite class can't: it is emitted before every
|
|
195
|
+
* component rule at the same zero specificity, so a layer that sets `fontSize` itself kept its size
|
|
196
|
+
* whatever preset was asked for (UDS-4610). As a rule class, the member's bag is emitted after the
|
|
197
|
+
* component's `when` rules at their specificity, so it beats the layer's base rules and its variants,
|
|
198
|
+
* and a style-prop utility — a later cascade layer — still beats it.
|
|
199
|
+
*
|
|
200
|
+
* Both kinds {@link compositePropsOf} reads: a prop the component declares (an extension a linking
|
|
201
|
+
* config adds is part of the resolved body), and one it exposes from a component layer through
|
|
202
|
+
* `forwards` — its module takes that value out of the bag, so the receiver never sees it, and the
|
|
203
|
+
* class has to be this component's.
|
|
204
|
+
*/
|
|
205
|
+
function compositePropRuleClasses(config, path) {
|
|
206
|
+
const declared = compositePropsOf(config, path);
|
|
207
|
+
if (!declared) return [];
|
|
208
|
+
const base = componentClassBase(config, path);
|
|
209
|
+
const out = [];
|
|
210
|
+
for (const [prop, groupRef, layer] of declared.groups) {
|
|
211
|
+
const group = pathOf(groupRef);
|
|
212
|
+
for (const composite of config.list("composite")) {
|
|
213
|
+
if (!composite.path.startsWith(`${group}/`)) continue;
|
|
214
|
+
const when = { [prop]: composite.path.slice(group.length + 1) };
|
|
215
|
+
out.push({
|
|
216
|
+
layer,
|
|
217
|
+
when,
|
|
218
|
+
className: `${base}-${layer}-${ruleCondition(when)}`,
|
|
219
|
+
composite: composite.path
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
181
223
|
return out;
|
|
182
224
|
}
|
|
183
225
|
/** The MEMBER a bag value names, under the group its prop binds — the value is either the qualified
|
|
@@ -290,27 +332,46 @@ function componentMotionClasses(config, path) {
|
|
|
290
332
|
* defaults, because a value the caller didn't set has to read the default — the same rule
|
|
291
333
|
* {@link layerStyles} follows.
|
|
292
334
|
*
|
|
293
|
-
* Keyed by layer
|
|
294
|
-
*
|
|
335
|
+
* Keyed by layer: a prop lands on the layer it names (the root by default), or on the layer that
|
|
336
|
+
* forwards it. Each member contributes two classes, the composite's own and the component's rule
|
|
337
|
+
* class for it; {@link componentRuleClasses} lists the second too, for the module and the sheet, so
|
|
338
|
+
* a projection reading both skips its `composite` entries.
|
|
295
339
|
*/
|
|
296
340
|
function componentPropClasses(config, path, state = {}) {
|
|
341
|
+
const out = {};
|
|
342
|
+
for (const { prop, layer, member, value } of componentPropComposites(config, path, state)) {
|
|
343
|
+
const classes = out[layer] ?? [];
|
|
344
|
+
classes.push(className(config, "composite", member), `${componentClassBase(config, path)}-${layer}-${ruleCondition({ [prop]: value })}`);
|
|
345
|
+
out[layer] = classes;
|
|
346
|
+
}
|
|
347
|
+
return out;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* The composites an instance's props pick, resolved over the component's defaults: each one beats
|
|
351
|
+
* the component's own rules on its layer (see {@link compositePropRuleClasses}).
|
|
352
|
+
*
|
|
353
|
+
* A projection that paints a component's rule declarations as props, as the component editor's cells
|
|
354
|
+
* do, needs this to hold that precedence: a rule's `fontSize` painted as a prop would otherwise beat
|
|
355
|
+
* the member a caller picked, where the stylesheet has the member win.
|
|
356
|
+
*/
|
|
357
|
+
function componentPropComposites(config, path, state = {}) {
|
|
297
358
|
const declared = compositePropsOf(config, path);
|
|
298
|
-
if (!declared) return
|
|
299
|
-
const { groups, defaultProps } = declared;
|
|
300
|
-
if (groups.length === 0) return {};
|
|
359
|
+
if (!declared || declared.groups.length === 0) return [];
|
|
301
360
|
const resolved = {
|
|
302
|
-
...defaultProps,
|
|
361
|
+
...declared.defaultProps,
|
|
303
362
|
...state
|
|
304
363
|
};
|
|
305
|
-
const out =
|
|
306
|
-
for (const [prop, group, layer] of groups) {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
if (
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
364
|
+
const out = [];
|
|
365
|
+
for (const [prop, group, layer] of declared.groups) {
|
|
366
|
+
const member = memberNamed(group, resolved[prop]);
|
|
367
|
+
if (member === void 0) continue;
|
|
368
|
+
if (!config.resolve("composite", member)) continue;
|
|
369
|
+
out.push({
|
|
370
|
+
prop,
|
|
371
|
+
layer,
|
|
372
|
+
member,
|
|
373
|
+
value: member.slice(pathOf(group).length + 1)
|
|
374
|
+
});
|
|
314
375
|
}
|
|
315
376
|
return out;
|
|
316
377
|
}
|
|
@@ -329,11 +390,13 @@ function compositePropsOf(config, path, seen = /* @__PURE__ */ new Set()) {
|
|
|
329
390
|
let defaultProps = {};
|
|
330
391
|
for (const [prop, decl] of Object.entries(props)) {
|
|
331
392
|
if (decl?.type !== "composite") continue;
|
|
393
|
+
const named = isRef(decl.layer) ? pathOf(decl.layer.__ref) : decl.layer;
|
|
394
|
+
const layer = typeof named === "string" && named in layers ? named : root;
|
|
332
395
|
const group = isRef(decl.value) ? decl.value.__ref : void 0;
|
|
333
396
|
if (group) groups.push([
|
|
334
397
|
prop,
|
|
335
398
|
group,
|
|
336
|
-
|
|
399
|
+
layer
|
|
337
400
|
]);
|
|
338
401
|
}
|
|
339
402
|
const inner = new Set([...seen, path]);
|
|
@@ -378,18 +441,68 @@ const compositePropsCache = createSliceMemo({
|
|
|
378
441
|
compute: (_config) => /* @__PURE__ */ new Map()
|
|
379
442
|
});
|
|
380
443
|
/**
|
|
444
|
+
* A style rule's bag with each composite it names written out in place: the member's styles first,
|
|
445
|
+
* the bag's own keys over them, and the composite's marker (`--<prefix>-composite-<group>`), so
|
|
446
|
+
* `_on<Group><Member>` still finds it.
|
|
447
|
+
*
|
|
448
|
+
* A composite a component's OWN rule names is part of that layer's defaults, the same as a
|
|
449
|
+
* `fontSize` beside it: the layer's element beats it with its variants, and a key the bag spells
|
|
450
|
+
* next to it overrides it (`{ textVariant: 'ui6', lineHeight: 'tight' }`). Forwarded to the element
|
|
451
|
+
* as a prop instead, it arrived as a caller's pick, and a caller's pick beats the element's
|
|
452
|
+
* variants (see {@link compositePropRuleClasses}), so FormInput's own `ui6` beat the `size="xs"`
|
|
453
|
+
* it renders its Input at. Only a composite passed as a prop at a call site takes that route.
|
|
454
|
+
*
|
|
455
|
+
* `element` is the layer's element. A key is a composite when the element declares it as one, so a
|
|
456
|
+
* plain-tag layer (which declares nothing) and a key naming no member are left as they are.
|
|
457
|
+
*/
|
|
458
|
+
function inlineRuleComposites(config, element, bag) {
|
|
459
|
+
if (!isRef(element)) return bag;
|
|
460
|
+
const declared = resolveComponentProps(config, pathOf(element.__ref));
|
|
461
|
+
let inlined;
|
|
462
|
+
const rest = {};
|
|
463
|
+
for (const [key, value] of Object.entries(bag)) {
|
|
464
|
+
const bound = declared[key]?.type === "composite" ? declared[key].value : void 0;
|
|
465
|
+
const member = isRef(bound) ? memberNamed(bound.__ref, value) : void 0;
|
|
466
|
+
const composite = member ? config.resolve("composite", member) : void 0;
|
|
467
|
+
if (!member || !composite) {
|
|
468
|
+
rest[key] = value;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const { styles = {} } = composite.toJSON();
|
|
472
|
+
const at = member.indexOf("/");
|
|
473
|
+
inlined = mergeStyleBags(inlined ?? {}, {
|
|
474
|
+
...styles,
|
|
475
|
+
[`--${varPrefixOf(config)}-composite-${member.slice(0, at)}`]: member.slice(at + 1)
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
return inlined ? mergeStyleBags(inlined, rest) : bag;
|
|
479
|
+
}
|
|
480
|
+
/** `over` laid on `base`, with a modifier block (`_md`, `_hover`) merged key by key rather than
|
|
481
|
+
* replaced, so a bag that adds one breakpoint value keeps the composite's others. */
|
|
482
|
+
function mergeStyleBags(base, over) {
|
|
483
|
+
const out = { ...base };
|
|
484
|
+
for (const [key, value] of Object.entries(over)) {
|
|
485
|
+
const prior = out[key];
|
|
486
|
+
out[key] = key.startsWith("_") && isPlainBlock(prior) && isPlainBlock(value) ? mergeStyleBags(prior, value) : value;
|
|
487
|
+
}
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
function isPlainBlock(value) {
|
|
491
|
+
return typeof value === "object" && value !== null && !isRef(value);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
381
494
|
* The props a style rule's bag sends to each layer's ELEMENT rather than to the stylesheet, for one
|
|
382
495
|
* instance. The same split the emitted module makes when it builds a layer's routed bundle.
|
|
383
496
|
*
|
|
384
497
|
* What makes a bag key CSS is that a style property claims it: that value lives in the stylesheet
|
|
385
498
|
* under the rule's condition class. Everything else in the bag is a prop of the layer's element and
|
|
386
|
-
* travels to it as one, whether or not the element declares it:
|
|
387
|
-
* `Text`-backed layer, which the receiver resolves to its own class), the receiver's own variant or
|
|
499
|
+
* travels to it as one, whether or not the element declares it: the receiver's own variant or
|
|
388
500
|
* scalar prop (`size: 'sm'` on an Icon layer), or an attribute of a plain element (`as: 'div'`,
|
|
389
501
|
* `type: 'button'`, `aria-hidden`), or a value the rule hands a receiver's forward or slot
|
|
390
502
|
* (`children: 'AI'` on a badge layer, `icon: 'XIcon'` on a button layer), which the emitted module
|
|
391
|
-
* routes the same way. A style property under another name
|
|
392
|
-
*
|
|
503
|
+
* routes the same way. A style property under another name, a composite (written into the rule's
|
|
504
|
+
* declarations, {@link inlineRuleComposites}) and motion stay out, since the stylesheet and the
|
|
505
|
+
* motion classes already carry them.
|
|
393
506
|
*
|
|
394
507
|
* Forwarding rather than resolving is what makes a layer's styling live-editable at all: a class only
|
|
395
508
|
* exists once a build has emitted the stylesheet, so an edit that makes a NEW style rule paints
|
|
@@ -416,7 +529,7 @@ function layerRoutedProps(config, path, state = {}) {
|
|
|
416
529
|
}) ?? prop;
|
|
417
530
|
if (config.resolve("style-property", styleKey)) continue;
|
|
418
531
|
const decl = declared[prop];
|
|
419
|
-
if (decl?.type === "styleProperty" || decl?.type === "motion") continue;
|
|
532
|
+
if (decl?.type === "styleProperty" || decl?.type === "motion" || decl?.type === "composite") continue;
|
|
420
533
|
out[layer] ??= {};
|
|
421
534
|
out[layer][prop] = authoredValue(config, prop, value);
|
|
422
535
|
}
|
|
@@ -715,4 +828,4 @@ function classesUsedBy(config, stored, token, bagsOf) {
|
|
|
715
828
|
return found;
|
|
716
829
|
}
|
|
717
830
|
//#endregion
|
|
718
|
-
export { componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, isForceModeProp, kebabComponent, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, stylePropClassBase, stylePropClassName, stylePropClasses, styleRuleMotionClasses };
|
|
831
|
+
export { componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, stylePropClassBase, stylePropClassName, stylePropClasses, styleRuleMotionClasses };
|
|
@@ -1078,6 +1078,7 @@ function classesFor(config, path, layer, state, extra) {
|
|
|
1078
1078
|
parts.push(...styleRuleMotionClasses(config, path, state)[layer] ?? []);
|
|
1079
1079
|
for (const rule of componentRuleClasses(config, path)) {
|
|
1080
1080
|
if (rule.layer !== layer) continue;
|
|
1081
|
+
if (rule.composite) continue;
|
|
1081
1082
|
if (Object.entries(rule.when).every(([prop, value]) => String(state[prop]) === String(value))) parts.push(rule.className);
|
|
1082
1083
|
}
|
|
1083
1084
|
parts.push(...extra);
|
|
@@ -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 = 20260928215509;
|
|
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 = 20260928215509;
|
|
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 > 20260928215509) 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 ?? 20260928215509;
|
|
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 ?? 20260928215509;
|
|
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 ?? 20260928215509;
|
|
183
183
|
const entries = [];
|
|
184
184
|
const held = [];
|
|
185
185
|
const retired = [];
|