@yahoo/uds-create-config 4.52.0 → 4.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capture/node-ops.js +1 -11
- package/dist/configs/CanvasConfig.js +5 -1
- package/dist/configs/system.js +4 -0
- package/dist/entities/canvas/node-gestures.d.ts +2 -0
- package/dist/entities/canvas/node-gestures.js +616 -0
- package/dist/{capture → entities/canvas}/node-patches.d.ts +6 -4
- package/dist/{capture → entities/canvas}/node-patches.js +16 -3
- package/dist/entities/system/Component.d.ts +35 -1
- package/dist/entities/system/Component.js +169 -18
- package/dist/entities/system/Motion.d.ts +5 -1
- package/dist/entities/system/Motion.js +7 -2
- package/dist/entities/system/component-forwards.d.ts +46 -0
- package/dist/entities/system/component-forwards.js +131 -0
- package/dist/entities/system/token-values.d.ts +9 -1
- package/dist/entities/system/token-values.js +4 -3
- package/dist/framework/Config.d.ts +1 -1
- package/dist/framework/Config.js +1 -1
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/framework/schemas.js +14 -3
- package/dist/index.d.ts +6 -5
- package/dist/index.js +4 -4
- package/dist/migrations/20261001130958_motion_exit_and_element_duplicate.d.ts +9 -0
- package/dist/migrations/20261001130958_motion_exit_and_element_duplicate.js +10 -0
- package/dist/migrations/20261001131000_layer_style_and_forward_ops.d.ts +11 -0
- package/dist/migrations/20261001131000_layer_style_and_forward_ops.js +10 -0
- package/dist/spec/slot.d.ts +19 -1
- package/dist/spec/slot.js +49 -11
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { Config, Patch } from "
|
|
2
|
-
import { Spec } from "
|
|
1
|
+
import { Config, Patch } from "../../framework/Config.js";
|
|
2
|
+
import { Spec } from "../../capture/capture-to-spec.js";
|
|
3
3
|
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/entities/canvas/node-patches.d.ts
|
|
5
|
+
/** The key a placed node's own element takes when its rows read back as a spec. */
|
|
6
|
+
declare const PLACED_NODE_KEY = "root";
|
|
5
7
|
interface Placement {
|
|
6
8
|
readonly page: string;
|
|
7
9
|
readonly x?: number;
|
|
@@ -29,4 +31,4 @@ declare function specToNodePatches({
|
|
|
29
31
|
readonly placement: Placement;
|
|
30
32
|
}): Patch[];
|
|
31
33
|
//#endregion
|
|
32
|
-
export { specToNodePatches };
|
|
34
|
+
export { PLACED_NODE_KEY, specToNodePatches };
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { rejectData } from "../../framework/rejection.js";
|
|
2
|
+
import { ref } from "../../framework/utils/refs.js";
|
|
3
|
+
//#region src/entities/canvas/node-patches.ts
|
|
3
4
|
/**
|
|
4
5
|
* A spec as the node rows of one new placed node. A placed node's content is its subtree rows, one
|
|
5
6
|
* per element, at `<node>/<element key>`, each pointing at its children by ref; the root element's
|
|
6
7
|
* content lives on the placed node itself beside its placement.
|
|
7
8
|
*/
|
|
9
|
+
/** The system this canvas draws on, with the slug node types are qualified by. */
|
|
10
|
+
function attachedSystem(config) {
|
|
11
|
+
const [source] = config.list("system-source");
|
|
12
|
+
const system = source ? config.sourceFor(source.path) : void 0;
|
|
13
|
+
if (!source || !system) return rejectData("This canvas has no system attached, so there are no components to place on it.");
|
|
14
|
+
return {
|
|
15
|
+
slug: source.path,
|
|
16
|
+
system
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** The key a placed node's own element takes when its rows read back as a spec. */
|
|
20
|
+
const PLACED_NODE_KEY = "root";
|
|
8
21
|
const INTRINSIC_TAG = /^[a-z][a-zA-Z0-9-]*$/;
|
|
9
22
|
const SPEC_PRIMITIVES = new Set(["Slot", "Fragment"]);
|
|
10
23
|
/** A spec element's `type` as the ref a node row stores, qualified with the system's slug. */
|
|
@@ -96,4 +109,4 @@ function specToNodePatches({ config, nodeId, spec, slug, placement }) {
|
|
|
96
109
|
return patches;
|
|
97
110
|
}
|
|
98
111
|
//#endregion
|
|
99
|
-
export { specToNodePatches };
|
|
112
|
+
export { PLACED_NODE_KEY, attachedSystem, elementTypeRef, postOrder, specToNodePatches };
|
|
@@ -12,9 +12,10 @@ import { TokenClass } from "./Token.js";
|
|
|
12
12
|
import { StoredPreviewCondition } from "../../framework/preview-store.js";
|
|
13
13
|
import { RepeatSpec } from "../../framework/render-spec.js";
|
|
14
14
|
import { AsInput, DefaultsOf, LayerMap, PhantomCallProps, PhantomReadProps, PropMap, RenderArgs, StyleRuleInput } from "./defineComponent.js";
|
|
15
|
-
import { listSlots, namedSlots, slotHostedBy, slotOf } from "../../spec/slot.js";
|
|
15
|
+
import { listSlots, namedSlots, slotHostedBy, slotOf, slotsFor } from "../../spec/slot.js";
|
|
16
16
|
import { cellAt } from "../../spec/cell.js";
|
|
17
17
|
import { instanceAt } from "../../spec/instance.js";
|
|
18
|
+
import { ForwardOptions, forwardOptions } from "./component-forwards.js";
|
|
18
19
|
import { Config } from "../../framework/Config.js";
|
|
19
20
|
import { z } from "zod";
|
|
20
21
|
|
|
@@ -739,6 +740,26 @@ declare const ComponentEntity: EntityClass<z.ZodObject<{
|
|
|
739
740
|
}, z.core.$strip>>;
|
|
740
741
|
}, z.core.$strip>;
|
|
741
742
|
};
|
|
743
|
+
readonly 'element-duplicate': {
|
|
744
|
+
readonly input: z.ZodObject<{
|
|
745
|
+
path: z.ZodType<string, string | undefined, z.core.$ZodTypeInternals<string, string | undefined>>;
|
|
746
|
+
key: z.ZodString;
|
|
747
|
+
data: z.ZodPrefault<z.ZodObject<{
|
|
748
|
+
count: z.ZodOptional<z.ZodNumber>;
|
|
749
|
+
}, z.core.$strip>>;
|
|
750
|
+
}, z.core.$strip>;
|
|
751
|
+
};
|
|
752
|
+
readonly 'layer-forward': {
|
|
753
|
+
readonly input: z.ZodObject<{
|
|
754
|
+
path: z.ZodType<string, string | undefined, z.core.$ZodTypeInternals<string, string | undefined>>;
|
|
755
|
+
key: z.ZodString;
|
|
756
|
+
data: z.ZodObject<{
|
|
757
|
+
props: z.ZodUnion<readonly [z.ZodLiteral<"*">, z.ZodArray<z.ZodString>]>;
|
|
758
|
+
except: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
759
|
+
replace: z.ZodOptional<z.ZodBoolean>;
|
|
760
|
+
}, z.core.$strip>;
|
|
761
|
+
}, z.core.$strip>;
|
|
762
|
+
};
|
|
742
763
|
readonly 'element-delete': {
|
|
743
764
|
readonly input: z.ZodObject<{
|
|
744
765
|
path: z.ZodType<string, string | undefined, z.core.$ZodTypeInternals<string, string | undefined>>;
|
|
@@ -768,6 +789,17 @@ declare const ComponentEntity: EntityClass<z.ZodObject<{
|
|
|
768
789
|
}, z.core.$strip>;
|
|
769
790
|
};
|
|
770
791
|
}, {
|
|
792
|
+
/**
|
|
793
|
+
* What each layer can forward, for the layers that render a component with props: every prop its
|
|
794
|
+
* element exposes, whether the layer forwards it now, and why one is blocked (the component
|
|
795
|
+
* declares it, or a sibling layer forwards it). Read before writing `forwards`, so "everything
|
|
796
|
+
* but icon" is a list the caller can spell.
|
|
797
|
+
*/
|
|
798
|
+
readonly forwardable: ({
|
|
799
|
+
config,
|
|
800
|
+
path,
|
|
801
|
+
member
|
|
802
|
+
}: ComputedInput) => Record<string, ForwardOptions>;
|
|
771
803
|
/**
|
|
772
804
|
* The prop names an instance may fill through a spec element's `slots` field — every prop that
|
|
773
805
|
* routes content: a `slot`, or a `forward` whose far end takes content. Resolved over the
|
|
@@ -874,6 +906,8 @@ interface ComponentClass extends Omit<typeof ComponentEntity, 'ref' | 'item'> {
|
|
|
874
906
|
slot: typeof slotOf;
|
|
875
907
|
slotHostedBy: typeof slotHostedBy;
|
|
876
908
|
namedSlots: typeof namedSlots;
|
|
909
|
+
slotsFor: typeof slotsFor;
|
|
910
|
+
forwardOptions: typeof forwardOptions;
|
|
877
911
|
cell: typeof cellAt;
|
|
878
912
|
instance: typeof instanceAt;
|
|
879
913
|
}
|
|
@@ -16,9 +16,10 @@ import { storedPreview } from "../../framework/preview-store.js";
|
|
|
16
16
|
import { glyphPropsOf, propOwner, propValueDomain, refListOf, resolveComponentProps, stylePropValueLeaves, validateComponentProps } from "../../framework/projections.js";
|
|
17
17
|
import { carriesContent, intoName, isValueSlot, renderBoundProp, routedPropIn, routesContent } from "../../framework/prop-surface.js";
|
|
18
18
|
import { slotAnatomyPropOf, slotTargetsOf, withoutTruthyTerm } from "../../framework/render-spec.js";
|
|
19
|
-
import { listSlots, namedSlots, slotHostedBy, slotOf } from "../../spec/slot.js";
|
|
19
|
+
import { listSlots, namedSlots, slotHostedBy, slotOf, slotsFor } from "../../spec/slot.js";
|
|
20
20
|
import { cellAt } from "../../spec/cell.js";
|
|
21
21
|
import { instanceAt } from "../../spec/instance.js";
|
|
22
|
+
import { forwardOptions, forwardsWrite } from "./component-forwards.js";
|
|
22
23
|
import { File } from "./File.js";
|
|
23
24
|
import { forwardRouteInvariants, refusedRoutes } from "./forward-route-invariants.js";
|
|
24
25
|
import { iconDomainInvariants } from "./icon-domain-invariants.js";
|
|
@@ -275,6 +276,11 @@ const ForwardDefinition = defineSubEntity({
|
|
|
275
276
|
input: entry,
|
|
276
277
|
message: `"${name}" is not a prop of layer "${key}"'s element (${pathOf(element.__ref)}).`
|
|
277
278
|
});
|
|
279
|
+
if (name && body.props?.[name] != null) ctx.issues.push({
|
|
280
|
+
code: "custom",
|
|
281
|
+
input: entry,
|
|
282
|
+
message: `"${name}" is declared by the component itself, which wins over a forward.`
|
|
283
|
+
});
|
|
278
284
|
}
|
|
279
285
|
const mine = forwardedNames(config, element, ctx.value);
|
|
280
286
|
if (!mine) return;
|
|
@@ -1671,24 +1677,45 @@ const ComponentEntity = defineEntity({
|
|
|
1671
1677
|
anatomy: AnatomyNode,
|
|
1672
1678
|
defaultProps: Style
|
|
1673
1679
|
},
|
|
1674
|
-
computed: {
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1680
|
+
computed: {
|
|
1681
|
+
/**
|
|
1682
|
+
* What each layer can forward, for the layers that render a component with props: every prop its
|
|
1683
|
+
* element exposes, whether the layer forwards it now, and why one is blocked (the component
|
|
1684
|
+
* declares it, or a sibling layer forwards it). Read before writing `forwards`, so "everything
|
|
1685
|
+
* but icon" is a list the caller can spell.
|
|
1686
|
+
*/
|
|
1687
|
+
forwardable: ({ config, path, member }) => {
|
|
1688
|
+
const out = {};
|
|
1689
|
+
const body = member;
|
|
1690
|
+
for (const layer of Object.keys(body.layers ?? {})) {
|
|
1691
|
+
const options = forwardOptions({
|
|
1692
|
+
config,
|
|
1693
|
+
path,
|
|
1694
|
+
layer,
|
|
1695
|
+
member: body
|
|
1696
|
+
});
|
|
1697
|
+
if (options) out[layer] = options;
|
|
1698
|
+
}
|
|
1699
|
+
return out;
|
|
1700
|
+
},
|
|
1701
|
+
/**
|
|
1702
|
+
* The prop names an instance may fill through a spec element's `slots` field — every prop that
|
|
1703
|
+
* routes content: a `slot`, or a `forward` whose far end takes content. Resolved over the
|
|
1704
|
+
* inheritance chain, since an inherited slot is as fillable as a declared one. This is the
|
|
1705
|
+
* `slots: string[]` json-render's catalog metadata declares per component, computed here so the
|
|
1706
|
+
* emitted catalog, the validator, and every picker read one answer.
|
|
1707
|
+
*/
|
|
1708
|
+
slots: ({ config, path, member }) => {
|
|
1709
|
+
const layers = member.layers;
|
|
1710
|
+
const names = [];
|
|
1711
|
+
for (const [name, decl] of Object.entries(resolveComponentProps(config, path))) {
|
|
1712
|
+
const body = decl;
|
|
1713
|
+
if (!body) continue;
|
|
1714
|
+
if (carriesContent(config, layers, body)) names.push(name);
|
|
1715
|
+
}
|
|
1716
|
+
return names;
|
|
1689
1717
|
}
|
|
1690
|
-
|
|
1691
|
-
} },
|
|
1718
|
+
},
|
|
1692
1719
|
nameEdges: bagNameEdges(componentBags),
|
|
1693
1720
|
styleBags: componentBags,
|
|
1694
1721
|
rewriteName: bagRewriteName(componentBags),
|
|
@@ -1956,6 +1983,126 @@ slots: ({ config, path, member }) => {
|
|
|
1956
1983
|
}
|
|
1957
1984
|
},
|
|
1958
1985
|
/**
|
|
1986
|
+
* Copy an element of a composition, with everything beneath it, right after it: each copy gets
|
|
1987
|
+
* layers of its own, and every style rule that styles a copied layer styles its copy the same.
|
|
1988
|
+
* One op for the reason `element-insert` is one: the layers, the nodes, the parent's edge and the
|
|
1989
|
+
* rule bags are one gesture, and any of them alone is a copy that renders wrong.
|
|
1990
|
+
*/
|
|
1991
|
+
"element-duplicate": {
|
|
1992
|
+
input: z.object({
|
|
1993
|
+
path: pathInput("component"),
|
|
1994
|
+
key: z.string(),
|
|
1995
|
+
data: z.object({
|
|
1996
|
+
/** How many copies, each after the last. */
|
|
1997
|
+
count: z.number().int().min(1).max(50).optional() }).prefault({})
|
|
1998
|
+
}),
|
|
1999
|
+
readOnly: false,
|
|
2000
|
+
label: "Duplicate element",
|
|
2001
|
+
description: "Copy an element of a component, with its subtree and its styles, right after it: each copy gets its own layers, and every style rule styling a copied layer styles the copy the same.",
|
|
2002
|
+
scope: "item",
|
|
2003
|
+
creates: true,
|
|
2004
|
+
title: (entity) => `Duplicate ${entity} element`,
|
|
2005
|
+
handler: (input, config) => {
|
|
2006
|
+
const path = String(input.path);
|
|
2007
|
+
const source = String(input.key);
|
|
2008
|
+
const count = input.data?.count ?? 1;
|
|
2009
|
+
const body = config.resolve("component", path)?.toJSON();
|
|
2010
|
+
const anatomy = body?.anatomy ?? {};
|
|
2011
|
+
if (!anatomy[source]) return rejectData(`"${source}" is not an element of ${path}.`, ["key"]);
|
|
2012
|
+
const parentEntry = Object.entries(anatomy).find(([, node]) => [...node.children ?? [], ...Object.values(node.slots ?? {}).flat()].includes(source));
|
|
2013
|
+
if (!parentEntry) return rejectData(`"${source}" is the root of ${path}; it has no place to copy into.`, ["key"]);
|
|
2014
|
+
const [parent, parentNode] = parentEntry;
|
|
2015
|
+
const slotProp = Object.entries(parentNode.slots ?? {}).find(([, fills]) => fills.includes(source))?.[0];
|
|
2016
|
+
const subtree = [];
|
|
2017
|
+
const visit = (at) => {
|
|
2018
|
+
subtree.push(at);
|
|
2019
|
+
const node = anatomy[at];
|
|
2020
|
+
for (const kid of [...node?.children ?? [], ...Object.values(node?.slots ?? {}).flat()]) visit(kid);
|
|
2021
|
+
};
|
|
2022
|
+
visit(source);
|
|
2023
|
+
const elements = Object.fromEntries(subtree.map((at) => {
|
|
2024
|
+
const node = anatomy[at] ?? {};
|
|
2025
|
+
return [at, {
|
|
2026
|
+
element: body?.layers?.[at] ?? "div",
|
|
2027
|
+
...node.props ? { props: node.props } : {},
|
|
2028
|
+
...node.children?.length ? { children: node.children } : {},
|
|
2029
|
+
...node.slots ? { slots: node.slots } : {},
|
|
2030
|
+
...node.visible !== void 0 ? { visible: node.visible } : {},
|
|
2031
|
+
...node.text !== void 0 ? { text: node.text } : {}
|
|
2032
|
+
}];
|
|
2033
|
+
}));
|
|
2034
|
+
let next = config;
|
|
2035
|
+
let previous = source;
|
|
2036
|
+
for (let n = 0; n < count; n++) {
|
|
2037
|
+
const key = freeLayerKey(next, path, source);
|
|
2038
|
+
const siblings = (next.resolve("component", path)?.toJSON())?.anatomy?.[parent]?.children ?? [];
|
|
2039
|
+
const index = siblings.indexOf(previous) + 1;
|
|
2040
|
+
next = next.run([{
|
|
2041
|
+
kind: "component",
|
|
2042
|
+
operation: "element-insert",
|
|
2043
|
+
path,
|
|
2044
|
+
key,
|
|
2045
|
+
data: {
|
|
2046
|
+
parent,
|
|
2047
|
+
...slotProp ? { slotProp } : { index: index > 0 ? index : siblings.length },
|
|
2048
|
+
subtree: {
|
|
2049
|
+
root: source,
|
|
2050
|
+
elements
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
}]);
|
|
2054
|
+
const writes = {};
|
|
2055
|
+
for (const [rule, entry] of Object.entries(body?.styles ?? {})) for (const at of subtree) {
|
|
2056
|
+
const bag = entry.layers?.[at];
|
|
2057
|
+
if (bag === void 0) continue;
|
|
2058
|
+
const copy = at === source ? key : graftKey(key, at);
|
|
2059
|
+
writes[`styles.${rule}.layers.${copy}`] = bag;
|
|
2060
|
+
}
|
|
2061
|
+
if (Object.keys(writes).length > 0) next = next.apply({
|
|
2062
|
+
kind: "component",
|
|
2063
|
+
operation: "update",
|
|
2064
|
+
path,
|
|
2065
|
+
data: writes
|
|
2066
|
+
});
|
|
2067
|
+
previous = key;
|
|
2068
|
+
}
|
|
2069
|
+
return next;
|
|
2070
|
+
}
|
|
2071
|
+
},
|
|
2072
|
+
/**
|
|
2073
|
+
* Make a layer forward the props a request names. The stored write is the generic `forwards`
|
|
2074
|
+
* `sub-upsert` or `sub-delete`; this op works out which, from what the layer forwards now.
|
|
2075
|
+
*/
|
|
2076
|
+
"layer-forward": {
|
|
2077
|
+
input: z.object({
|
|
2078
|
+
path: pathInput("component"),
|
|
2079
|
+
/** The layer whose element's props are forwarded. */
|
|
2080
|
+
key: z.string(),
|
|
2081
|
+
data: z.object({
|
|
2082
|
+
/** Every prop the layer can forward, or the ones to add to what it forwards now. */
|
|
2083
|
+
props: z.union([z.literal("*"), z.array(z.string().min(1))]),
|
|
2084
|
+
/** Props to leave out of either. */
|
|
2085
|
+
except: z.array(z.string().min(1)).optional(),
|
|
2086
|
+
/** Forward exactly `props`, dropping what the layer forwards now. */
|
|
2087
|
+
replace: z.boolean().optional()
|
|
2088
|
+
})
|
|
2089
|
+
}),
|
|
2090
|
+
readOnly: false,
|
|
2091
|
+
label: "Forward layer props",
|
|
2092
|
+
description: "Expose a layer's props at the component's top level (\"forward everything from the root but icon\", \"also forward width\"): `props` is `\"*\"` for every prop the layer's element takes, or a list added to what the layer forwards now, or that replaces it with `replace`; `except` leaves props out of either. A prop the element lacks, one the component declares itself, and one a sibling layer already forwards are refused. Read `component/get` for what a layer forwards now.",
|
|
2093
|
+
scope: "item",
|
|
2094
|
+
title: (entity) => `Forward ${entity} layer props`,
|
|
2095
|
+
handler: (input, config) => {
|
|
2096
|
+
const patch = forwardsWrite({
|
|
2097
|
+
config,
|
|
2098
|
+
path: String(input.path),
|
|
2099
|
+
layer: String(input.key),
|
|
2100
|
+
request: input.data
|
|
2101
|
+
});
|
|
2102
|
+
return patch ? config.apply(patch) : config;
|
|
2103
|
+
}
|
|
2104
|
+
},
|
|
2105
|
+
/**
|
|
1959
2106
|
* Remove an element from a composition — the node, its layer, and everything that named it.
|
|
1960
2107
|
*
|
|
1961
2108
|
* A custom op for the same reason `element-insert` is: it writes more than one thing and either
|
|
@@ -2271,6 +2418,10 @@ const Component = Object.assign(ComponentEntity, {
|
|
|
2271
2418
|
slotHostedBy,
|
|
2272
2419
|
/** The named node slots alone, without a positioned `children`. */
|
|
2273
2420
|
namedSlots,
|
|
2421
|
+
/** Every component slot whose `accepts` names an element. */
|
|
2422
|
+
slotsFor,
|
|
2423
|
+
/** What one layer can forward, and why a prop is blocked. */
|
|
2424
|
+
forwardOptions,
|
|
2274
2425
|
/** The component under one coordinate of its matrix. */
|
|
2275
2426
|
cell: cellAt,
|
|
2276
2427
|
/** The component a design file's spec element places, with what the file put in its slots. */
|
|
@@ -3,6 +3,9 @@ import { Named } from "../../framework/named.js";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
//#region src/entities/system/Motion.d.ts
|
|
6
|
+
/** The fields of a JS preset that `motion.create` reads as props, in schema order. The emitter
|
|
7
|
+
* reads this rather than restating the schema, so a field added here reaches every emitted preset. */
|
|
8
|
+
declare const jsMotionPropFields: readonly string[];
|
|
6
9
|
declare const MotionDefinition: EntityClass<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
7
10
|
runtime: z.ZodLiteral<"css">;
|
|
8
11
|
keyframes: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -16,6 +19,7 @@ declare const MotionDefinition: EntityClass<z.ZodDiscriminatedUnion<[z.ZodObject
|
|
|
16
19
|
runtime: z.ZodLiteral<"js">;
|
|
17
20
|
initial: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
18
21
|
animate: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
22
|
+
exit: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
19
23
|
transition: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
20
24
|
whileHover: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
21
25
|
whileTap: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -28,4 +32,4 @@ declare const MotionDefinition: EntityClass<z.ZodDiscriminatedUnion<[z.ZodObject
|
|
|
28
32
|
interface MotionClass extends Named<typeof MotionDefinition> {}
|
|
29
33
|
declare const Motion: MotionClass;
|
|
30
34
|
//#endregion
|
|
31
|
-
export { Motion, MotionClass };
|
|
35
|
+
export { Motion, MotionClass, jsMotionPropFields };
|
|
@@ -6,7 +6,8 @@ import { z } from "zod";
|
|
|
6
6
|
* coupled to how it animates, not loosely optional:
|
|
7
7
|
* - `css` → `keyframes` REQUIRED (codegen emits `@keyframes` from them); a css preset with
|
|
8
8
|
* no keyframes — which used to validate and silently emit nothing — is now rejected.
|
|
9
|
-
* - `js` → no keyframes; carries the interaction state the client drives
|
|
9
|
+
* - `js` → no keyframes; carries the interaction state the client drives, `exit` included,
|
|
10
|
+
* which runs under an `AnimatePresence` ancestor when the element unmounts.
|
|
10
11
|
* Illegal cross-runtime mixes (a css preset with `animate`, a js preset with `keyframes`)
|
|
11
12
|
* can't be represented — each member is strict.
|
|
12
13
|
*
|
|
@@ -30,6 +31,7 @@ const JsMotion = z.strictObject({
|
|
|
30
31
|
runtime: z.literal("js").describe(RUNTIME),
|
|
31
32
|
initial: gesture.describe("The starting values, as `motion/react` reads them."),
|
|
32
33
|
animate: gesture.describe("The values to animate to."),
|
|
34
|
+
exit: gesture.describe("The values to animate to as the element leaves."),
|
|
33
35
|
transition: gesture.describe("How the animation moves, such as a spring."),
|
|
34
36
|
whileHover: gesture.describe("Values held while the element is hovered."),
|
|
35
37
|
whileTap: gesture.describe("Values held while the element is pressed."),
|
|
@@ -39,6 +41,9 @@ const JsMotion = z.strictObject({
|
|
|
39
41
|
dragConstraints: z.record(z.string(), z.number()).optional().describe("The box a drag stays within."),
|
|
40
42
|
dragElastic: z.number().optional().describe("How far past its constraints a drag can stretch.")
|
|
41
43
|
});
|
|
44
|
+
/** The fields of a JS preset that `motion.create` reads as props, in schema order. The emitter
|
|
45
|
+
* reads this rather than restating the schema, so a field added here reaches every emitted preset. */
|
|
46
|
+
const jsMotionPropFields = Object.keys(JsMotion.shape).filter((field) => field !== "runtime");
|
|
42
47
|
const Motion = defineEntity({
|
|
43
48
|
kind: "motion",
|
|
44
49
|
label: "Motion",
|
|
@@ -46,4 +51,4 @@ const Motion = defineEntity({
|
|
|
46
51
|
fields: z.discriminatedUnion("runtime", [CssMotion, JsMotion])
|
|
47
52
|
});
|
|
48
53
|
//#endregion
|
|
49
|
-
export { Motion };
|
|
54
|
+
export { Motion, jsMotionPropFields };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ComponentBody } from "./Component.js";
|
|
2
|
+
import { Config } from "../../framework/Config.js";
|
|
3
|
+
|
|
4
|
+
//#region src/entities/system/component-forwards.d.ts
|
|
5
|
+
/** Why a prop can't be forwarded from a layer: the component declares it itself, which always wins
|
|
6
|
+
* over a forward, or a sibling layer's entry already delivers it. */
|
|
7
|
+
type ForwardBlockedReason = {
|
|
8
|
+
readonly by: 'own';
|
|
9
|
+
} | {
|
|
10
|
+
readonly by: 'layer';
|
|
11
|
+
readonly layer: string;
|
|
12
|
+
};
|
|
13
|
+
/** One prop a layer's element exposes, as a forward from that layer sees it. */
|
|
14
|
+
interface ForwardOption {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
/** Whether the layer's `forwards` entry covers it now. */
|
|
17
|
+
readonly forwarded: boolean;
|
|
18
|
+
readonly blocked?: ForwardBlockedReason;
|
|
19
|
+
}
|
|
20
|
+
/** What one layer of a component can forward, and what it forwards now. */
|
|
21
|
+
interface ForwardOptions {
|
|
22
|
+
/** Every prop the layer's element exposes, in name order. */
|
|
23
|
+
readonly options: readonly ForwardOption[];
|
|
24
|
+
/** Whether the layer has a `forwards` entry at all. */
|
|
25
|
+
readonly forwarded: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* What `layer` of the component at `path` can forward. An option the component declares itself, or a
|
|
29
|
+
* sibling layer already delivers, is listed with the reason it's blocked rather than left out. Null
|
|
30
|
+
* when the layer renders a tag, or its element declares no props.
|
|
31
|
+
*/
|
|
32
|
+
declare function forwardOptions(args: {
|
|
33
|
+
config: Config;
|
|
34
|
+
path: string;
|
|
35
|
+
layer: string;
|
|
36
|
+
member?: ComponentBody;
|
|
37
|
+
}): ForwardOptions | null;
|
|
38
|
+
/** What a forward asks for: every prop, or a list that joins what the layer forwards now unless it
|
|
39
|
+
* replaces it, less `except`. */
|
|
40
|
+
interface ForwardRequest {
|
|
41
|
+
readonly props: '*' | readonly string[];
|
|
42
|
+
readonly except?: readonly string[];
|
|
43
|
+
readonly replace?: boolean;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { ForwardBlockedReason, ForwardOption, ForwardOptions, ForwardRequest, forwardOptions };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { reject } from "../../framework/rejection.js";
|
|
2
|
+
import { isRef, kindOf, memberRef, pathOf } from "../../framework/utils/refs.js";
|
|
3
|
+
import { forwardedPropName } from "./element.js";
|
|
4
|
+
import { propOwner, resolveComponentProps } from "../../framework/projections.js";
|
|
5
|
+
import { forwardClaims } from "../../framework/prop-surface.js";
|
|
6
|
+
//#region src/entities/system/component-forwards.ts
|
|
7
|
+
function siteOf({ config, path, layer, member }) {
|
|
8
|
+
const body = member ?? config.resolve("component", path)?.toJSON();
|
|
9
|
+
const element = body?.layers?.[layer];
|
|
10
|
+
if (!body || !isRef(element) || kindOf(element) !== "component") return null;
|
|
11
|
+
const elementPath = pathOf(element);
|
|
12
|
+
const exposes = Object.keys(resolveComponentProps(config, elementPath));
|
|
13
|
+
if (exposes.length === 0) return null;
|
|
14
|
+
const own = body.forwards?.[layer];
|
|
15
|
+
let stored;
|
|
16
|
+
if (own === "*") stored = exposes;
|
|
17
|
+
else if (Array.isArray(own)) stored = own.map(forwardedPropName).filter((name) => !!name);
|
|
18
|
+
else stored = [];
|
|
19
|
+
const claims = forwardClaims(config, path);
|
|
20
|
+
return {
|
|
21
|
+
body,
|
|
22
|
+
elementPath,
|
|
23
|
+
options: [...exposes].sort((a, b) => a.localeCompare(b)).map((name) => {
|
|
24
|
+
let blocked;
|
|
25
|
+
const claimer = claims.forwarded.get(name);
|
|
26
|
+
if (claims.own.has(name)) blocked = { by: "own" };
|
|
27
|
+
else if (claimer !== void 0 && claimer !== layer) blocked = {
|
|
28
|
+
by: "layer",
|
|
29
|
+
layer: claimer
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
name,
|
|
33
|
+
forwarded: stored.includes(name),
|
|
34
|
+
...blocked ? { blocked } : {}
|
|
35
|
+
};
|
|
36
|
+
}),
|
|
37
|
+
forwarded: own !== void 0
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* What `layer` of the component at `path` can forward. An option the component declares itself, or a
|
|
42
|
+
* sibling layer already delivers, is listed with the reason it's blocked rather than left out. Null
|
|
43
|
+
* when the layer renders a tag, or its element declares no props.
|
|
44
|
+
*/
|
|
45
|
+
function forwardOptions(args) {
|
|
46
|
+
const site = siteOf(args);
|
|
47
|
+
return site && {
|
|
48
|
+
options: site.options,
|
|
49
|
+
forwarded: site.forwarded
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The names a forward ends up exposing: `'*'` is every unblocked prop, and a list joins what the
|
|
53
|
+
* layer forwards now unless it replaces it. `except` comes off either. */
|
|
54
|
+
function selectionOf({ site, data }) {
|
|
55
|
+
const names = new Set(site.options.map((option) => option.name));
|
|
56
|
+
const asked = data.props === "*" ? [] : data.props;
|
|
57
|
+
for (const name of [...asked, ...data.except ?? []]) if (!names.has(name)) reject("NOT_FOUND", `"${name}" is not a prop of the layer's element (${[...names].join(", ")}).`);
|
|
58
|
+
for (const name of asked) {
|
|
59
|
+
const blocked = site.options.find((option) => option.name === name)?.blocked;
|
|
60
|
+
if (!blocked) continue;
|
|
61
|
+
reject("ALREADY_EXISTS", blocked.by === "own" ? `"${name}" is declared by the component itself, which wins over a forward.` : `"${name}" is already forwarded from the "${blocked.layer}" layer.`);
|
|
62
|
+
}
|
|
63
|
+
const selectable = site.options.filter((option) => !option.blocked);
|
|
64
|
+
const kept = /* @__PURE__ */ new Set();
|
|
65
|
+
if (data.props === "*") for (const option of selectable) kept.add(option.name);
|
|
66
|
+
else {
|
|
67
|
+
if (!data.replace) {
|
|
68
|
+
for (const option of selectable) if (option.forwarded) kept.add(option.name);
|
|
69
|
+
}
|
|
70
|
+
for (const name of asked) kept.add(name);
|
|
71
|
+
}
|
|
72
|
+
for (const name of data.except ?? []) kept.delete(name);
|
|
73
|
+
return selectable.map((option) => option.name).filter((name) => kept.has(name));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The `forwards` write that makes `next` the layer's forwarded set, or null when it already is. An
|
|
77
|
+
* empty set deletes the entry, the full surface is `'*'` when no sibling layer has an entry of its
|
|
78
|
+
* own, and anything else is a list of member refs.
|
|
79
|
+
*/
|
|
80
|
+
function forwardPatch({ config, path, layer, site, next }) {
|
|
81
|
+
const current = site.options.filter((option) => option.forwarded && !option.blocked).map((option) => option.name);
|
|
82
|
+
if (next.length === current.length && next.every((name) => current.includes(name))) return null;
|
|
83
|
+
const at = {
|
|
84
|
+
kind: "component",
|
|
85
|
+
path,
|
|
86
|
+
field: "forwards",
|
|
87
|
+
key: layer
|
|
88
|
+
};
|
|
89
|
+
if (next.length === 0) return site.forwarded ? {
|
|
90
|
+
...at,
|
|
91
|
+
operation: "sub-delete"
|
|
92
|
+
} : null;
|
|
93
|
+
const hasSiblings = Object.keys(site.body.forwards ?? {}).some((sibling) => sibling !== layer);
|
|
94
|
+
const unowned = site.options.filter((option) => option.blocked?.by !== "own");
|
|
95
|
+
const data = !hasSiblings && unowned.length > 0 && unowned.every((option) => next.includes(option.name)) ? "*" : next.map((name) => memberRef("component", propOwner(config, site.elementPath, name) ?? site.elementPath, "props", name));
|
|
96
|
+
return {
|
|
97
|
+
...at,
|
|
98
|
+
operation: "sub-upsert",
|
|
99
|
+
data
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The generic `forwards` write that makes `layer` forward what `request` asks for: a `sub-upsert`
|
|
104
|
+
* with the refs minted (compacted to `'*'` where no sibling entry could collide), a `sub-delete`
|
|
105
|
+
* when nothing is left, or null when the layer already forwards exactly that. Refuses a prop the
|
|
106
|
+
* layer's element lacks, one the component declares itself or a sibling layer forwards, and a layer
|
|
107
|
+
* with nothing to forward.
|
|
108
|
+
*/
|
|
109
|
+
function forwardsWrite({ config, path, layer, request }) {
|
|
110
|
+
const body = config.resolve("component", path)?.toJSON();
|
|
111
|
+
if (!body) reject("NOT_FOUND", `No component at "${path}".`);
|
|
112
|
+
if (!(layer in (body.layers ?? {}))) reject("NOT_FOUND", `"${layer}" is not one of this component's layers (${Object.keys(body.layers ?? {}).join(", ")}).`);
|
|
113
|
+
const site = siteOf({
|
|
114
|
+
config,
|
|
115
|
+
path,
|
|
116
|
+
layer
|
|
117
|
+
});
|
|
118
|
+
if (!site) reject("UNSUPPORTED", `The "${layer}" layer doesn't render a component with props, so it has nothing to forward.`);
|
|
119
|
+
return forwardPatch({
|
|
120
|
+
config,
|
|
121
|
+
path,
|
|
122
|
+
layer,
|
|
123
|
+
site,
|
|
124
|
+
next: selectionOf({
|
|
125
|
+
site,
|
|
126
|
+
data: request
|
|
127
|
+
})
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { forwardOptions, forwardsWrite };
|
|
@@ -13,6 +13,14 @@ interface TokenValueEntry {
|
|
|
13
13
|
readonly px?: number;
|
|
14
14
|
readonly ms?: number;
|
|
15
15
|
}
|
|
16
|
+
/** A resolved token value as its CSS string and the numbers a JS caller reads off it. */
|
|
17
|
+
declare function parseTokenValue({
|
|
18
|
+
value,
|
|
19
|
+
rem
|
|
20
|
+
}: {
|
|
21
|
+
value: string | number; /** The pixels one `rem` is on the page, or `undefined` when the root is fluid and fixes none. */
|
|
22
|
+
rem: number | undefined;
|
|
23
|
+
}): TokenValueEntry;
|
|
16
24
|
/** What a token resolved to, or why it did not. */
|
|
17
25
|
type TokenValueState = /** One value, the same under every mode and state. */{
|
|
18
26
|
readonly kind: 'value';
|
|
@@ -144,4 +152,4 @@ declare function spellTokenRead({
|
|
|
144
152
|
reader: TokenReader;
|
|
145
153
|
}): string;
|
|
146
154
|
//#endregion
|
|
147
|
-
export { RegisteredTokenValues, TOKEN_READERS, TokenGroupReader, TokenReader, TokenReaders, TokenValueEntry, TokenValueRow, TokenValueState, isTokenReader, rootFontSize, spellTokenRead, tokenExists, tokenValueRow, tokenValueRows };
|
|
155
|
+
export { RegisteredTokenValues, TOKEN_READERS, TokenGroupReader, TokenReader, TokenReaders, TokenValueEntry, TokenValueRow, TokenValueState, isTokenReader, parseTokenValue, rootFontSize, spellTokenRead, tokenExists, tokenValueRow, tokenValueRows };
|
|
@@ -19,7 +19,8 @@ import { cssVar, resolveTokenValue } from "../../framework/projections.js";
|
|
|
19
19
|
* whatever the viewer's mode or state says on the page, so it is only ever reachable as its `var()`.
|
|
20
20
|
*/
|
|
21
21
|
const NUMERIC = /^(-?(?:\d+(?:\.\d*)?|\.\d+))(px|rem|ms|s)?$/;
|
|
22
|
-
|
|
22
|
+
/** A resolved token value as its CSS string and the numbers a JS caller reads off it. */
|
|
23
|
+
function parseTokenValue({ value, rem }) {
|
|
23
24
|
if (typeof value === "number") return {
|
|
24
25
|
css: String(value),
|
|
25
26
|
px: value
|
|
@@ -93,7 +94,7 @@ function stateOf({ config, path, rem }) {
|
|
|
93
94
|
if (resolved === void 0) return { kind: "unresolved" };
|
|
94
95
|
return {
|
|
95
96
|
kind: "value",
|
|
96
|
-
value:
|
|
97
|
+
value: parseTokenValue({
|
|
97
98
|
value: resolved,
|
|
98
99
|
rem
|
|
99
100
|
})
|
|
@@ -158,4 +159,4 @@ function spellTokenRead({ path, reader }) {
|
|
|
158
159
|
return `Token.ref(${JSON.stringify(path)}).${reader}()`;
|
|
159
160
|
}
|
|
160
161
|
//#endregion
|
|
161
|
-
export { TOKEN_READERS, isTokenReader, rootFontSize, spellTokenRead, tokenExists, tokenValueRow, tokenValueRows };
|
|
162
|
+
export { TOKEN_READERS, isTokenReader, parseTokenValue, rootFontSize, spellTokenRead, tokenExists, tokenValueRow, tokenValueRows };
|
|
@@ -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 = 20261001131000;
|
|
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
|
@@ -5250,7 +5250,7 @@ var Config = class Config {
|
|
|
5250
5250
|
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
5251
5251
|
hydrateFrom(stored, options) {
|
|
5252
5252
|
const claimed = schemaVersionOf(stored);
|
|
5253
|
-
if (claimed >
|
|
5253
|
+
if (claimed > 20261001131e3) throw new SchemaVersionTooNew(claimed);
|
|
5254
5254
|
const wire = upgradeSerializedConfig(stored);
|
|
5255
5255
|
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\`.`);
|
|
5256
5256
|
const leftover = wire.options;
|
|
@@ -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 = 20261001131000;
|
|
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 {
|