@yahoo/uds-create-config 4.25.1 → 4.26.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.
- package/dist/entities/system/Component.js +9 -4
- package/dist/entities/system/Icon.js +6 -10
- package/dist/entities/system/defineComponent.js +11 -10
- package/dist/entities/system/glyph-identity.js +54 -9
- package/dist/framework/bindings.d.ts +97 -0
- package/dist/framework/bindings.js +173 -0
- package/dist/framework/prop-surface.d.ts +23 -1
- package/dist/framework/prop-surface.js +40 -2
- package/dist/framework/render-spec.d.ts +4 -0
- package/dist/framework/render-spec.js +17 -5
- package/dist/framework/slot-host.d.ts +2 -0
- package/dist/framework/slot-host.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/renderer/wrappers/glyph-props.js +6 -7
- package/dist/spec/glyph-members.d.ts +18 -0
- package/dist/spec/glyph-members.js +18 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -1
|
@@ -8,7 +8,7 @@ import { defineSubEntity } from "../../framework/defineSubEntity.js";
|
|
|
8
8
|
import { derivedColor, gradient } from "./color.js";
|
|
9
9
|
import { listedIn } from "../../framework/utils/enumerated.js";
|
|
10
10
|
import { valueSchemaOf } from "../../framework/value-domain.js";
|
|
11
|
-
import { glyphLeafOf } from "./glyph-identity.js";
|
|
11
|
+
import { bindPreviewGlyphs, glyphLeafOf } from "./glyph-identity.js";
|
|
12
12
|
import { Style, addBagKeyIssues, anatomyPropValue, bagNameEdges, bagRewriteName, bindBagTokens, collectBag, isPlainRecord, openBag, styleValue } from "./style-bag.js";
|
|
13
13
|
import { storedPreview } from "../../framework/preview-store.js";
|
|
14
14
|
import { glyphPropsOf, propValueDomain, refListOf, resolveComponentProps, stylePropValueLeaves, validateComponentProps } from "../../framework/projections.js";
|
|
@@ -21,7 +21,7 @@ import { File } from "./File.js";
|
|
|
21
21
|
import { iconDomainInvariants } from "./icon-domain-invariants.js";
|
|
22
22
|
import { rootLayerInvariants } from "./root-layer-invariants.js";
|
|
23
23
|
import { Token } from "./Token.js";
|
|
24
|
-
import { carriesContent, intoName, isValueSlot, routedPropIn, routesContent } from "../../framework/prop-surface.js";
|
|
24
|
+
import { carriesContent, intoName, isValueSlot, renderBoundProp, routedPropIn, routesContent } from "../../framework/prop-surface.js";
|
|
25
25
|
import { z } from "zod";
|
|
26
26
|
//#region src/entities/system/Component.ts
|
|
27
27
|
/**
|
|
@@ -985,7 +985,10 @@ const Preview = defineSubEntity({
|
|
|
985
985
|
for (const [name, decl] of Object.entries(body.props ?? {})) when[name] = conditionValue(name, decl, config).optional();
|
|
986
986
|
return z.strictObject({
|
|
987
987
|
when: z.strictObject(when, { error: previewShapeError }).nullable().default(null),
|
|
988
|
-
props: z.record(z.string(), z.unknown())
|
|
988
|
+
props: z.record(z.string(), z.unknown()).transform((props) => bindPreviewGlyphs({
|
|
989
|
+
config,
|
|
990
|
+
props
|
|
991
|
+
}))
|
|
989
992
|
}, { error: previewShapeError });
|
|
990
993
|
}
|
|
991
994
|
});
|
|
@@ -1329,11 +1332,13 @@ const AnatomyNode = defineSubEntity({
|
|
|
1329
1332
|
continue;
|
|
1330
1333
|
}
|
|
1331
1334
|
if (!isPlainObject(value) || !("$state" in value)) continue;
|
|
1335
|
+
const read = renderBoundProp(value);
|
|
1336
|
+
if (read !== void 0 && body.props && read in body.props) continue;
|
|
1332
1337
|
ctx.issues.push({
|
|
1333
1338
|
code: "custom",
|
|
1334
1339
|
input: value,
|
|
1335
1340
|
path: ["props", prop],
|
|
1336
|
-
message: `\`${prop}\` is bound with \`$state\`, which anatomy uses for \`visible\` and for an \`element\` a prop chooses,
|
|
1341
|
+
message: `\`${prop}\` is bound with \`$state\`, which anatomy uses for \`visible\` and for an \`element\` a prop chooses, and for a bare read of one of this component's own props that the render passes down (\`{ "$state": "/<prop>" }\`); any other binding would reach the element as-is and paint nothing. Content reaches a node through the prop's declaration — \`{ "type": "forward", "target": "<layer>/children" }\` lands it in that layer, and a \`{ "element": "Slot", "props": { "name": "<prop>" } }\` node among a parent's \`children\` marks where a slot's content goes. To vary content by a prop, give each state its own node and choose between them with \`visible\`.`
|
|
1337
1342
|
});
|
|
1338
1343
|
}
|
|
1339
1344
|
const slotAnatomyProp = slotAnatomyPropOf({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isRef, pathOf } from "../../framework/utils/refs.js";
|
|
2
2
|
import { defineEntity } from "../../framework/defineEntity.js";
|
|
3
|
-
import { glyphOfLibrary, recordGlyphIdentities } from "./glyph-identity.js";
|
|
3
|
+
import { glyphOfLibrary, libraryGlyphFacts, recordGlyphIdentities } from "./glyph-identity.js";
|
|
4
4
|
import { Package } from "./Package.js";
|
|
5
5
|
import { iconDomainInvariants } from "./icon-domain-invariants.js";
|
|
6
6
|
import { iconMemberMetadata, iconMetadataDeclaration } from "./icon-metadata.js";
|
|
@@ -93,15 +93,11 @@ glyphs: ({ input, body }) => {
|
|
|
93
93
|
* open-bag pass turned it into. Either is answered as the member name when the export is one of
|
|
94
94
|
* this library's and comes from the module its `package` names.
|
|
95
95
|
*/
|
|
96
|
-
authoredValue: ({ config, target, leaves }) => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
module,
|
|
102
|
-
leaves
|
|
103
|
-
});
|
|
104
|
-
},
|
|
96
|
+
authoredValue: ({ config, target, leaves }) => glyphOfLibrary(libraryGlyphFacts({
|
|
97
|
+
config,
|
|
98
|
+
target,
|
|
99
|
+
leaves
|
|
100
|
+
})),
|
|
105
101
|
invariants: (config) => iconDomainInvariants(config)
|
|
106
102
|
});
|
|
107
103
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { canonicalCondition, isRef, kindOf, memberRef, pathOf } from "../../framework/utils/refs.js";
|
|
2
|
-
import { isComponentType } from "./glyph-identity.js";
|
|
2
|
+
import { glyphIdentityOf, glyphPackageRefOf, isComponentType } from "./glyph-identity.js";
|
|
3
3
|
import "../../framework/preview-store.js";
|
|
4
4
|
import { Component, canonicalWhen, layerRef } from "./Component.js";
|
|
5
5
|
//#region src/entities/system/defineComponent.ts
|
|
@@ -46,13 +46,10 @@ function build(body) {
|
|
|
46
46
|
* this runs when a config is registered, and a reader can't tell which made the config it holds.
|
|
47
47
|
*
|
|
48
48
|
* A component handed as a value (`icon: PlusIcon`) stores as the bare ref the registry keys it under,
|
|
49
|
-
* so the receiving component gets the component back rather than a node.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* (`Preview.fields`) and never re-bound to a member, so a ref written here would reach the build as
|
|
54
|
-
* a package export the package row never lists. An absent default renders nothing; the build fills
|
|
55
|
-
* the prop from the import specifier (`extractPreviewProps`).
|
|
49
|
+
* so the receiving component gets the component back rather than a node. A glyph of a registered
|
|
50
|
+
* library stores as its package export, and the owner-bound pass (`Preview.fields`) binds that to
|
|
51
|
+
* the library member, the spelling the build writes. A component neither side can name is dropped:
|
|
52
|
+
* an absent default renders nothing, where a guessed ref would render the wrong thing.
|
|
56
53
|
*/
|
|
57
54
|
function storableProps(props) {
|
|
58
55
|
if (props === void 0) return void 0;
|
|
@@ -68,9 +65,13 @@ function storableProps(props) {
|
|
|
68
65
|
function isReactElement(value) {
|
|
69
66
|
return typeof value === "object" && value !== null && "$$typeof" in value && "props" in value;
|
|
70
67
|
}
|
|
71
|
-
/** A component handed as a value, as the ref that names it
|
|
68
|
+
/** A component handed as a value, as the ref that names it: a component of this system under its
|
|
69
|
+
* path, a glyph a library handed `register` as its package export. `undefined` for one neither
|
|
70
|
+
* names. */
|
|
72
71
|
function storableComponentRef(value) {
|
|
73
|
-
|
|
72
|
+
if (isComponentElement(value) && value.path) return Component.ref(value.path);
|
|
73
|
+
const identity = glyphIdentityOf(value);
|
|
74
|
+
return identity ? glyphPackageRefOf(identity) : void 0;
|
|
74
75
|
}
|
|
75
76
|
const FRAGMENT = Symbol.for("react.fragment");
|
|
76
77
|
/**
|
|
@@ -99,6 +99,25 @@ const glyphPackageRef = z.custom(isComponentType).transform((value, ctx) => {
|
|
|
99
99
|
});
|
|
100
100
|
glyphPackageRef._zod.toJSONSchema = () => ({ not: {} });
|
|
101
101
|
/**
|
|
102
|
+
* The facts for the library a target names. `target` is the library's ref (`icon:phosphor`, with a
|
|
103
|
+
* source qualifier for a borrowed set), whose config answers for its `package`. `leaves` may be
|
|
104
|
+
* handed in by a domain that has already expanded them.
|
|
105
|
+
*/
|
|
106
|
+
function libraryGlyphFacts({ config, target, leaves = memberLeaves(config, target) }) {
|
|
107
|
+
const library = pathOf(target);
|
|
108
|
+
const named = (config.configFor(target)?.groupMeta("icon", library))?.package;
|
|
109
|
+
return {
|
|
110
|
+
library,
|
|
111
|
+
module: isRef(named) ? pathOf(named) : void 0,
|
|
112
|
+
leaves
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Whether a library ships a glyph: the identity's specifier is the library's package and its name
|
|
116
|
+
* is one of the members. */
|
|
117
|
+
function libraryClaims(facts, identity) {
|
|
118
|
+
return facts.module === identity.specifier && facts.leaves.includes(identity.name);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
102
121
|
* The owner-bound form of a glyph for one library: a component or its `package:` ref, answered as
|
|
103
122
|
* the member name so the domain binds it like a written leaf. Refused, naming the reason, when the
|
|
104
123
|
* export belongs to another module than the library draws from or is not one of its members.
|
|
@@ -138,16 +157,42 @@ function glyphOfLibrary({ library, module, leaves }) {
|
|
|
138
157
|
* so a glyph of a library the key does not reach says so.
|
|
139
158
|
*/
|
|
140
159
|
function glyphLeafOf({ config, libraries }) {
|
|
141
|
-
const arms = libraries.map((library) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
module: isRef(named) ? pathOf(named) : void 0,
|
|
146
|
-
leaves: memberLeaves(config, `icon:${library}`)
|
|
147
|
-
});
|
|
148
|
-
});
|
|
160
|
+
const arms = libraries.map((library) => glyphOfLibrary(libraryGlyphFacts({
|
|
161
|
+
config,
|
|
162
|
+
target: `icon:${library}`
|
|
163
|
+
})));
|
|
149
164
|
if (arms.length === 1) return arms[0];
|
|
150
165
|
return z.union(arms);
|
|
151
166
|
}
|
|
167
|
+
/** `icon:<library>/<name>` for a glyph identity a library of this config claims; `undefined` when
|
|
168
|
+
* none does. */
|
|
169
|
+
function glyphMemberRefOf(config, identity) {
|
|
170
|
+
for (const { path } of config.groups("icon")) if (libraryClaims(libraryGlyphFacts({
|
|
171
|
+
config,
|
|
172
|
+
target: `icon:${path}`
|
|
173
|
+
}), identity)) return { __ref: `icon:${path}/${identity.name}` };
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Preview props with every glyph bound to its library member.
|
|
177
|
+
*
|
|
178
|
+
* A glyph handed under `.preview()` is stored open as the package export it is
|
|
179
|
+
* (`package:<specifier>#<name>`), since the props there are not each bound to a declared prop the
|
|
180
|
+
* way `defaultProps` are. Once an owner is known, this pass binds each such ref to
|
|
181
|
+
* `icon:<library>/<name>` through the library whose package ships it, at the top level and inside a
|
|
182
|
+
* stored element's props, which is the spelling the build writes when it reads `.preview()` from
|
|
183
|
+
* source. A `package:` ref no library claims is another package's export and stays as written.
|
|
184
|
+
*/
|
|
185
|
+
function bindPreviewGlyphs({ config, props }) {
|
|
186
|
+
const bind = (value) => {
|
|
187
|
+
if (typeof value !== "object" || value === null) return value;
|
|
188
|
+
if (Array.isArray(value)) return value.map(bind);
|
|
189
|
+
const identity = glyphIdentityOfRef(value);
|
|
190
|
+
if (identity) return glyphMemberRefOf(config, identity) ?? value;
|
|
191
|
+
const out = {};
|
|
192
|
+
for (const [key, member] of Object.entries(value)) out[key] = bind(member);
|
|
193
|
+
return out;
|
|
194
|
+
};
|
|
195
|
+
return bind(props);
|
|
196
|
+
}
|
|
152
197
|
//#endregion
|
|
153
|
-
export { glyphLeafOf, glyphOfLibrary, glyphPackageRef, isComponentType, recordGlyphIdentities };
|
|
198
|
+
export { bindPreviewGlyphs, glyphIdentityOf, glyphLeafOf, glyphOfLibrary, glyphPackageRef, glyphPackageRefOf, isComponentType, libraryGlyphFacts, recordGlyphIdentities };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Config } from "./Config.js";
|
|
2
|
+
import { BindingKind } from "./prop-surface.js";
|
|
3
|
+
|
|
4
|
+
//#region src/framework/bindings.d.ts
|
|
5
|
+
/** Which marker a value is, by the key that makes it one. */
|
|
6
|
+
type MarkerKind = 'state' | 'bindState' | 'cond' | 'item' | 'index' | 'computed' | 'template' | 'other';
|
|
7
|
+
/**
|
|
8
|
+
* The marker a value is, or `undefined` for a literal. A record with any other `$`-prefixed key is
|
|
9
|
+
* `other`: a directive this module can name but not settle.
|
|
10
|
+
*/
|
|
11
|
+
declare function markerKind(value: unknown): MarkerKind | undefined;
|
|
12
|
+
/** Whether a value reads state anywhere, however deep: a `$state` or `$bindState`, in a prop value or
|
|
13
|
+
* in a condition. */
|
|
14
|
+
declare function readsState(value: unknown): boolean;
|
|
15
|
+
/** The state path a `$state` or `$bindState` reads, as written, or `undefined` for any other value. */
|
|
16
|
+
declare function bindingPath(value: unknown): string | undefined;
|
|
17
|
+
/** A `$cond` value's parts, or `undefined` for any other value. */
|
|
18
|
+
declare function condArms(value: unknown): {
|
|
19
|
+
when: unknown;
|
|
20
|
+
ifTrue: unknown;
|
|
21
|
+
ifFalse: unknown;
|
|
22
|
+
} | undefined;
|
|
23
|
+
/** Whether a marker sits anywhere in a value, however deep. */
|
|
24
|
+
declare function containsMarker(value: unknown): boolean;
|
|
25
|
+
/** A value settled against a prop state, or the fact that it could not be. */
|
|
26
|
+
type ResolvedBinding = {
|
|
27
|
+
readonly resolved: true;
|
|
28
|
+
readonly value: unknown;
|
|
29
|
+
} | {
|
|
30
|
+
readonly resolved: false;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* A value settled against `state`, the whole prop state of the instance being read: its defaults,
|
|
34
|
+
* overrides and cell coordinate.
|
|
35
|
+
*
|
|
36
|
+
* A `$state` or `$bindState` reads the state, and a prop the state lacks reads `undefined`. A
|
|
37
|
+
* `$cond` settles only when its condition reads props the state holds, since a capture can key a
|
|
38
|
+
* condition on a seed no prop state carries, and reading that seed as `undefined` would pick a
|
|
39
|
+
* branch the render never paints. Only the branch taken has to settle. Any other directive, and
|
|
40
|
+
* `$template` in particular, which interpolates an unread path to `''`, stays unresolved.
|
|
41
|
+
*/
|
|
42
|
+
declare function resolveBinding(value: unknown, state: Record<string, unknown>): ResolvedBinding;
|
|
43
|
+
/** A read of one prop: `{ $state: '/tone' }`. */
|
|
44
|
+
declare function stateRef(prop: string): {
|
|
45
|
+
$state: string;
|
|
46
|
+
};
|
|
47
|
+
/** A two-way read of one state key, for a control that writes it back: `{ $bindState: '/open' }`. */
|
|
48
|
+
declare function bindStateRef(key: string): {
|
|
49
|
+
$bindState: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* A test of one prop, in the grammar `visible` and `$cond` share: truthy, or equal to a value, and
|
|
53
|
+
* negated with `not`. Naming `eq` makes it a comparison even when its value is `undefined`.
|
|
54
|
+
*/
|
|
55
|
+
declare function stateTest(test: {
|
|
56
|
+
prop: string;
|
|
57
|
+
eq?: unknown;
|
|
58
|
+
not?: boolean;
|
|
59
|
+
}): Record<string, unknown>;
|
|
60
|
+
/** A value chosen by a condition: `{ $cond, $then, $else }`. */
|
|
61
|
+
declare function condValue({
|
|
62
|
+
when,
|
|
63
|
+
ifTrue,
|
|
64
|
+
ifFalse
|
|
65
|
+
}: {
|
|
66
|
+
when: unknown;
|
|
67
|
+
ifTrue: unknown;
|
|
68
|
+
ifFalse: unknown;
|
|
69
|
+
}): {
|
|
70
|
+
$cond: unknown;
|
|
71
|
+
$then: unknown;
|
|
72
|
+
$else: unknown;
|
|
73
|
+
};
|
|
74
|
+
/** The component prop that drives one of a layer's props, and how it gets there. */
|
|
75
|
+
interface LayerPropDriver {
|
|
76
|
+
/** The component prop, without a pointer slash. */
|
|
77
|
+
readonly prop: string;
|
|
78
|
+
/** A declared forward, or a binding the render recorded. */
|
|
79
|
+
readonly kind: BindingKind;
|
|
80
|
+
/** For a render binding, the anatomy node it sits on, which is what clearing it edits. */
|
|
81
|
+
readonly node?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Every prop of `layer` a component prop drives, keyed by the layer prop. A declared forward wins
|
|
85
|
+
* where a render binding names the same layer prop.
|
|
86
|
+
*/
|
|
87
|
+
declare function layerPropDrivers({
|
|
88
|
+
config,
|
|
89
|
+
path,
|
|
90
|
+
layer
|
|
91
|
+
}: {
|
|
92
|
+
config: Config;
|
|
93
|
+
path: string;
|
|
94
|
+
layer: string;
|
|
95
|
+
}): Record<string, LayerPropDriver>;
|
|
96
|
+
//#endregion
|
|
97
|
+
export { LayerPropDriver, MarkerKind, ResolvedBinding, bindStateRef, bindingPath, condArms, condValue, containsMarker, layerPropDrivers, markerKind, readsState, resolveBinding, stateRef, stateTest };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { isExpression, readStatePointer, resolveVisibility, slotTargetsOf, valueForwardsOf } from "./render-spec.js";
|
|
2
|
+
import { renderBindingsOf } from "./prop-surface.js";
|
|
3
|
+
//#region src/framework/bindings.ts
|
|
4
|
+
const KIND_BY_KEY = [
|
|
5
|
+
["$bindState", "bindState"],
|
|
6
|
+
["$cond", "cond"],
|
|
7
|
+
["$computed", "computed"],
|
|
8
|
+
["$template", "template"],
|
|
9
|
+
["$item", "item"],
|
|
10
|
+
["$index", "index"],
|
|
11
|
+
["$state", "state"]
|
|
12
|
+
];
|
|
13
|
+
function isRecord(value) {
|
|
14
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The marker a value is, or `undefined` for a literal. A record with any other `$`-prefixed key is
|
|
18
|
+
* `other`: a directive this module can name but not settle.
|
|
19
|
+
*/
|
|
20
|
+
function markerKind(value) {
|
|
21
|
+
if (!isRecord(value)) return void 0;
|
|
22
|
+
for (const [key, kind] of KIND_BY_KEY) if (key in value) return kind;
|
|
23
|
+
return Object.keys(value).some((key) => key.startsWith("$")) ? "other" : void 0;
|
|
24
|
+
}
|
|
25
|
+
/** Whether a value reads state anywhere, however deep: a `$state` or `$bindState`, in a prop value or
|
|
26
|
+
* in a condition. */
|
|
27
|
+
function readsState(value) {
|
|
28
|
+
if (Array.isArray(value)) return value.some(readsState);
|
|
29
|
+
if (!isRecord(value)) return false;
|
|
30
|
+
if ("$state" in value || "$bindState" in value) return true;
|
|
31
|
+
return Object.values(value).some(readsState);
|
|
32
|
+
}
|
|
33
|
+
/** The state path a `$state` or `$bindState` reads, as written, or `undefined` for any other value. */
|
|
34
|
+
function bindingPath(value) {
|
|
35
|
+
if (!isRecord(value)) return void 0;
|
|
36
|
+
const kind = markerKind(value);
|
|
37
|
+
const path = kind === "state" ? value.$state : kind === "bindState" ? value.$bindState : void 0;
|
|
38
|
+
return typeof path === "string" ? path : void 0;
|
|
39
|
+
}
|
|
40
|
+
/** A `$cond` value's parts, or `undefined` for any other value. */
|
|
41
|
+
function condArms(value) {
|
|
42
|
+
if (!isRecord(value) || markerKind(value) !== "cond") return void 0;
|
|
43
|
+
if (!Object.hasOwn(value, "$then") || !Object.hasOwn(value, "$else")) return;
|
|
44
|
+
return {
|
|
45
|
+
when: value.$cond,
|
|
46
|
+
ifTrue: value.$then,
|
|
47
|
+
ifFalse: value.$else
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Whether a marker sits anywhere in a value, however deep. */
|
|
51
|
+
function containsMarker(value) {
|
|
52
|
+
if (Array.isArray(value)) return value.some(containsMarker);
|
|
53
|
+
if (!isRecord(value)) return false;
|
|
54
|
+
if (isExpression(value) || markerKind(value) !== void 0) return true;
|
|
55
|
+
return Object.values(value).some(containsMarker);
|
|
56
|
+
}
|
|
57
|
+
const UNRESOLVED = { resolved: false };
|
|
58
|
+
/**
|
|
59
|
+
* A value settled against `state`, the whole prop state of the instance being read: its defaults,
|
|
60
|
+
* overrides and cell coordinate.
|
|
61
|
+
*
|
|
62
|
+
* A `$state` or `$bindState` reads the state, and a prop the state lacks reads `undefined`. A
|
|
63
|
+
* `$cond` settles only when its condition reads props the state holds, since a capture can key a
|
|
64
|
+
* condition on a seed no prop state carries, and reading that seed as `undefined` would pick a
|
|
65
|
+
* branch the render never paints. Only the branch taken has to settle. Any other directive, and
|
|
66
|
+
* `$template` in particular, which interpolates an unread path to `''`, stays unresolved.
|
|
67
|
+
*/
|
|
68
|
+
function resolveBinding(value, state) {
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
const out = [];
|
|
71
|
+
for (const entry of value) {
|
|
72
|
+
const inner = resolveBinding(entry, state);
|
|
73
|
+
if (!inner.resolved) return UNRESOLVED;
|
|
74
|
+
out.push(inner.value);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
resolved: true,
|
|
78
|
+
value: out
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (!isRecord(value)) return {
|
|
82
|
+
resolved: true,
|
|
83
|
+
value
|
|
84
|
+
};
|
|
85
|
+
const kind = markerKind(value);
|
|
86
|
+
const directives = Object.keys(value).filter((key) => key.startsWith("$"));
|
|
87
|
+
if (kind === void 0) {
|
|
88
|
+
const out = {};
|
|
89
|
+
for (const [key, member] of Object.entries(value)) {
|
|
90
|
+
const inner = resolveBinding(member, state);
|
|
91
|
+
if (!inner.resolved) return UNRESOLVED;
|
|
92
|
+
out[key] = inner.value;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
resolved: true,
|
|
96
|
+
value: out
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (kind === "cond") {
|
|
100
|
+
if (directives.some((key) => !COND_KEYS.has(key))) return UNRESOLVED;
|
|
101
|
+
const holds = resolveVisibility(value.$cond, state, { seededOnly: true });
|
|
102
|
+
if (holds === void 0) return UNRESOLVED;
|
|
103
|
+
return resolveBinding(holds ? value.$then : value.$else, state);
|
|
104
|
+
}
|
|
105
|
+
if (directives.length !== 1) return UNRESOLVED;
|
|
106
|
+
const pointer = kind === "state" ? value.$state : value.$bindState;
|
|
107
|
+
if (kind !== "state" && kind !== "bindState" || typeof pointer !== "string") return UNRESOLVED;
|
|
108
|
+
return {
|
|
109
|
+
resolved: true,
|
|
110
|
+
value: readStatePointer(state, pointer)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const COND_KEYS = new Set([
|
|
114
|
+
"$cond",
|
|
115
|
+
"$then",
|
|
116
|
+
"$else"
|
|
117
|
+
]);
|
|
118
|
+
/** The JSON pointer json-render reads a prop by: `/tone`. */
|
|
119
|
+
function pointerTo(prop) {
|
|
120
|
+
return prop.startsWith("/") ? prop : `/${prop}`;
|
|
121
|
+
}
|
|
122
|
+
/** A read of one prop: `{ $state: '/tone' }`. */
|
|
123
|
+
function stateRef(prop) {
|
|
124
|
+
return { $state: pointerTo(prop) };
|
|
125
|
+
}
|
|
126
|
+
/** A two-way read of one state key, for a control that writes it back: `{ $bindState: '/open' }`. */
|
|
127
|
+
function bindStateRef(key) {
|
|
128
|
+
return { $bindState: pointerTo(key) };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* A test of one prop, in the grammar `visible` and `$cond` share: truthy, or equal to a value, and
|
|
132
|
+
* negated with `not`. Naming `eq` makes it a comparison even when its value is `undefined`.
|
|
133
|
+
*/
|
|
134
|
+
function stateTest(test) {
|
|
135
|
+
return {
|
|
136
|
+
...stateRef(test.prop),
|
|
137
|
+
...Object.hasOwn(test, "eq") ? { eq: test.eq } : {},
|
|
138
|
+
...test.not ? { not: true } : {}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** A value chosen by a condition: `{ $cond, $then, $else }`. */
|
|
142
|
+
function condValue({ when, ifTrue, ifFalse }) {
|
|
143
|
+
return {
|
|
144
|
+
$cond: when,
|
|
145
|
+
$then: ifTrue,
|
|
146
|
+
$else: ifFalse
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Every prop of `layer` a component prop drives, keyed by the layer prop. A declared forward wins
|
|
151
|
+
* where a render binding names the same layer prop.
|
|
152
|
+
*/
|
|
153
|
+
function layerPropDrivers({ config, path, layer }) {
|
|
154
|
+
const out = {};
|
|
155
|
+
for (const route of [...slotTargetsOf(config, path), ...valueForwardsOf(config, path)]) {
|
|
156
|
+
if (route.layer !== layer || route.into in out) continue;
|
|
157
|
+
out[route.into] = {
|
|
158
|
+
prop: route.from,
|
|
159
|
+
kind: "forward"
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
for (const route of renderBindingsOf(config, path)) {
|
|
163
|
+
if (route.layer !== layer || route.into in out) continue;
|
|
164
|
+
out[route.into] = {
|
|
165
|
+
prop: route.from,
|
|
166
|
+
kind: "render",
|
|
167
|
+
...route.node !== void 0 ? { node: route.node } : {}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
export { bindStateRef, bindingPath, condArms, condValue, containsMarker, layerPropDrivers, markerKind, readsState, resolveBinding, stateRef, stateTest };
|
|
@@ -191,5 +191,27 @@ declare function forwardClaims(config: Config, componentPath: string): ForwardCl
|
|
|
191
191
|
* surface editing the layer's values has nothing to write for it.
|
|
192
192
|
*/
|
|
193
193
|
declare function layerPropRoutes(config: Config, componentPath: string, layer: string): ReadonlyMap<string, string>;
|
|
194
|
+
/** How a component prop reaches a layer prop: a `forward` the config declares, or a read the render
|
|
195
|
+
* records (`<Pill color={color} />`). */
|
|
196
|
+
type BindingKind = 'forward' | 'render';
|
|
197
|
+
/** A component prop delivered to one prop of one layer. */
|
|
198
|
+
interface PropRoute {
|
|
199
|
+
readonly layer: string;
|
|
200
|
+
/** The component prop the value arrives as. */
|
|
201
|
+
readonly from: string;
|
|
202
|
+
/** The layer prop it lands on. */
|
|
203
|
+
readonly into: string;
|
|
204
|
+
/** The anatomy node the render binding sits on, for a route the render records; absent for a
|
|
205
|
+
* declared forward. */
|
|
206
|
+
readonly node?: string;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The layer props a render binds to a component prop by name, as routes. `<ChipButton color={color} />`
|
|
210
|
+
* in a render is recorded on the layer's node as `color: { $state: '/color' }`, the same relationship a
|
|
211
|
+
* `forward` declares, so a surface routing a layer prop back to the instance prop that delivers it
|
|
212
|
+
* reads both. Only a bare, single-segment `$state` naming a declared prop counts: a nested pointer or
|
|
213
|
+
* a conditional is a value a surface shows, not a prop it edits.
|
|
214
|
+
*/
|
|
215
|
+
declare function renderBindingsOf(config: Config, componentPath: string): PropRoute[];
|
|
194
216
|
//#endregion
|
|
195
|
-
export { CodeSlot, ForwardClaims, SurfaceCompositeProp, SurfaceProp, SurfacePropCondition, SurfacePropKind, SurfaceScalarProp, SurfaceSlotProp, SurfaceStylePropertyProp, SurfaceVariantProp, codeSlotsOf, forwardClaims, forwardedLayers, intoName, isValueSlot, layerPropRoutes, propApplies, propRegistryRef, routedProp, routedPropIn, routesContent, slotCodeForms, standsInForCode, surfaceProp, surfaceProps };
|
|
217
|
+
export { BindingKind, CodeSlot, ForwardClaims, PropRoute, SurfaceCompositeProp, SurfaceProp, SurfacePropCondition, SurfacePropKind, SurfaceScalarProp, SurfaceSlotProp, SurfaceStylePropertyProp, SurfaceVariantProp, codeSlotsOf, forwardClaims, forwardedLayers, intoName, isValueSlot, layerPropRoutes, propApplies, propRegistryRef, renderBindingsOf, routedProp, routedPropIn, routesContent, slotCodeForms, standsInForCode, surfaceProp, surfaceProps };
|
|
@@ -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 { propValueDomain, refListOf, resolveComponentProps, takenByForward } from "./projections.js";
|
|
4
|
-
import { slotHostLayer } from "./slot-host.js";
|
|
4
|
+
import { layerOfElement, slotHostLayer } from "./slot-host.js";
|
|
5
5
|
import { Component } from "../entities/system/Component.js";
|
|
6
6
|
//#region src/framework/prop-surface.ts
|
|
7
7
|
/**
|
|
@@ -461,7 +461,45 @@ function layerPropRoutes(config, componentPath, layer) {
|
|
|
461
461
|
}
|
|
462
462
|
const { forwarded } = computeForwardClaims(config, componentPath);
|
|
463
463
|
for (const [name, claimedBy] of forwarded) if (claimedBy === layer && !routes.has(name)) routes.set(name, name);
|
|
464
|
+
for (const route of renderBindingsOf(config, componentPath)) if (route.layer === layer && !routes.has(route.into)) routes.set(route.into, route.from);
|
|
464
465
|
return routes;
|
|
465
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* The layer props a render binds to a component prop by name, as routes. `<ChipButton color={color} />`
|
|
469
|
+
* in a render is recorded on the layer's node as `color: { $state: '/color' }`, the same relationship a
|
|
470
|
+
* `forward` declares, so a surface routing a layer prop back to the instance prop that delivers it
|
|
471
|
+
* reads both. Only a bare, single-segment `$state` naming a declared prop counts: a nested pointer or
|
|
472
|
+
* a conditional is a value a surface shows, not a prop it edits.
|
|
473
|
+
*/
|
|
474
|
+
function renderBindingsOf(config, componentPath) {
|
|
475
|
+
const body = Component.get(config, componentPath)?.toJSON();
|
|
476
|
+
if (!body?.anatomy) return [];
|
|
477
|
+
const declared = resolveComponentProps(config, componentPath);
|
|
478
|
+
const routes = [];
|
|
479
|
+
for (const [key, node] of Object.entries(body.anatomy)) {
|
|
480
|
+
const layer = layerOfElement(node.element);
|
|
481
|
+
if (layer === void 0 || !node.props) continue;
|
|
482
|
+
for (const [into, value] of Object.entries(node.props)) {
|
|
483
|
+
const from = renderBoundProp(value);
|
|
484
|
+
if (from !== void 0 && from in declared) routes.push({
|
|
485
|
+
layer,
|
|
486
|
+
from,
|
|
487
|
+
into,
|
|
488
|
+
node: key
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return routes;
|
|
493
|
+
}
|
|
494
|
+
/** The prop a bare `{ $state: '/name' }` reads, or `undefined` for any other value. */
|
|
495
|
+
function renderBoundProp(value) {
|
|
496
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
497
|
+
const keys = Object.keys(value);
|
|
498
|
+
if (keys.length !== 1 || keys[0] !== "$state") return void 0;
|
|
499
|
+
const pointer = value.$state;
|
|
500
|
+
if (typeof pointer !== "string" || !pointer.startsWith("/")) return void 0;
|
|
501
|
+
const name = pointer.slice(1);
|
|
502
|
+
return name.length > 0 && !name.includes("/") ? name : void 0;
|
|
503
|
+
}
|
|
466
504
|
//#endregion
|
|
467
|
-
export { carriesContent, codeSlotsOf, forwardClaims, forwardedLayers, intoName, isValueSlot, layerPropRoutes, propApplies, propRegistryRef, routedProp, routedPropIn, routesContent, routesIntoText, slotCodeForms, standsInForCode, surfaceProp, surfaceProps };
|
|
505
|
+
export { carriesContent, codeSlotsOf, forwardClaims, forwardedLayers, intoName, isValueSlot, layerPropRoutes, propApplies, propRegistryRef, renderBindingsOf, renderBoundProp, routedProp, routedPropIn, routesContent, routesIntoText, slotCodeForms, standsInForCode, surfaceProp, surfaceProps };
|
|
@@ -67,6 +67,10 @@ interface ResolveContext {
|
|
|
67
67
|
* left for the renderer (and where fill routing reads it); on inside a `$computed`'s arguments
|
|
68
68
|
* and a repeat's source, which have to be values before the call. */
|
|
69
69
|
readonly settle?: boolean;
|
|
70
|
+
/** Read a `$state` only when the bag holds its prop. A condition a capture keyed on a seed no prop
|
|
71
|
+
* state carries would otherwise read that seed as `undefined` and pick a branch the render never
|
|
72
|
+
* takes. */
|
|
73
|
+
readonly seededOnly?: boolean;
|
|
70
74
|
}
|
|
71
75
|
/**
|
|
72
76
|
* The registry key of an element rendered THROUGH a package factory — `motion.create(Box)`, spelled
|
|
@@ -6,7 +6,7 @@ import { authoredBag, authoredValue, declaredPropDefaults, memberFromLeaf, refLi
|
|
|
6
6
|
import { componentLayerClass, componentPropClasses, componentRuleClasses, layerRoutedProps, splitStyleProps, styleRuleMotionClasses } from "./class-names.js";
|
|
7
7
|
import { graftPreviewElements } from "../spec/preview-elements.js";
|
|
8
8
|
import { slotHostLayer } from "./slot-host.js";
|
|
9
|
-
import { forwardedLayers, intoName, isValueSlot, routesContent } from "./prop-surface.js";
|
|
9
|
+
import { forwardedLayers, intoName, isValueSlot, renderBindingsOf, routesContent } from "./prop-surface.js";
|
|
10
10
|
//#region src/framework/render-spec.ts
|
|
11
11
|
const refOf = (value) => typeof value?.__ref === "string" ? value.__ref : void 0;
|
|
12
12
|
const layerOf = (element) => {
|
|
@@ -578,7 +578,10 @@ const UNREADABLE = Symbol("unreadable");
|
|
|
578
578
|
* `$index` read the row of a repeated node; outside one they read nothing.
|
|
579
579
|
*/
|
|
580
580
|
function readBinding(test, state, context) {
|
|
581
|
-
if (typeof test.$state === "string")
|
|
581
|
+
if (typeof test.$state === "string") {
|
|
582
|
+
if (context.seededOnly && !Object.hasOwn(state, statePointerProp(test.$state))) return UNREADABLE;
|
|
583
|
+
return readStatePointer(state, test.$state);
|
|
584
|
+
}
|
|
582
585
|
if (typeof test.$item === "string") {
|
|
583
586
|
if (!context.row) return UNREADABLE;
|
|
584
587
|
if (test.$item === "") return context.row.item;
|
|
@@ -989,17 +992,26 @@ function valueForwardsOf(config, path) {
|
|
|
989
992
|
* instance prop's preview value, not a style.
|
|
990
993
|
*/
|
|
991
994
|
function propInto({ config, path, layer, into }) {
|
|
992
|
-
return [
|
|
995
|
+
return [
|
|
996
|
+
...slotTargetsOf(config, path),
|
|
997
|
+
...valueForwardsOf(config, path),
|
|
998
|
+
...renderBindingsOf(config, path)
|
|
999
|
+
].find((route) => route.layer === layer && route.into === into)?.from;
|
|
993
1000
|
}
|
|
994
1001
|
/** The route a prop's content or value takes into a layer, or `undefined` for a prop that reaches
|
|
995
1002
|
* no layer. */
|
|
996
1003
|
function routeOf({ config, path, prop }) {
|
|
997
|
-
return [
|
|
1004
|
+
return [
|
|
1005
|
+
...slotTargetsOf(config, path),
|
|
1006
|
+
...valueForwardsOf(config, path),
|
|
1007
|
+
...renderBindingsOf(config, path)
|
|
1008
|
+
].find((route) => route.from === prop);
|
|
998
1009
|
}
|
|
999
1010
|
function propsIntoLayer({ config, path, layer }) {
|
|
1000
1011
|
const names = /* @__PURE__ */ new Set();
|
|
1001
1012
|
for (const route of slotTargetsOf(config, path)) if (route.layer === layer) names.add(route.from);
|
|
1002
1013
|
for (const route of valueForwardsOf(config, path)) if (route.layer === layer) names.add(route.from);
|
|
1014
|
+
for (const route of renderBindingsOf(config, path)) if (route.layer === layer) names.add(route.from);
|
|
1003
1015
|
for (const name of forwardedLayers(config, path).get(layer) ?? []) names.add(name);
|
|
1004
1016
|
return [...names];
|
|
1005
1017
|
}
|
|
@@ -1413,4 +1425,4 @@ function expandComponent(config, path, props = {}, options = {}) {
|
|
|
1413
1425
|
return expandSpec(config, instanceSpec(path, props), options);
|
|
1414
1426
|
}
|
|
1415
1427
|
//#endregion
|
|
1416
|
-
export { ANATOMY_MARKER, anatomyElementComponent, anatomyTextProp, andListOf, boundElementType, componentSpec, deriveSpec, entryKeyOf, expandComponent, expandSpec, instanceSpec, layerTextProp, liveStateProps, previewSpec, propInto, propsIntoLayer, readStatePointer, resolveValue, resolveVisibility, routeOf, slotAnatomyPropOf, slotTargetsOf, specWithResolvedVisibility, spelled, statePointerProp, valueForwardsOf, visibilityStateProps, visibilityTerms, withoutTruthyTerm, wrappedElementKey };
|
|
1428
|
+
export { ANATOMY_MARKER, anatomyElementComponent, anatomyTextProp, andListOf, boundElementType, componentSpec, deriveSpec, entryKeyOf, expandComponent, expandSpec, instanceSpec, isExpression, layerTextProp, liveStateProps, previewSpec, propInto, propsIntoLayer, readStatePointer, resolveValue, resolveVisibility, routeOf, slotAnatomyPropOf, slotTargetsOf, specWithResolvedVisibility, spelled, statePointerProp, valueForwardsOf, visibilityStateProps, visibilityTerms, withoutTruthyTerm, wrappedElementKey };
|
|
@@ -5,6 +5,8 @@ interface AnatomyNode {
|
|
|
5
5
|
readonly children?: readonly string[];
|
|
6
6
|
readonly slots?: Record<string, string[]>;
|
|
7
7
|
}
|
|
8
|
+
/** The layer a node's element renders as: a `layer:<name>` ref, or the layer a prop-chosen element stands in for. */
|
|
9
|
+
export declare function layerOfElement(element: unknown): string | undefined;
|
|
8
10
|
/**
|
|
9
11
|
* The layer holding a slot prop's content-position node: the nearest layer above the `Slot` node
|
|
10
12
|
* named for the prop, itself included. `undefined` when the anatomy records no position for the
|