@multiplatform.one/node-graph 7.34.4 → 7.36.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/css/graph.css +0 -2
- package/dist/cjs/diagnostics/GraphDiagnostics.cjs +1 -1
- package/dist/cjs/diagnostics/GraphDiagnostics.native.js +1 -1
- package/dist/cjs/diagnostics/GraphDiagnostics.native.js.map +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.native.js +1 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/knobs/KnobEnvelopeProvider.cjs +0 -1
- package/dist/cjs/knobs/KnobEnvelopeProvider.native.js +0 -1
- package/dist/cjs/knobs/KnobEnvelopeProvider.native.js.map +1 -1
- package/dist/cjs/knobs/envelope.cjs +1 -1
- package/dist/cjs/knobs/envelope.native.js +1 -1
- package/dist/cjs/knobs/envelope.native.js.map +1 -1
- package/dist/cjs/knobs/useKnobEnvelope.cjs +1 -2
- package/dist/cjs/knobs/useKnobEnvelope.native.js +6 -7
- package/dist/cjs/knobs/useKnobEnvelope.native.js.map +1 -1
- package/dist/cjs/nodes/GraphNode.cjs +3 -2
- package/dist/cjs/nodes/GraphNode.native.js +3 -2
- package/dist/cjs/nodes/GraphNode.native.js.map +1 -1
- package/dist/cjs/nodes/GraphPort.cjs +2 -1
- package/dist/cjs/nodes/GraphPort.native.js +2 -1
- package/dist/cjs/nodes/GraphPort.native.js.map +1 -1
- package/dist/cjs/nodes/nodeMeasure.cjs +12 -19
- package/dist/cjs/nodes/nodeMeasure.native.js +15 -19
- package/dist/cjs/nodes/nodeMeasure.native.js.map +1 -1
- package/dist/cjs/nodes/overviewNode.cjs +1 -1
- package/dist/cjs/nodes/overviewNode.native.js +1 -1
- package/dist/cjs/nodes/overviewNode.native.js.map +1 -1
- package/dist/cjs/react-flow/ConnectionPuck.cjs +1 -1
- package/dist/cjs/react-flow/ConnectionPuck.native.js +1 -1
- package/dist/cjs/react-flow/ConnectionPuck.native.js.map +1 -1
- package/dist/cjs/react-flow/NodeGraphWeb.cjs +2 -1
- package/dist/cjs/react-flow/NodeGraphWeb.native.js +2 -1
- package/dist/cjs/react-flow/NodeGraphWeb.native.js.map +1 -1
- package/dist/cjs/theme/graphVars.cjs +13 -16
- package/dist/cjs/theme/graphVars.native.js +18 -19
- package/dist/cjs/theme/graphVars.native.js.map +1 -1
- package/dist/esm/diagnostics/GraphDiagnostics.mjs +1 -1
- package/dist/esm/diagnostics/GraphDiagnostics.mjs.map +1 -1
- package/dist/esm/diagnostics/GraphDiagnostics.native.js +1 -1
- package/dist/esm/diagnostics/GraphDiagnostics.native.js.map +1 -1
- package/dist/esm/index.mjs +2 -2
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +2 -2
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/knobs/KnobEnvelopeProvider.mjs +0 -1
- package/dist/esm/knobs/KnobEnvelopeProvider.mjs.map +1 -1
- package/dist/esm/knobs/KnobEnvelopeProvider.native.js +0 -1
- package/dist/esm/knobs/KnobEnvelopeProvider.native.js.map +1 -1
- package/dist/esm/knobs/envelope.mjs +1 -1
- package/dist/esm/knobs/envelope.mjs.map +1 -1
- package/dist/esm/knobs/envelope.native.js +1 -1
- package/dist/esm/knobs/envelope.native.js.map +1 -1
- package/dist/esm/knobs/useKnobEnvelope.mjs +1 -2
- package/dist/esm/knobs/useKnobEnvelope.mjs.map +1 -1
- package/dist/esm/knobs/useKnobEnvelope.native.js +6 -7
- package/dist/esm/knobs/useKnobEnvelope.native.js.map +1 -1
- package/dist/esm/nodes/GraphNode.mjs +4 -3
- package/dist/esm/nodes/GraphNode.mjs.map +1 -1
- package/dist/esm/nodes/GraphNode.native.js +4 -3
- package/dist/esm/nodes/GraphNode.native.js.map +1 -1
- package/dist/esm/nodes/GraphPort.mjs +2 -1
- package/dist/esm/nodes/GraphPort.mjs.map +1 -1
- package/dist/esm/nodes/GraphPort.native.js +2 -1
- package/dist/esm/nodes/GraphPort.native.js.map +1 -1
- package/dist/esm/nodes/nodeMeasure.mjs +13 -20
- package/dist/esm/nodes/nodeMeasure.mjs.map +1 -1
- package/dist/esm/nodes/nodeMeasure.native.js +16 -20
- package/dist/esm/nodes/nodeMeasure.native.js.map +1 -1
- package/dist/esm/nodes/overviewNode.mjs +2 -2
- package/dist/esm/nodes/overviewNode.mjs.map +1 -1
- package/dist/esm/nodes/overviewNode.native.js +2 -2
- package/dist/esm/nodes/overviewNode.native.js.map +1 -1
- package/dist/esm/react-flow/ConnectionPuck.mjs +2 -2
- package/dist/esm/react-flow/ConnectionPuck.mjs.map +1 -1
- package/dist/esm/react-flow/ConnectionPuck.native.js +2 -2
- package/dist/esm/react-flow/ConnectionPuck.native.js.map +1 -1
- package/dist/esm/react-flow/NodeGraphWeb.mjs +2 -1
- package/dist/esm/react-flow/NodeGraphWeb.mjs.map +1 -1
- package/dist/esm/react-flow/NodeGraphWeb.native.js +2 -1
- package/dist/esm/react-flow/NodeGraphWeb.native.js.map +1 -1
- package/dist/esm/theme/graphVars.mjs +14 -17
- package/dist/esm/theme/graphVars.mjs.map +1 -1
- package/dist/esm/theme/graphVars.native.js +19 -20
- package/dist/esm/theme/graphVars.native.js.map +1 -1
- package/dist/jsx/diagnostics/GraphDiagnostics.mjs +1 -1
- package/dist/jsx/diagnostics/GraphDiagnostics.mjs.map +1 -1
- package/dist/jsx/diagnostics/GraphDiagnostics.native.js +1 -1
- package/dist/jsx/diagnostics/GraphDiagnostics.native.js.map +1 -1
- package/dist/jsx/index.js +2 -2
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +2 -2
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +1 -1
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/knobs/KnobEnvelopeProvider.mjs +0 -1
- package/dist/jsx/knobs/KnobEnvelopeProvider.mjs.map +1 -1
- package/dist/jsx/knobs/KnobEnvelopeProvider.native.js +0 -1
- package/dist/jsx/knobs/KnobEnvelopeProvider.native.js.map +1 -1
- package/dist/jsx/knobs/envelope.mjs +1 -1
- package/dist/jsx/knobs/envelope.mjs.map +1 -1
- package/dist/jsx/knobs/envelope.native.js +1 -1
- package/dist/jsx/knobs/envelope.native.js.map +1 -1
- package/dist/jsx/knobs/useKnobEnvelope.mjs +1 -2
- package/dist/jsx/knobs/useKnobEnvelope.mjs.map +1 -1
- package/dist/jsx/knobs/useKnobEnvelope.native.js +6 -7
- package/dist/jsx/knobs/useKnobEnvelope.native.js.map +1 -1
- package/dist/jsx/nodes/GraphNode.mjs +4 -3
- package/dist/jsx/nodes/GraphNode.mjs.map +1 -1
- package/dist/jsx/nodes/GraphNode.native.js +3 -2
- package/dist/jsx/nodes/GraphNode.native.js.map +1 -1
- package/dist/jsx/nodes/GraphPort.mjs +2 -1
- package/dist/jsx/nodes/GraphPort.mjs.map +1 -1
- package/dist/jsx/nodes/GraphPort.native.js +2 -1
- package/dist/jsx/nodes/GraphPort.native.js.map +1 -1
- package/dist/jsx/nodes/nodeMeasure.mjs +13 -20
- package/dist/jsx/nodes/nodeMeasure.mjs.map +1 -1
- package/dist/jsx/nodes/nodeMeasure.native.js +15 -19
- package/dist/jsx/nodes/nodeMeasure.native.js.map +1 -1
- package/dist/jsx/nodes/overviewNode.mjs +2 -2
- package/dist/jsx/nodes/overviewNode.mjs.map +1 -1
- package/dist/jsx/nodes/overviewNode.native.js +1 -1
- package/dist/jsx/nodes/overviewNode.native.js.map +1 -1
- package/dist/jsx/react-flow/ConnectionPuck.mjs +2 -2
- package/dist/jsx/react-flow/ConnectionPuck.mjs.map +1 -1
- package/dist/jsx/react-flow/ConnectionPuck.native.js +1 -1
- package/dist/jsx/react-flow/ConnectionPuck.native.js.map +1 -1
- package/dist/jsx/react-flow/NodeGraphWeb.mjs +2 -1
- package/dist/jsx/react-flow/NodeGraphWeb.mjs.map +1 -1
- package/dist/jsx/react-flow/NodeGraphWeb.native.js +2 -1
- package/dist/jsx/react-flow/NodeGraphWeb.native.js.map +1 -1
- package/dist/jsx/theme/graphVars.mjs +14 -17
- package/dist/jsx/theme/graphVars.mjs.map +1 -1
- package/dist/jsx/theme/graphVars.native.js +18 -19
- package/dist/jsx/theme/graphVars.native.js.map +1 -1
- package/package.json +7 -7
- package/src/diagnostics/GraphDiagnostics.tsx +1 -1
- package/src/dom/GraphDomLayout.tsx +1 -1
- package/src/index.ts +1 -2
- package/src/knobs/KnobEnvelopeProvider.tsx +3 -20
- package/src/knobs/envelope.spec.tsx +18 -32
- package/src/knobs/envelope.ts +8 -14
- package/src/knobs/useKnobEnvelope.ts +1 -1
- package/src/nodes/GraphNode.spec.tsx +34 -43
- package/src/nodes/GraphNode.stories.tsx +2 -8
- package/src/nodes/GraphNode.tsx +5 -3
- package/src/nodes/GraphPort.spec.tsx +4 -6
- package/src/nodes/GraphPort.stories.tsx +6 -3
- package/src/nodes/GraphPort.tsx +3 -2
- package/src/nodes/Overview.stories.tsx +2 -2
- package/src/nodes/handleGeometry.ts +1 -1
- package/src/nodes/lod.spec.tsx +3 -2
- package/src/nodes/nodeMeasure.spec.ts +52 -43
- package/src/nodes/nodeMeasure.ts +32 -51
- package/src/nodes/overviewNode.tsx +3 -3
- package/src/react-flow/ConnectionPuck.spec.tsx +4 -4
- package/src/react-flow/ConnectionPuck.tsx +2 -2
- package/src/react-flow/NodeGraphWeb.spec.tsx +2 -6
- package/src/react-flow/NodeGraphWeb.stories.tsx +3 -9
- package/src/react-flow/NodeGraphWeb.tsx +2 -2
- package/src/theme/graphVars.spec.ts +65 -33
- package/src/theme/graphVars.ts +22 -27
- package/types/dom/GraphDomLayout.d.ts +1 -1
- package/types/index.d.ts +2 -2
- package/types/index.d.ts.map +1 -1
- package/types/knobs/KnobEnvelopeProvider.d.ts +1 -1
- package/types/knobs/KnobEnvelopeProvider.d.ts.map +1 -1
- package/types/knobs/envelope.d.ts +7 -14
- package/types/knobs/envelope.d.ts.map +1 -1
- package/types/nodes/GraphNode.d.ts.map +1 -1
- package/types/nodes/GraphPort.d.ts.map +1 -1
- package/types/nodes/handleGeometry.d.ts +1 -1
- package/types/nodes/nodeMeasure.d.ts +10 -28
- package/types/nodes/nodeMeasure.d.ts.map +1 -1
- package/types/nodes/overviewNode.d.ts.map +1 -1
- package/types/theme/graphVars.d.ts +1 -10
- package/types/theme/graphVars.d.ts.map +1 -1
|
@@ -11,14 +11,20 @@
|
|
|
11
11
|
import {
|
|
12
12
|
capContainerRadius,
|
|
13
13
|
defaultKnobs,
|
|
14
|
+
getActiveScaleTokens,
|
|
15
|
+
setActiveScaleTokens,
|
|
16
|
+
generateScale,
|
|
14
17
|
type Knobs,
|
|
15
18
|
MIN_PRESS_TARGET,
|
|
19
|
+
panelPaddingPx,
|
|
16
20
|
resolveKnobs,
|
|
17
21
|
resolveRadiusForPart,
|
|
18
22
|
} from "@multiplatform.one/theme";
|
|
19
23
|
import { describe, expect, it } from "vitest";
|
|
20
24
|
import {
|
|
21
25
|
defaultNodeMeasure,
|
|
26
|
+
containerRadiusPx,
|
|
27
|
+
nodeRadiusTokenPx,
|
|
22
28
|
FINE_PORT_PITCH,
|
|
23
29
|
type GraphNodePort,
|
|
24
30
|
graphNodeMeasure,
|
|
@@ -26,13 +32,13 @@ import {
|
|
|
26
32
|
graphNodeRadius,
|
|
27
33
|
graphNodeRadiusPx,
|
|
28
34
|
graphNodeWidth,
|
|
29
|
-
|
|
35
|
+
type GraphNodeSizeKnob,
|
|
30
36
|
portSpacing,
|
|
31
37
|
verticalPortCrowd,
|
|
32
38
|
} from "./nodeMeasure";
|
|
33
39
|
|
|
34
|
-
const sizes = ["small", "medium", "large"] as const satisfies readonly
|
|
35
|
-
const
|
|
40
|
+
const sizes = ["small", "medium", "large"] as const satisfies readonly GraphNodeSizeKnob[];
|
|
41
|
+
const sizeTokens = { small: "$3", medium: "$true", large: "$5" } as const;
|
|
36
42
|
const radiusStops = [
|
|
37
43
|
"none",
|
|
38
44
|
"small",
|
|
@@ -60,7 +66,11 @@ describe("width: remapped to the per-kind table, never the control ladder", () =
|
|
|
60
66
|
|
|
61
67
|
it("never lands on a control height — the ladder a container must not ride", () => {
|
|
62
68
|
const controlHeights = new Set(
|
|
63
|
-
sizes.map(
|
|
69
|
+
sizes.map(
|
|
70
|
+
(size) =>
|
|
71
|
+
resolveKnobs(defaultKnobs, undefined, "light", { sizeToken: sizeTokens[size] }).knobProps
|
|
72
|
+
.control.height,
|
|
73
|
+
),
|
|
64
74
|
);
|
|
65
75
|
expect(controlHeights.size).toBeGreaterThan(0);
|
|
66
76
|
for (const size of sizes) {
|
|
@@ -75,63 +85,44 @@ describe("width: remapped to the per-kind table, never the control ladder", () =
|
|
|
75
85
|
});
|
|
76
86
|
|
|
77
87
|
describe("radius: CONTAINER-CAP at all five stops", () => {
|
|
78
|
-
it("
|
|
79
|
-
|
|
80
|
-
// so this equality pins our mirror to `panelPaddingPxMap` in resolveKnobs.
|
|
81
|
-
for (const space of spaces) {
|
|
82
|
-
expect(capContainerRadius("full", space)).toBe(nodePanelPaddingPx[space]);
|
|
83
|
-
}
|
|
88
|
+
it("caps at the panelPadding role, the same px capContainerRadius uses", () => {
|
|
89
|
+
expect(capContainerRadius("full")).toBe(panelPaddingPx());
|
|
84
90
|
});
|
|
85
91
|
|
|
86
|
-
it("paints exactly what capContainerRadius resolves, at every stop
|
|
92
|
+
it("paints exactly what capContainerRadius resolves, at every stop", () => {
|
|
87
93
|
for (const borderRadius of radiusStops) {
|
|
88
|
-
|
|
89
|
-
expect(graphNodeRadius({ borderRadius, space })).toBe(
|
|
90
|
-
capContainerRadius(borderRadius, space),
|
|
91
|
-
);
|
|
92
|
-
}
|
|
94
|
+
expect(graphNodeRadius({ borderRadius })).toBe(capContainerRadius(borderRadius));
|
|
93
95
|
}
|
|
94
96
|
});
|
|
95
97
|
|
|
96
|
-
it("agrees with the DG-RAD doctrine table at every stop
|
|
98
|
+
it("agrees with the DG-RAD doctrine table at every stop", () => {
|
|
97
99
|
for (const borderRadius of radiusStops) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
);
|
|
106
|
-
}
|
|
100
|
+
expect(graphNodeRadiusPx({ borderRadius })).toBe(
|
|
101
|
+
resolveRadiusForPart({
|
|
102
|
+
radiusClass: "CONTAINER-CAP",
|
|
103
|
+
stop: borderRadius,
|
|
104
|
+
paddingPx: panelPaddingPx(),
|
|
105
|
+
}).px,
|
|
106
|
+
);
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
it("never exceeds the node's own padding — the cap is what stops a child clip", () => {
|
|
111
111
|
for (const borderRadius of radiusStops) {
|
|
112
|
-
|
|
113
|
-
expect(graphNodeRadiusPx({ borderRadius, space })).toBeLessThanOrEqual(
|
|
114
|
-
nodePanelPaddingPx[space],
|
|
115
|
-
);
|
|
116
|
-
}
|
|
112
|
+
expect(graphNodeRadiusPx({ borderRadius })).toBeLessThanOrEqual(panelPaddingPx());
|
|
117
113
|
}
|
|
118
114
|
});
|
|
119
115
|
|
|
120
116
|
it("caps rather than passes through where the token would overflow the padding", () => {
|
|
121
|
-
// `full` (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
expect(typeof graphNodeRadius({ borderRadius: "large", space: "large" })).toBe("string");
|
|
127
|
-
// ...and does not fit inside `small` padding (13px), so it caps.
|
|
128
|
-
expect(graphNodeRadius({ borderRadius: "large", space: "small" })).toBe(13);
|
|
117
|
+
// `full` (44px) exceeds the 16px panelPadding role, so it caps.
|
|
118
|
+
expect(graphNodeRadius({ borderRadius: "full" })).toBe(panelPaddingPx());
|
|
119
|
+
// `medium` (14px) fits inside it and keeps its token.
|
|
120
|
+
expect(typeof graphNodeRadius({ borderRadius: "medium" })).toBe("string");
|
|
121
|
+
expect(graphNodeRadius({ borderRadius: "large" })).toBe(panelPaddingPx());
|
|
129
122
|
});
|
|
130
123
|
|
|
131
|
-
it("is 0 at `none`
|
|
132
|
-
|
|
133
|
-
expect(graphNodeRadiusPx({ borderRadius: "none", space })).toBe(0);
|
|
134
|
-
}
|
|
124
|
+
it("is 0 at `none`", () => {
|
|
125
|
+
expect(graphNodeRadiusPx({ borderRadius: "none" })).toBe(0);
|
|
135
126
|
});
|
|
136
127
|
});
|
|
137
128
|
|
|
@@ -243,3 +234,21 @@ describe("graphNodeMeasure seeds both axes before first paint", () => {
|
|
|
243
234
|
).toStrictEqual({ width: 300, minHeight: 200 });
|
|
244
235
|
});
|
|
245
236
|
});
|
|
237
|
+
|
|
238
|
+
it("container radius tokens read the active scale, including the $8 container key", () => {
|
|
239
|
+
const previous = getActiveScaleTokens();
|
|
240
|
+
try {
|
|
241
|
+
for (const control of [40, 36, 44]) {
|
|
242
|
+
const tokens = generateScale({ control, type: 15 }).tokens;
|
|
243
|
+
setActiveScaleTokens(tokens);
|
|
244
|
+
for (const key of ["0", "4", "6", "8", "12"] as const) {
|
|
245
|
+
expect(containerRadiusPx(`$${key}`)).toBe(tokens.radius[key]);
|
|
246
|
+
}
|
|
247
|
+
expect(containerRadiusPx(13)).toBe(13);
|
|
248
|
+
}
|
|
249
|
+
expect(() => containerRadiusPx("missing")).toThrow();
|
|
250
|
+
expect(nodeRadiusTokenPx.$8).toBe(20);
|
|
251
|
+
} finally {
|
|
252
|
+
setActiveScaleTokens(previous);
|
|
253
|
+
}
|
|
254
|
+
});
|
package/src/nodes/nodeMeasure.ts
CHANGED
|
@@ -12,15 +12,28 @@
|
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
14
|
capContainerRadius,
|
|
15
|
+
defaultScaleInputs,
|
|
16
|
+
generateScale,
|
|
17
|
+
getActiveScaleTokens,
|
|
15
18
|
type Knobs,
|
|
16
19
|
MIN_PRESS_TARGET,
|
|
20
|
+
panelPaddingPx,
|
|
17
21
|
resolveRadiusForPart,
|
|
22
|
+
sizeTokenStop,
|
|
18
23
|
} from "@multiplatform.one/theme";
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The row of the per-kind tables a node reads: its size token's side of the
|
|
27
|
+
* default control size (`sizeTokenStop`), never the control-height recipe.
|
|
28
|
+
*/
|
|
29
|
+
export type GraphNodeSizeKnob = "small" | "medium" | "large";
|
|
22
30
|
export type GraphNodeRadiusKnob = Knobs["borderRadius"];
|
|
23
31
|
|
|
32
|
+
/** The table row for a resolved size token. */
|
|
33
|
+
export function graphNodeSize(sizeToken: string): GraphNodeSizeKnob {
|
|
34
|
+
return sizeTokenStop(sizeToken);
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
export type GraphPortSide = "left" | "right" | "top" | "bottom";
|
|
25
38
|
|
|
26
39
|
/**
|
|
@@ -58,21 +71,6 @@ export const defaultNodeMeasure: Record<GraphNodeSizeKnob, GraphNodeMeasure> = {
|
|
|
58
71
|
large: { width: 216, minHeight: 84 },
|
|
59
72
|
};
|
|
60
73
|
|
|
61
|
-
/**
|
|
62
|
-
* Container padding in px per `space` stop.
|
|
63
|
-
*
|
|
64
|
-
* Mirrors `panelPaddingPxMap` in `resolveKnobs.ts`, which is private to that
|
|
65
|
-
* module — the resolver mirrors the same numbers for the same reason (it stays
|
|
66
|
-
* pure, so it cannot read live tokens). `graphNode.spec.ts` pins this mirror
|
|
67
|
-
* against `capContainerRadius(..., "full")`, which returns the padding px
|
|
68
|
-
* itself, so a change in the resolver fails the spec here instead of drifting.
|
|
69
|
-
*/
|
|
70
|
-
export const nodePanelPaddingPx: Record<GraphNodeSpaceKnob, number> = {
|
|
71
|
-
small: 13,
|
|
72
|
-
medium: 18,
|
|
73
|
-
large: 32,
|
|
74
|
-
};
|
|
75
|
-
|
|
76
74
|
/**
|
|
77
75
|
* Vertical pitch between two ports on the same node edge.
|
|
78
76
|
*
|
|
@@ -92,7 +90,7 @@ export function portSpacing(opts: { readonly coarsePointer: boolean }): number {
|
|
|
92
90
|
/**
|
|
93
91
|
* §11 Size, GraphNode frame: `remap` to `NodeKindSpec.measure.width`.
|
|
94
92
|
*
|
|
95
|
-
* The size
|
|
93
|
+
* The size token reaches the width only through the per-kind table. It never
|
|
96
94
|
* reaches the control-height recipe (`knobProps.control.height`), because a
|
|
97
95
|
* node is a container and a container that steps with a control ladder is a
|
|
98
96
|
* control wearing a frame.
|
|
@@ -144,41 +142,27 @@ export function graphNodeMinHeight(opts: {
|
|
|
144
142
|
*/
|
|
145
143
|
export function graphNodeRadius(knobs: {
|
|
146
144
|
readonly borderRadius: GraphNodeRadiusKnob;
|
|
147
|
-
readonly space: GraphNodeSpaceKnob;
|
|
148
145
|
}): string | number {
|
|
149
|
-
return capContainerRadius(knobs.borderRadius
|
|
146
|
+
return capContainerRadius(knobs.borderRadius);
|
|
150
147
|
}
|
|
151
148
|
|
|
152
|
-
/**
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
* stop and space, so a token that stops meaning what it says fails there.
|
|
160
|
-
*/
|
|
161
|
-
export const nodeRadiusTokenPx: Readonly<Record<string, number>> = {
|
|
162
|
-
$0: 0,
|
|
163
|
-
$2: 5,
|
|
164
|
-
$4: 9,
|
|
165
|
-
$6: 16,
|
|
166
|
-
$12: 50,
|
|
167
|
-
};
|
|
149
|
+
/** The default scale's radius tokens; rendered nodes read the active scale. */
|
|
150
|
+
export const nodeRadiusTokenPx: Readonly<Record<string, number>> = Object.fromEntries(
|
|
151
|
+
Object.entries(generateScale(defaultScaleInputs).tokens.radius).map(([key, px]) => [
|
|
152
|
+
`$${key}`,
|
|
153
|
+
px,
|
|
154
|
+
]),
|
|
155
|
+
);
|
|
168
156
|
|
|
169
|
-
/**
|
|
170
|
-
* px for whatever `capContainerRadius` resolved to. A number is already the
|
|
171
|
-
* capped px; a token goes through the mirror above.
|
|
172
|
-
*/
|
|
157
|
+
/** px for a capped number or a radius token on the active scale. */
|
|
173
158
|
export function containerRadiusPx(value: string | number): number {
|
|
174
159
|
if (typeof value === "number") return value;
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
);
|
|
160
|
+
const radius = getActiveScaleTokens().radius;
|
|
161
|
+
const key = value.slice(1);
|
|
162
|
+
if (!value.startsWith("$") || !Object.hasOwn(radius, key)) {
|
|
163
|
+
throw new Error(`containerRadiusPx: "${value}" is not a radius token on the active scale`);
|
|
180
164
|
}
|
|
181
|
-
return
|
|
165
|
+
return radius[key as keyof typeof radius];
|
|
182
166
|
}
|
|
183
167
|
|
|
184
168
|
/**
|
|
@@ -191,14 +175,11 @@ export function containerRadiusPx(value: string | number): number {
|
|
|
191
175
|
* doctrine/resolver drift is a failing test rather than a corner that quietly
|
|
192
176
|
* clips a child.
|
|
193
177
|
*/
|
|
194
|
-
export function graphNodeRadiusPx(knobs: {
|
|
195
|
-
readonly borderRadius: GraphNodeRadiusKnob;
|
|
196
|
-
readonly space: GraphNodeSpaceKnob;
|
|
197
|
-
}): number {
|
|
178
|
+
export function graphNodeRadiusPx(knobs: { readonly borderRadius: GraphNodeRadiusKnob }): number {
|
|
198
179
|
return resolveRadiusForPart({
|
|
199
180
|
radiusClass: "CONTAINER-CAP",
|
|
200
181
|
stop: knobs.borderRadius,
|
|
201
|
-
paddingPx:
|
|
182
|
+
paddingPx: panelPaddingPx(),
|
|
202
183
|
}).px;
|
|
203
184
|
}
|
|
204
185
|
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
|
|
21
21
|
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
22
22
|
import { YStack } from "tamagui";
|
|
23
|
-
import type { GraphNodeMeasure, GraphNodePort
|
|
24
|
-
import { graphNodeMinHeight, graphNodeWidth, portSpacing } from "./nodeMeasure";
|
|
23
|
+
import type { GraphNodeMeasure, GraphNodePort } from "./nodeMeasure";
|
|
24
|
+
import { graphNodeMinHeight, graphNodeWidth, portSpacing, graphNodeSize } from "./nodeMeasure";
|
|
25
25
|
|
|
26
26
|
export interface OverviewNodeProps {
|
|
27
27
|
readonly id: string;
|
|
@@ -47,7 +47,7 @@ export function OverviewNode({
|
|
|
47
47
|
// The same resolver call the detail body makes, so the fill is the fill the
|
|
48
48
|
// user was just looking at rather than a second grey invented here.
|
|
49
49
|
const { knobProps } = useResolvedKnobs({ component: "GraphNode", intent });
|
|
50
|
-
const size = knobProps.
|
|
50
|
+
const size = graphNodeSize(knobProps.sizeToken);
|
|
51
51
|
const portSpacingPx = portSpacing({ coarsePointer });
|
|
52
52
|
|
|
53
53
|
return (
|
|
@@ -132,7 +132,7 @@ function DriveConnection({ to }: { to: { nodeId: string; id: string } | null })
|
|
|
132
132
|
function mountWithConnection(to: { nodeId: string; id: string } | null) {
|
|
133
133
|
return renderWithProviders(
|
|
134
134
|
<Preset cascade={false}>
|
|
135
|
-
<Surface size="
|
|
135
|
+
<Surface size="$true">
|
|
136
136
|
<NodeGraphWeb schema={schema()} defaultDocument={doc()}>
|
|
137
137
|
<DriveConnection to={to} />
|
|
138
138
|
</NodeGraphWeb>
|
|
@@ -149,7 +149,7 @@ describe("the puck reads React Flow's own connection state", () => {
|
|
|
149
149
|
it("renders nothing while no connection is in progress", () => {
|
|
150
150
|
const { container } = renderWithProviders(
|
|
151
151
|
<Preset cascade={false}>
|
|
152
|
-
<Surface size="
|
|
152
|
+
<Surface size="$true">
|
|
153
153
|
<NodeGraphWeb schema={schema()} defaultDocument={doc()} />
|
|
154
154
|
</Surface>
|
|
155
155
|
</Preset>,
|
|
@@ -184,7 +184,7 @@ describe("the puck reads React Flow's own connection state", () => {
|
|
|
184
184
|
it("can be turned off, for a consumer that draws its own", () => {
|
|
185
185
|
const { container } = renderWithProviders(
|
|
186
186
|
<Preset cascade={false}>
|
|
187
|
-
<Surface size="
|
|
187
|
+
<Surface size="$true">
|
|
188
188
|
<NodeGraphWeb schema={schema()} defaultDocument={doc()} connectionPuck={false}>
|
|
189
189
|
<DriveConnection to={{ nodeId: "c", id: "in" }} />
|
|
190
190
|
</NodeGraphWeb>
|
|
@@ -197,7 +197,7 @@ describe("the puck reads React Flow's own connection state", () => {
|
|
|
197
197
|
it("mounted a second time renders a second div — which is why the canvas owns it", () => {
|
|
198
198
|
const { container } = renderWithProviders(
|
|
199
199
|
<Preset cascade={false}>
|
|
200
|
-
<Surface size="
|
|
200
|
+
<Surface size="$true">
|
|
201
201
|
<NodeGraphWeb schema={schema()} defaultDocument={doc()} connectionPuck={false}>
|
|
202
202
|
<DriveConnection to={{ nodeId: "c", id: "in" }} />
|
|
203
203
|
<ConnectionPuck document={doc()} schema={schema()} />
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* target the drop will then reject is worse than naming nothing.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
23
|
+
import { transitionProps, useResolvedKnobs } from "@multiplatform.one/theme";
|
|
24
24
|
import { SizableText, XStack } from "tamagui";
|
|
25
25
|
import { isLegalTarget } from "../interaction/legalTargets";
|
|
26
26
|
import type {
|
|
@@ -165,7 +165,7 @@ export function ConnectionPuck<N extends NodeKindDefs, E extends EdgeKindDefs>({
|
|
|
165
165
|
// saying something true, what you are dragging, just not the thing the
|
|
166
166
|
// user is looking for.
|
|
167
167
|
opacity={label.legal ? 1 : 0.7}
|
|
168
|
-
|
|
168
|
+
{...transitionProps(knobProps.motion.overlay)}
|
|
169
169
|
>
|
|
170
170
|
<SizableText {...knobProps.label} numberOfLines={1}>
|
|
171
171
|
{label.text}
|
|
@@ -42,9 +42,7 @@ afterEach(() => {
|
|
|
42
42
|
function mount(element: ReactElement) {
|
|
43
43
|
return renderWithProviders(
|
|
44
44
|
<Preset cascade={false}>
|
|
45
|
-
<Surface size="
|
|
46
|
-
{element}
|
|
47
|
-
</Surface>
|
|
45
|
+
<Surface size="$true">{element}</Surface>
|
|
48
46
|
</Preset>,
|
|
49
47
|
);
|
|
50
48
|
}
|
|
@@ -337,9 +335,7 @@ describe("onlyRenderVisibleElements", () => {
|
|
|
337
335
|
|
|
338
336
|
view.rerender(
|
|
339
337
|
<Preset cascade={false}>
|
|
340
|
-
<Surface size="
|
|
341
|
-
{canvas(false)}
|
|
342
|
-
</Surface>
|
|
338
|
+
<Surface size="$true">{canvas(false)}</Surface>
|
|
343
339
|
</Preset>,
|
|
344
340
|
);
|
|
345
341
|
|
|
@@ -186,24 +186,20 @@ const fuzzCombos: readonly { readonly label: string; readonly knobs: Partial<Kno
|
|
|
186
186
|
borderRadius: "medium",
|
|
187
187
|
borderWidth: "none",
|
|
188
188
|
elevation: "none",
|
|
189
|
-
space: "large",
|
|
190
|
-
size: "medium",
|
|
191
189
|
textAccent: "medium",
|
|
192
190
|
headingFont: "geometric",
|
|
193
191
|
bodyFont: "sans-serif",
|
|
194
192
|
fontWeight: "bold",
|
|
195
|
-
animation: "
|
|
193
|
+
animation: "slow",
|
|
196
194
|
},
|
|
197
195
|
},
|
|
198
196
|
{
|
|
199
|
-
label: "combo 2 · radius none ·
|
|
197
|
+
label: "combo 2 · radius none · animation none",
|
|
200
198
|
knobs: {
|
|
201
199
|
fillStyle: "filled",
|
|
202
200
|
borderRadius: "none",
|
|
203
201
|
borderWidth: "none",
|
|
204
202
|
elevation: "small",
|
|
205
|
-
space: "small",
|
|
206
|
-
size: "small",
|
|
207
203
|
textAccent: "medium",
|
|
208
204
|
headingFont: "rounded",
|
|
209
205
|
bodyFont: "cursive",
|
|
@@ -212,14 +208,12 @@ const fuzzCombos: readonly { readonly label: string; readonly knobs: Partial<Kno
|
|
|
212
208
|
},
|
|
213
209
|
},
|
|
214
210
|
{
|
|
215
|
-
label: "combo 3 · radius full ·
|
|
211
|
+
label: "combo 3 · radius full · elevation large",
|
|
216
212
|
knobs: {
|
|
217
213
|
fillStyle: "filled",
|
|
218
214
|
borderRadius: "full",
|
|
219
215
|
borderWidth: "small",
|
|
220
216
|
elevation: "large",
|
|
221
|
-
space: "medium",
|
|
222
|
-
size: "large",
|
|
223
217
|
textAccent: "high",
|
|
224
218
|
headingFont: "mono",
|
|
225
219
|
bodyFont: "serif",
|
|
@@ -67,7 +67,7 @@ import type {
|
|
|
67
67
|
} from "../model/index";
|
|
68
68
|
import { portConnectionRadius } from "../nodes/handleGeometry";
|
|
69
69
|
import { defaultOverviewZoom } from "../nodes/lod";
|
|
70
|
-
import
|
|
70
|
+
import { graphNodeSize } from "../nodes/nodeMeasure";
|
|
71
71
|
import { GraphVars, graphRootClassName } from "../theme/GraphVarsStyle";
|
|
72
72
|
import { GraphActionsProvider } from "./GraphActions";
|
|
73
73
|
import type { GraphActions } from "./GraphActions";
|
|
@@ -336,7 +336,7 @@ function NodeGraphCanvas<N extends NodeKindDefs, E extends EdgeKindDefs>(
|
|
|
336
336
|
// `useResolvedKnobs`, not from here; this is the `size` the measure seed
|
|
337
337
|
// reads and the pointer class the var layer needs.
|
|
338
338
|
const { knobProps } = useResolvedKnobs();
|
|
339
|
-
const size = knobProps.
|
|
339
|
+
const size = graphNodeSize(knobProps.sizeToken);
|
|
340
340
|
|
|
341
341
|
const envelope = useKnobEnvelope({
|
|
342
342
|
knobs,
|
|
@@ -18,12 +18,14 @@ import {
|
|
|
18
18
|
type Knobs,
|
|
19
19
|
type ResolvedKnobs,
|
|
20
20
|
defaultKnobs,
|
|
21
|
+
cssAnimationConfig,
|
|
21
22
|
isResolvableThemeValue,
|
|
22
23
|
resolveKnobs,
|
|
23
24
|
} from "@multiplatform.one/theme";
|
|
24
25
|
import {
|
|
25
26
|
type GraphThemeAccess,
|
|
26
27
|
edgeStrokeWidthPx,
|
|
28
|
+
elevationBoxShadow,
|
|
27
29
|
graphCssVars,
|
|
28
30
|
graphTransitionCss,
|
|
29
31
|
graphVarValues,
|
|
@@ -85,6 +87,7 @@ function enclosingSelector(css: string, index: number): string {
|
|
|
85
87
|
// ── A theme fixture with HEX raws, so the hex ban is not vacuous ──────────────
|
|
86
88
|
|
|
87
89
|
const fixtureRaw: Record<string, string> = {
|
|
90
|
+
shadowColor: "rgba(0,0,0,0.12)",
|
|
88
91
|
color: "hsl(0, 0%, 9.0%)",
|
|
89
92
|
color1: "#ffffff",
|
|
90
93
|
color4: "hsl(0, 0%, 94.1%)",
|
|
@@ -102,8 +105,12 @@ const fixtureTheme: GraphThemeAccess = {
|
|
|
102
105
|
raw: (key) => fixtureRaw[key],
|
|
103
106
|
};
|
|
104
107
|
|
|
105
|
-
function resolveFor(
|
|
106
|
-
|
|
108
|
+
function resolveFor(
|
|
109
|
+
knobs: Partial<Knobs>,
|
|
110
|
+
scheme: "light" | "dark" = "light",
|
|
111
|
+
sizeToken?: string,
|
|
112
|
+
): ResolvedKnobs {
|
|
113
|
+
return resolveKnobs({ ...defaultKnobs, ...knobs } as Knobs, undefined, scheme, { sizeToken });
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
const defaultResolved = resolveFor({});
|
|
@@ -164,10 +171,10 @@ describe("every mapped value resolves", () => {
|
|
|
164
171
|
const schemes = ["light", "dark"] as const;
|
|
165
172
|
const knobSweep: Partial<Knobs>[] = [
|
|
166
173
|
{},
|
|
167
|
-
{ fillStyle: "outlined", borderWidth: "none", elevation: "none"
|
|
168
|
-
{ borderWidth: "small", elevation: "medium"
|
|
169
|
-
{ borderWidth: "large", elevation: "large",
|
|
170
|
-
{ animation: "bouncy",
|
|
174
|
+
{ fillStyle: "outlined", borderWidth: "none", elevation: "none" },
|
|
175
|
+
{ borderWidth: "small", elevation: "medium" },
|
|
176
|
+
{ borderWidth: "large", elevation: "large", animation: "none" },
|
|
177
|
+
{ animation: "bouncy", textAccent: "low" },
|
|
171
178
|
];
|
|
172
179
|
|
|
173
180
|
for (const scheme of schemes) {
|
|
@@ -243,19 +250,20 @@ describe("the knob laws this layer owns", () => {
|
|
|
243
250
|
}
|
|
244
251
|
});
|
|
245
252
|
|
|
246
|
-
it("moves the painted handle with
|
|
247
|
-
const painted = (["
|
|
248
|
-
(
|
|
253
|
+
it("moves the painted handle with the size token and never paints it at 44", () => {
|
|
254
|
+
const painted = (["$3", "$true", "$5"] as const).map(
|
|
255
|
+
(sizeToken) =>
|
|
256
|
+
graphCssVars(resolveFor({}, "light", sizeToken), fixtureTheme)["--mpo-graph-handle-size"],
|
|
249
257
|
);
|
|
250
258
|
expect(painted).toEqual(["8px", "10px", "12px"]);
|
|
251
259
|
expect(painted).not.toContain("44px");
|
|
252
260
|
});
|
|
253
261
|
|
|
254
262
|
it("floors the handle HIT at 44 on coarse and at max(painted, 24) on fine", () => {
|
|
255
|
-
const coarse = graphCssVars(resolveFor({
|
|
263
|
+
const coarse = graphCssVars(resolveFor({}, "light", "$3"), fixtureTheme, {
|
|
256
264
|
pointer: "coarse",
|
|
257
265
|
});
|
|
258
|
-
const fine = graphCssVars(resolveFor({
|
|
266
|
+
const fine = graphCssVars(resolveFor({}, "light", "$3"), fixtureTheme, { pointer: "fine" });
|
|
259
267
|
expect(coarse["--mpo-graph-handle-hit"]).toBe("44px");
|
|
260
268
|
expect(fine["--mpo-graph-handle-hit"]).toBe("24px");
|
|
261
269
|
});
|
|
@@ -267,15 +275,16 @@ describe("the knob laws this layer owns", () => {
|
|
|
267
275
|
it("emits a complete CSS transition shorthand, or `none`", () => {
|
|
268
276
|
expect(
|
|
269
277
|
graphCssVars(resolveFor({ animation: "quick" }), fixtureTheme)["--mpo-graph-transition"],
|
|
270
|
-
).toBe(
|
|
278
|
+
).toBe(`opacity ${cssAnimationConfig.quick}`);
|
|
271
279
|
expect(
|
|
272
280
|
graphCssVars(resolveFor({ animation: "none" }), fixtureTheme)["--mpo-graph-transition"],
|
|
273
281
|
).toBe("none");
|
|
274
282
|
});
|
|
275
283
|
|
|
276
284
|
it("emits no `$token` — a CSS variable whose value is a token is dead", () => {
|
|
277
|
-
for (const
|
|
278
|
-
|
|
285
|
+
for (const sizeToken of [undefined, "$3", "$5"]) {
|
|
286
|
+
const emitted = graphCssVars(resolveFor({}, "light", sizeToken), fixtureTheme);
|
|
287
|
+
for (const [name, value] of Object.entries(emitted)) {
|
|
279
288
|
expect(value.includes("$"), `${name}: ${value}`).toBe(false);
|
|
280
289
|
}
|
|
281
290
|
}
|
|
@@ -294,25 +303,10 @@ describe("the mirrored token tables", () => {
|
|
|
294
303
|
}
|
|
295
304
|
});
|
|
296
305
|
|
|
297
|
-
it("
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// both of which would make an import prove something other than the claim.
|
|
302
|
-
const source = readFileSync(
|
|
303
|
-
path.resolve(packageRoot, "../theme/src/theme/animations/css.ts"),
|
|
304
|
-
"utf8",
|
|
305
|
-
);
|
|
306
|
-
const block = /export const animationConfig = \{([\s\S]*?)\n\} as const;/.exec(source);
|
|
307
|
-
expect(block, "animationConfig is no longer an object literal in css.ts").not.toBeNull();
|
|
308
|
-
const installed: Record<string, string> = {};
|
|
309
|
-
for (const entry of (block as RegExpExecArray)[1].matchAll(/(\w+):\s*"([^"]+)"/g)) {
|
|
310
|
-
installed[entry[1]] = entry[2];
|
|
311
|
-
}
|
|
312
|
-
expect(Object.keys(installed).length).toBeGreaterThan(0);
|
|
313
|
-
expect(Object.keys(graphTransitionCss).sort()).toEqual(Object.keys(installed).sort());
|
|
314
|
-
for (const [name, timing] of Object.entries(installed)) {
|
|
315
|
-
expect(graphTransitionCss[name], name).toBe(`all ${timing}`);
|
|
306
|
+
it("uses the theme CSS table for explicit guide opacity transitions", () => {
|
|
307
|
+
expect(Object.keys(graphTransitionCss)).toEqual(Object.keys(cssAnimationConfig));
|
|
308
|
+
for (const [name, timing] of Object.entries(cssAnimationConfig)) {
|
|
309
|
+
expect(graphTransitionCss[name], name).toBe(`opacity ${timing}`);
|
|
316
310
|
}
|
|
317
311
|
});
|
|
318
312
|
|
|
@@ -320,3 +314,41 @@ describe("the mirrored token tables", () => {
|
|
|
320
314
|
expect(Object.keys(edgeStrokeWidthPx).sort()).toEqual(["0", "0.5", "1", "2"].sort());
|
|
321
315
|
});
|
|
322
316
|
});
|
|
317
|
+
|
|
318
|
+
describe("size-formula shadow bridge", () => {
|
|
319
|
+
it("resolves the shadow variable into CSS and its colour list", () => {
|
|
320
|
+
const resolved = resolveKnobs({ ...defaultKnobs, elevation: "medium" });
|
|
321
|
+
expect(elevationBoxShadow(resolved, fixtureTheme)).toEqual({
|
|
322
|
+
css: "0px 8px 15px rgba(0,0,0,0.12)",
|
|
323
|
+
colors: [fixtureRaw.shadowColor],
|
|
324
|
+
});
|
|
325
|
+
const native = {
|
|
326
|
+
...resolved,
|
|
327
|
+
knobProps: {
|
|
328
|
+
...resolved.knobProps,
|
|
329
|
+
elevationChrome: { ...resolved.knobProps.elevationChrome, style: undefined },
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
expect(elevationBoxShadow(native, fixtureTheme)).toEqual(
|
|
333
|
+
elevationBoxShadow(resolved, fixtureTheme),
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("paints the controls with the elevated pair, so dark separators stay off the fill", () => {
|
|
338
|
+
for (const scheme of ["light", "dark"] as const) {
|
|
339
|
+
const resolved = resolveFor({}, scheme);
|
|
340
|
+
const vars = graphVarValues(resolved, fixtureTheme);
|
|
341
|
+
const surface = resolved.knobProps.elevatedSurface;
|
|
342
|
+
expect(vars["--xy-controls-button-background-color"]).toMatchObject({
|
|
343
|
+
color: fixtureTheme.css(String(surface.backgroundColor).slice(1)),
|
|
344
|
+
});
|
|
345
|
+
expect(vars["--xy-controls-button-border-color"]).toMatchObject({
|
|
346
|
+
color: fixtureTheme.css(String(surface.borderColor).slice(1)),
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
const dark = graphVarValues(resolveFor({}, "dark"), fixtureTheme);
|
|
350
|
+
expect(dark["--xy-controls-button-border-color"]).not.toEqual(
|
|
351
|
+
dark["--xy-controls-button-background-color"],
|
|
352
|
+
);
|
|
353
|
+
});
|
|
354
|
+
});
|