@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
|
@@ -25,8 +25,7 @@ import {
|
|
|
25
25
|
type Knobs,
|
|
26
26
|
type ResolvedKnobs,
|
|
27
27
|
Surface,
|
|
28
|
-
type
|
|
29
|
-
type SurfaceSizeIntent,
|
|
28
|
+
type SurfaceSize,
|
|
30
29
|
defaultKnobs,
|
|
31
30
|
registerPreset,
|
|
32
31
|
useResolvedKnobs,
|
|
@@ -95,34 +94,25 @@ const knobVocabulary = {
|
|
|
95
94
|
cornerSmoothing: ["round", "smooth"],
|
|
96
95
|
borderWidth: ["none", "small", "medium", "large"],
|
|
97
96
|
elevation: ["none", "small", "medium", "large"],
|
|
98
|
-
space: ["small", "medium", "large"],
|
|
99
|
-
size: ["small", "medium", "large"],
|
|
100
|
-
density: ["comfortable", "compact"],
|
|
101
97
|
textAccent: ["low", "medium", "high"],
|
|
102
98
|
headingFont: ["sans-serif", "serif", "mono", "slab", "rounded"],
|
|
103
99
|
bodyFont: ["sans-serif", "serif", "mono", "condensed"],
|
|
104
100
|
fontWeight: ["regular", "bold"],
|
|
105
101
|
pageTitleScale: ["moderate", "display"],
|
|
106
|
-
animation: ["none", "quick", "bouncy", "lazy", "
|
|
102
|
+
animation: ["none", "quick", "bouncy", "lazy", "quickest"],
|
|
107
103
|
} as const;
|
|
108
104
|
|
|
109
|
-
const surfaceSizes: readonly (
|
|
105
|
+
const surfaceSizes: readonly (Exclude<SurfaceSize, "unset"> | undefined)[] = [
|
|
110
106
|
undefined,
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
];
|
|
115
|
-
const surfaceDensities: readonly (SurfaceDensity | undefined)[] = [
|
|
116
|
-
undefined,
|
|
117
|
-
"compact",
|
|
118
|
-
"comfortable",
|
|
107
|
+
"$3",
|
|
108
|
+
"$true",
|
|
109
|
+
"$5",
|
|
119
110
|
];
|
|
120
111
|
const intents: readonly (string | null)[] = [null, "accent", "error", "warning", "success"];
|
|
121
112
|
|
|
122
113
|
interface Combo {
|
|
123
114
|
readonly knobs: Partial<Knobs>;
|
|
124
|
-
readonly surfaceSize:
|
|
125
|
-
readonly surfaceDensity: SurfaceDensity | undefined;
|
|
115
|
+
readonly surfaceSize: Exclude<SurfaceSize, "unset"> | undefined;
|
|
126
116
|
readonly intent: string | null;
|
|
127
117
|
}
|
|
128
118
|
|
|
@@ -141,7 +131,6 @@ function seededCombos(label: string, count: number): Combo[] {
|
|
|
141
131
|
combos.push({
|
|
142
132
|
knobs: knobs as Partial<Knobs>,
|
|
143
133
|
surfaceSize: pick(rand, surfaceSizes),
|
|
144
|
-
surfaceDensity: pick(rand, surfaceDensities),
|
|
145
134
|
intent: pick(rand, intents),
|
|
146
135
|
});
|
|
147
136
|
}
|
|
@@ -179,8 +168,7 @@ interface CrossOptions {
|
|
|
179
168
|
readonly presetName?: string;
|
|
180
169
|
/** Passed to `useKnobEnvelope`; omit to prove the fallback to `defaultIntents`. */
|
|
181
170
|
readonly carryPresetName?: boolean;
|
|
182
|
-
readonly surfaceSize?:
|
|
183
|
-
readonly surfaceDensity?: SurfaceDensity;
|
|
171
|
+
readonly surfaceSize?: Exclude<SurfaceSize, "unset">;
|
|
184
172
|
readonly contextIntent?: string | null;
|
|
185
173
|
/** Explicit `useResolvedKnobs({ intent })` on both sides; omit to lean on context. */
|
|
186
174
|
readonly resolveIntent?: string;
|
|
@@ -196,7 +184,7 @@ function cross(options: CrossOptions): Crossing {
|
|
|
196
184
|
const hostCapture: Capture = {};
|
|
197
185
|
const tree: ReactNode = (
|
|
198
186
|
<Preset preset={options.presetName} overrides={options.knobs}>
|
|
199
|
-
<Surface size={options.surfaceSize}
|
|
187
|
+
<Surface size={options.surfaceSize}>
|
|
200
188
|
<IntentContext.Provider value={options.contextIntent ?? null}>
|
|
201
189
|
<HostProbe
|
|
202
190
|
capture={hostCapture}
|
|
@@ -234,9 +222,8 @@ function cross(options: CrossOptions): Crossing {
|
|
|
234
222
|
describe("the knob envelope crosses the stack (MPO-191 AC-3)", () => {
|
|
235
223
|
it("resolves identically for the design's own host tree", () => {
|
|
236
224
|
const { host, rehydrated } = cross({
|
|
237
|
-
knobs: { borderRadius: "large", elevation: "medium",
|
|
238
|
-
surfaceSize: "
|
|
239
|
-
surfaceDensity: "compact",
|
|
225
|
+
knobs: { borderRadius: "large", elevation: "medium", animation: "bouncy" },
|
|
226
|
+
surfaceSize: "$3",
|
|
240
227
|
contextIntent: "error",
|
|
241
228
|
resolveIntent: "error",
|
|
242
229
|
});
|
|
@@ -251,7 +238,6 @@ describe("the knob envelope crosses the stack (MPO-191 AC-3)", () => {
|
|
|
251
238
|
const { host, rehydrated } = cross({
|
|
252
239
|
knobs: combo.knobs,
|
|
253
240
|
surfaceSize: combo.surfaceSize,
|
|
254
|
-
surfaceDensity: combo.surfaceDensity,
|
|
255
241
|
contextIntent: combo.intent,
|
|
256
242
|
resolveIntent: combo.intent ?? undefined,
|
|
257
243
|
});
|
|
@@ -262,7 +248,7 @@ describe("the knob envelope crosses the stack (MPO-191 AC-3)", () => {
|
|
|
262
248
|
it("resolves identically when the intent comes only from context", () => {
|
|
263
249
|
const { host, rehydrated } = cross({
|
|
264
250
|
knobs: { borderRadius: "full", textAccent: "low" },
|
|
265
|
-
surfaceSize: "
|
|
251
|
+
surfaceSize: "$true",
|
|
266
252
|
contextIntent: "warning",
|
|
267
253
|
});
|
|
268
254
|
expect(rehydrated).toEqual(host);
|
|
@@ -271,7 +257,7 @@ describe("the knob envelope crosses the stack (MPO-191 AC-3)", () => {
|
|
|
271
257
|
it("carries a NAMED preset's own intents across, which is what presetName is for", () => {
|
|
272
258
|
const { host, rehydrated, envelope } = cross({
|
|
273
259
|
presetName: "graphFuzz",
|
|
274
|
-
surfaceSize: "
|
|
260
|
+
surfaceSize: "$5",
|
|
275
261
|
contextIntent: "error",
|
|
276
262
|
resolveIntent: "error",
|
|
277
263
|
});
|
|
@@ -283,7 +269,7 @@ describe("the knob envelope crosses the stack (MPO-191 AC-3)", () => {
|
|
|
283
269
|
// these two would be identical and the test above would prove nothing.
|
|
284
270
|
const control = cross({
|
|
285
271
|
presetName: "graphFuzz",
|
|
286
|
-
surfaceSize: "
|
|
272
|
+
surfaceSize: "$5",
|
|
287
273
|
contextIntent: "warning",
|
|
288
274
|
resolveIntent: "warning",
|
|
289
275
|
});
|
|
@@ -295,7 +281,7 @@ describe("the knob envelope crosses the stack (MPO-191 AC-3)", () => {
|
|
|
295
281
|
const { host, rehydrated } = cross({
|
|
296
282
|
presetName: "graphFuzz",
|
|
297
283
|
carryPresetName: false,
|
|
298
|
-
surfaceSize: "
|
|
284
|
+
surfaceSize: "$5",
|
|
299
285
|
contextIntent: "error",
|
|
300
286
|
resolveIntent: "error",
|
|
301
287
|
});
|
|
@@ -306,14 +292,14 @@ describe("the knob envelope crosses the stack (MPO-191 AC-3)", () => {
|
|
|
306
292
|
});
|
|
307
293
|
|
|
308
294
|
describe("the envelope itself", () => {
|
|
309
|
-
it("keeps `unset` surface
|
|
295
|
+
it("keeps an `unset` surface size instead of coercing it", () => {
|
|
310
296
|
const capture: Capture = {};
|
|
311
297
|
const render = renderWithProviders(
|
|
312
|
-
<Preset overrides={{
|
|
298
|
+
<Preset overrides={{ borderRadius: "large" }}>
|
|
313
299
|
<HostProbe capture={capture} />
|
|
314
300
|
</Preset>,
|
|
315
301
|
);
|
|
316
|
-
expect(capture.envelope?.surface).toEqual({ size: "unset"
|
|
302
|
+
expect(capture.envelope?.surface).toEqual({ size: "unset" });
|
|
317
303
|
render.unmount();
|
|
318
304
|
});
|
|
319
305
|
|
package/src/knobs/envelope.ts
CHANGED
|
@@ -11,22 +11,16 @@
|
|
|
11
11
|
* Design: docs/design/node-graph-editor.md section 6.1.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type { Animation, Knobs,
|
|
14
|
+
import type { Animation, Knobs, SurfaceSize } from "@multiplatform.one/theme";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* `unset` is part of the surface vocabulary, and dropping it changes what
|
|
18
|
-
* resolves
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* density: "unset" }` (Surface.tsx:23-26) and `applySurfaceToKnobs`
|
|
23
|
-
* (useResolvedKnobs.ts:44-70) SKIPS an `unset` axis entirely. Coercing a host
|
|
24
|
-
* that sits under no `<Surface>` to "medium" would then step `knobs.size` down
|
|
25
|
-
* from `large`, and the rehydrated tree would resolve differently from the host
|
|
26
|
-
* it copied. So the envelope carries the third value.
|
|
18
|
+
* resolves: `SurfaceContext`'s default is `{ size: "unset" }` and
|
|
19
|
+
* `useResolvedKnobs` treats an `unset` ceiling as none. Coercing a host that
|
|
20
|
+
* sits under no `<Surface>` to a token would clamp the rehydrated tree where
|
|
21
|
+
* the host it copied was not clamped. So the envelope carries the value as is.
|
|
27
22
|
*/
|
|
28
|
-
export type GraphSurfaceSize = SurfaceSize
|
|
29
|
-
export type GraphSurfaceDensity = SurfaceDensity | "unset";
|
|
23
|
+
export type GraphSurfaceSize = SurfaceSize;
|
|
30
24
|
|
|
31
25
|
/** A face the DOM side has to register itself, because WKWebView cannot see an expo-font registration. */
|
|
32
26
|
export interface GraphFontRef {
|
|
@@ -38,7 +32,7 @@ export interface GraphFontRef {
|
|
|
38
32
|
}
|
|
39
33
|
|
|
40
34
|
/** Bumped when the envelope's shape changes in a way the DOM side must refuse. */
|
|
41
|
-
export const knobEnvelopeSchemaVersion =
|
|
35
|
+
export const knobEnvelopeSchemaVersion = 2;
|
|
42
36
|
|
|
43
37
|
export interface KnobEnvelope {
|
|
44
38
|
readonly schemaVersion: typeof knobEnvelopeSchemaVersion;
|
|
@@ -58,7 +52,7 @@ export interface KnobEnvelope {
|
|
|
58
52
|
*/
|
|
59
53
|
readonly presetName?: string;
|
|
60
54
|
readonly intent: string | null;
|
|
61
|
-
readonly surface: { readonly size: GraphSurfaceSize
|
|
55
|
+
readonly surface: { readonly size: GraphSurfaceSize };
|
|
62
56
|
readonly scheme: "light" | "dark";
|
|
63
57
|
readonly themeName: string;
|
|
64
58
|
/**
|
|
@@ -125,7 +125,7 @@ export function useKnobEnvelope(options?: UseKnobEnvelopeOptions): KnobEnvelope
|
|
|
125
125
|
knobs: merged,
|
|
126
126
|
presetName: options?.presetName,
|
|
127
127
|
intent: options?.intent !== undefined ? options.intent : contextIntent,
|
|
128
|
-
surface: { size: surfaceCtx?.size ?? "unset"
|
|
128
|
+
surface: { size: surfaceCtx?.size ?? "unset" },
|
|
129
129
|
scheme: themeName.startsWith("dark") ? "dark" : "light",
|
|
130
130
|
themeName,
|
|
131
131
|
prefersReducedMotion,
|
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
* is what gets mocked, which is the point of having one import site.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
panelPaddingPx,
|
|
20
|
+
Preset,
|
|
21
|
+
resolveRadiusForPart,
|
|
22
|
+
Surface,
|
|
23
|
+
useResolvedKnobs,
|
|
24
|
+
} from "@multiplatform.one/theme";
|
|
19
25
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
20
26
|
import { cleanup } from "@testing-library/react";
|
|
21
27
|
import { readFileSync } from "node:fs";
|
|
@@ -25,7 +31,7 @@ import type { ReactElement } from "react";
|
|
|
25
31
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
26
32
|
import { declarationFor, hexLiteralsIn, paintDeclarationCount } from "../../tests/styles";
|
|
27
33
|
import type { Knobs } from "@multiplatform.one/theme";
|
|
28
|
-
import { containerRadiusPx, type GraphNodePort
|
|
34
|
+
import { containerRadiusPx, type GraphNodePort } from "./nodeMeasure";
|
|
29
35
|
|
|
30
36
|
const updateNodeInternals = vi.fn();
|
|
31
37
|
|
|
@@ -43,14 +49,11 @@ const radiusStops = [
|
|
|
43
49
|
"large",
|
|
44
50
|
"full",
|
|
45
51
|
] as const satisfies readonly Knobs["borderRadius"][];
|
|
46
|
-
const spaces = ["small", "medium", "large"] as const satisfies readonly Knobs["space"][];
|
|
47
52
|
|
|
48
53
|
function renderNode(ui: ReactElement, overrides?: Partial<Knobs>) {
|
|
49
54
|
return renderWithProviders(
|
|
50
55
|
<Preset cascade={false} overrides={overrides}>
|
|
51
|
-
<Surface size="
|
|
52
|
-
{ui}
|
|
53
|
-
</Surface>
|
|
56
|
+
<Surface size="$true">{ui}</Surface>
|
|
54
57
|
</Preset>,
|
|
55
58
|
);
|
|
56
59
|
}
|
|
@@ -71,41 +74,30 @@ afterEach(cleanup);
|
|
|
71
74
|
|
|
72
75
|
describe("radius: CONTAINER-CAP at all five stops, measured on the rendered frame", () => {
|
|
73
76
|
it.each(radiusStops)("resolves %s through the doctrine table", (borderRadius) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
stop: borderRadius,
|
|
83
|
-
paddingPx: nodePanelPaddingPx[space],
|
|
84
|
-
}).px;
|
|
85
|
-
expect(frame.getAttribute("data-graph-node-radius")).toBe(String(expected));
|
|
86
|
-
cleanup();
|
|
87
|
-
}
|
|
77
|
+
const { container } = renderNode(<GraphNode id="n1" title="Transform" />, { borderRadius });
|
|
78
|
+
const frame = frameOf(container);
|
|
79
|
+
const expected = resolveRadiusForPart({
|
|
80
|
+
radiusClass: "CONTAINER-CAP",
|
|
81
|
+
stop: borderRadius,
|
|
82
|
+
paddingPx: panelPaddingPx(),
|
|
83
|
+
}).px;
|
|
84
|
+
expect(frame.getAttribute("data-graph-node-radius")).toBe(String(expected));
|
|
88
85
|
});
|
|
89
86
|
|
|
90
87
|
it("paints that same radius — the emitted declaration, not just the attribute", () => {
|
|
91
88
|
for (const borderRadius of radiusStops) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const token = /^var\(--(?:t-)?radius-(\d+)\)$/.exec(painted!);
|
|
105
|
-
const paintedPx = token ? containerRadiusPx(`$${token[1]}`) : Number.parseFloat(painted!);
|
|
106
|
-
expect(paintedPx, `${borderRadius}/${space} painted ${painted}`).toBe(reported);
|
|
107
|
-
cleanup();
|
|
108
|
-
}
|
|
89
|
+
const { container } = renderNode(<GraphNode id="n1" title="Transform" />, { borderRadius });
|
|
90
|
+
const frame = frameOf(container);
|
|
91
|
+
const painted = declarationFor(frame, "border-top-left-radius");
|
|
92
|
+
expect(painted, `${borderRadius} paints no radius at all`).toBeDefined();
|
|
93
|
+
const reported = Number(frame.getAttribute("data-graph-node-radius"));
|
|
94
|
+
// happy-dom does not resolve custom properties, so a token-valued
|
|
95
|
+
// declaration reads back as `var(--t-radius-N)`. Decode it the same
|
|
96
|
+
// way the component does and require the two to agree.
|
|
97
|
+
const token = /^var\(--(?:t-)?radius-(\d+)\)$/.exec(painted!);
|
|
98
|
+
const paintedPx = token ? containerRadiusPx(`$${token[1]}`) : Number.parseFloat(painted!);
|
|
99
|
+
expect(paintedPx, `${borderRadius} painted ${painted}`).toBe(reported);
|
|
100
|
+
cleanup();
|
|
109
101
|
}
|
|
110
102
|
});
|
|
111
103
|
});
|
|
@@ -181,7 +173,7 @@ describe("useUpdateNodeInternals fires on a port change and never on a knob flip
|
|
|
181
173
|
];
|
|
182
174
|
rerender(
|
|
183
175
|
<Preset cascade={false}>
|
|
184
|
-
<Surface size="
|
|
176
|
+
<Surface size="$true">
|
|
185
177
|
<GraphNode id="n1" title="Transform" ports={grown} />
|
|
186
178
|
</Surface>
|
|
187
179
|
</Preset>,
|
|
@@ -193,20 +185,19 @@ describe("useUpdateNodeInternals fires on a port change and never on a knob flip
|
|
|
193
185
|
it("does NOT fire on a pure knob flip that keeps the same ports array", () => {
|
|
194
186
|
const { rerender } = renderNode(<GraphNode id="n1" title="Transform" ports={twoPorts} />, {
|
|
195
187
|
borderRadius: "none",
|
|
196
|
-
space: "small",
|
|
197
188
|
});
|
|
198
189
|
expect(updateNodeInternals).toHaveBeenCalledTimes(0);
|
|
199
190
|
|
|
200
191
|
rerender(
|
|
201
|
-
<Preset cascade={false} overrides={{ borderRadius: "full"
|
|
202
|
-
<Surface size="
|
|
192
|
+
<Preset cascade={false} overrides={{ borderRadius: "full" }}>
|
|
193
|
+
<Surface size="$true">
|
|
203
194
|
<GraphNode id="n1" title="Transform" ports={twoPorts} />
|
|
204
195
|
</Surface>
|
|
205
196
|
</Preset>,
|
|
206
197
|
);
|
|
207
198
|
// The flip must actually have restyled the node, or this proves nothing.
|
|
208
199
|
expect(frameOf(document.body as HTMLElement).getAttribute("data-graph-node-radius")).toBe(
|
|
209
|
-
String(
|
|
200
|
+
String(panelPaddingPx()),
|
|
210
201
|
);
|
|
211
202
|
expect(updateNodeInternals).toHaveBeenCalledTimes(0);
|
|
212
203
|
});
|
|
@@ -223,7 +214,7 @@ function renderResolved(overrides: Partial<Knobs>): {
|
|
|
223
214
|
}
|
|
224
215
|
renderWithProviders(
|
|
225
216
|
<Preset cascade={false} overrides={overrides}>
|
|
226
|
-
<Surface size="
|
|
217
|
+
<Surface size="$true">
|
|
227
218
|
<Probe />
|
|
228
219
|
</Surface>
|
|
229
220
|
</Preset>,
|
|
@@ -171,13 +171,11 @@ const fuzzCombos = [
|
|
|
171
171
|
borderRadius: "medium",
|
|
172
172
|
borderWidth: "none",
|
|
173
173
|
elevation: "none",
|
|
174
|
-
space: "large",
|
|
175
|
-
size: "medium",
|
|
176
174
|
textAccent: "medium",
|
|
177
175
|
headingFont: "geometric",
|
|
178
176
|
bodyFont: "sans-serif",
|
|
179
177
|
fontWeight: "bold",
|
|
180
|
-
animation: "
|
|
178
|
+
animation: "slow",
|
|
181
179
|
},
|
|
182
180
|
},
|
|
183
181
|
{
|
|
@@ -187,8 +185,6 @@ const fuzzCombos = [
|
|
|
187
185
|
borderRadius: "none",
|
|
188
186
|
borderWidth: "none",
|
|
189
187
|
elevation: "small",
|
|
190
|
-
space: "small",
|
|
191
|
-
size: "small",
|
|
192
188
|
textAccent: "medium",
|
|
193
189
|
headingFont: "rounded",
|
|
194
190
|
bodyFont: "cursive",
|
|
@@ -203,8 +199,6 @@ const fuzzCombos = [
|
|
|
203
199
|
borderRadius: "full",
|
|
204
200
|
borderWidth: "small",
|
|
205
201
|
elevation: "large",
|
|
206
|
-
space: "medium",
|
|
207
|
-
size: "large",
|
|
208
202
|
textAccent: "high",
|
|
209
203
|
headingFont: "mono",
|
|
210
204
|
bodyFont: "serif",
|
|
@@ -242,7 +236,7 @@ export const KnobFuzz: Story = {
|
|
|
242
236
|
<FuzzColumn key={scheme} scheme={scheme}>
|
|
243
237
|
{fuzzCombos.map((combo) => (
|
|
244
238
|
<Preset key={combo.label} cascade={false} overrides={combo.knobs}>
|
|
245
|
-
<Surface size="
|
|
239
|
+
<Surface size="$4">
|
|
246
240
|
<YStack gap="$2">
|
|
247
241
|
<SizableText size="$1" opacity={0.6}>
|
|
248
242
|
{combo.label}
|
package/src/nodes/GraphNode.tsx
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* would stack a second forced-layout storm on the observer's.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import { transitionProps } from "@multiplatform.one/theme";
|
|
22
|
+
|
|
21
23
|
import { type ReactNode, useEffect, useRef } from "react";
|
|
22
24
|
import { useGraphNodeKnobs } from "../knobs/graphKnobs";
|
|
23
25
|
import { SizableText, XStack, YStack } from "tamagui";
|
|
@@ -29,10 +31,10 @@ import {
|
|
|
29
31
|
containerRadiusPx,
|
|
30
32
|
type GraphNodeMeasure,
|
|
31
33
|
type GraphNodePort,
|
|
32
|
-
type GraphNodeSizeKnob,
|
|
33
34
|
graphNodeMinHeight,
|
|
34
35
|
graphNodeWidth,
|
|
35
36
|
portSpacing,
|
|
37
|
+
graphNodeSize,
|
|
36
38
|
} from "./nodeMeasure";
|
|
37
39
|
|
|
38
40
|
export interface GraphNodeProps {
|
|
@@ -100,7 +102,7 @@ export function GraphNode({
|
|
|
100
102
|
|
|
101
103
|
usePortInternals(id, ports);
|
|
102
104
|
|
|
103
|
-
const size = knobProps.
|
|
105
|
+
const size = graphNodeSize(knobProps.sizeToken);
|
|
104
106
|
const portSpacingPx = portSpacing({ coarsePointer });
|
|
105
107
|
// The dot straddles the row's edge, so half of it lands inside the row. The
|
|
106
108
|
// label clears the whole diameter rather than the half it strictly needs, so
|
|
@@ -143,7 +145,7 @@ export function GraphNode({
|
|
|
143
145
|
// §7.1 H6. Handles overhang the frame; a clipping node eats them.
|
|
144
146
|
overflow="visible"
|
|
145
147
|
opacity={dragging ? 0.85 : 1}
|
|
146
|
-
|
|
148
|
+
{...transitionProps(knobProps.motion.control, ["borderRadius", "borderWidth"])}
|
|
147
149
|
>
|
|
148
150
|
<XStack {...knobProps.gap} alignItems="center" justifyContent="space-between">
|
|
149
151
|
{/*
|
|
@@ -46,6 +46,7 @@ const radiusStops = [
|
|
|
46
46
|
"full",
|
|
47
47
|
] as const satisfies readonly Knobs["borderRadius"][];
|
|
48
48
|
const sizes = ["small", "medium", "large"] as const satisfies readonly GraphNodeSizeKnob[];
|
|
49
|
+
const sizeTokenFor = { small: "$3", medium: "$true", large: "$5" } as const;
|
|
49
50
|
|
|
50
51
|
const graphCss = readFileSync(
|
|
51
52
|
join(dirname(fileURLToPath(import.meta.url)), "../../css/graph.css"),
|
|
@@ -56,11 +57,8 @@ const afterRule = graphCss.match(/\.mp-graph-port::after\s*\{([^}]*)\}/);
|
|
|
56
57
|
const discRule = graphCss.match(/\.mp-graph-port\s*\{([^}]*)\}/);
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
|
-
* `Surface` is the size
|
|
60
|
-
*
|
|
61
|
-
* `useResolvedKnobs` (LC-68). So the size stops are driven through the Surface
|
|
62
|
-
* here — setting `overrides={{ size }}` under a fixed Surface is a no-op, and a
|
|
63
|
-
* spec that did it would be testing nothing while looking like it tested a lot.
|
|
60
|
+
* `Surface` is the size authority: the size token reaches the port only
|
|
61
|
+
* through the Surface ceiling, so each size row is driven through it here.
|
|
64
62
|
*/
|
|
65
63
|
function renderPort(
|
|
66
64
|
props: { side: GraphPortSide; coarsePointer?: boolean; portSpacingPx?: number },
|
|
@@ -69,7 +67,7 @@ function renderPort(
|
|
|
69
67
|
) {
|
|
70
68
|
return renderWithProviders(
|
|
71
69
|
<Preset cascade={false} overrides={overrides}>
|
|
72
|
-
<Surface size={surfaceSize}
|
|
70
|
+
<Surface size={sizeTokenFor[surfaceSize]}>
|
|
73
71
|
<ReactFlowProvider>
|
|
74
72
|
<GraphPort id="p1" direction="out" {...props} />
|
|
75
73
|
</ReactFlowProvider>
|
|
@@ -40,6 +40,9 @@ import { GraphPort } from "./GraphPort";
|
|
|
40
40
|
import { paintedPortSize, portGeometry, portHitSize } from "./handleGeometry";
|
|
41
41
|
import type { GraphNodePort, GraphNodeSizeKnob, GraphPortSide } from "./nodeMeasure";
|
|
42
42
|
|
|
43
|
+
/** The size token whose side of `$true` reads each table row. */
|
|
44
|
+
const rowToken = { small: "$3", medium: "$4", large: "$5" } as const;
|
|
45
|
+
|
|
43
46
|
const sides: GraphPortSide[] = ["left", "right", "top", "bottom"];
|
|
44
47
|
|
|
45
48
|
const crowded: GraphNodePort[] = [
|
|
@@ -59,7 +62,7 @@ const meta: Meta<typeof GraphPort> = {
|
|
|
59
62
|
docs: {
|
|
60
63
|
description: {
|
|
61
64
|
component:
|
|
62
|
-
"A port is painted only — it draws a dot and declares a hit region, and decides nothing about whether a connection is legal. The painted dot is 8/10/12 by the size
|
|
65
|
+
"A port is painted only — it draws a dot and declares a hit region, and decides nothing about whether a connection is legal. The painted dot is 8/10/12 by the size token and NEVER 44 (H1); the 44pt target is a ::after pseudo-element that grows outward (H2), so the rendered edge endpoint stays where the 8px dot put it. The circle survives borderRadius:none, because a square port reads as a resize grip.",
|
|
63
66
|
},
|
|
64
67
|
},
|
|
65
68
|
},
|
|
@@ -111,7 +114,7 @@ export const Main: Story = {
|
|
|
111
114
|
</SizableText>
|
|
112
115
|
{(["small", "medium", "large"] as const satisfies readonly GraphNodeSizeKnob[]).map(
|
|
113
116
|
(size) => (
|
|
114
|
-
<Surface key={size} size={size}
|
|
117
|
+
<Surface key={size} size={rowToken[size]}>
|
|
115
118
|
<XStack gap="$8" alignItems="center">
|
|
116
119
|
<SizableText size="$2" opacity={0.6} width={70}>
|
|
117
120
|
size {size}
|
|
@@ -329,7 +332,7 @@ export const HitTarget: Story = {
|
|
|
329
332
|
<XStack gap="$9" alignItems="center">
|
|
330
333
|
{(["small", "medium", "large"] as const satisfies readonly GraphNodeSizeKnob[]).map(
|
|
331
334
|
(size) => (
|
|
332
|
-
<Surface key={size} size={size}
|
|
335
|
+
<Surface key={size} size={rowToken[size]}>
|
|
333
336
|
<YStack gap="$3" alignItems="center" width={120}>
|
|
334
337
|
{/*
|
|
335
338
|
64 square so a 44 px region has room to paint without
|
package/src/nodes/GraphPort.tsx
CHANGED
|
@@ -21,7 +21,8 @@ import { isTouchSurface, useResolvedKnobs } from "@multiplatform.one/theme";
|
|
|
21
21
|
import type { CSSProperties } from "react";
|
|
22
22
|
import { Handle, Position } from "../react-flow/xy";
|
|
23
23
|
import { paintedPortSize, portHitSize, portRegionInset } from "./handleGeometry";
|
|
24
|
-
import type {
|
|
24
|
+
import type { GraphPortSide } from "./nodeMeasure";
|
|
25
|
+
import { graphNodeSize } from "./nodeMeasure";
|
|
25
26
|
|
|
26
27
|
const positionForSide: Record<GraphPortSide, Position> = {
|
|
27
28
|
left: Position.Left,
|
|
@@ -61,7 +62,7 @@ export function GraphPort({
|
|
|
61
62
|
isConnectable = true,
|
|
62
63
|
}: GraphPortProps) {
|
|
63
64
|
const { knobProps } = useResolvedKnobs({ component: "GraphPort", intent });
|
|
64
|
-
const size = knobProps.
|
|
65
|
+
const size = graphNodeSize(knobProps.sizeToken);
|
|
65
66
|
|
|
66
67
|
const painted = paintedPortSize(size);
|
|
67
68
|
const hit = portHitSize({ size, coarsePointer, portSpacingPx });
|
|
@@ -167,7 +167,7 @@ export const main = {
|
|
|
167
167
|
// between reloads, and it is why `size` is not fuzzable here.
|
|
168
168
|
render: () => (
|
|
169
169
|
<Preset>
|
|
170
|
-
<Surface size="
|
|
170
|
+
<Surface size="$4">
|
|
171
171
|
<YStack gap="$3" padding="$3">
|
|
172
172
|
<View width="100%" height={620}>
|
|
173
173
|
<NodeGraphWeb schema={schema} defaultDocument={bigDoc}>
|
|
@@ -200,7 +200,7 @@ export const crossover = {
|
|
|
200
200
|
const [high, setHigh] = useState(false);
|
|
201
201
|
return (
|
|
202
202
|
<Preset>
|
|
203
|
-
<Surface size="
|
|
203
|
+
<Surface size="$4">
|
|
204
204
|
<YStack gap="$3" padding="$3">
|
|
205
205
|
<Switch
|
|
206
206
|
id="overview-zoom"
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* The laws, verbatim from §7.1:
|
|
7
7
|
*
|
|
8
|
-
* H1. The painted dot is 8 / 10 / 12 by the size
|
|
8
|
+
* H1. The painted dot is 8 / 10 / 12 by the size token and NEVER grows to 44.
|
|
9
9
|
* A 44 px painted dot is a different component.
|
|
10
10
|
* H2. The hit region is a pseudo-element, never element growth, because
|
|
11
11
|
* `getEdgePosition` reads the handle box's outer edge (`center` defaults
|
package/src/nodes/lod.spec.tsx
CHANGED
|
@@ -48,6 +48,7 @@ const ports: GraphNodePort[] = [
|
|
|
48
48
|
];
|
|
49
49
|
|
|
50
50
|
const sizes = ["small", "medium", "large"] as const satisfies readonly GraphNodeSizeKnob[];
|
|
51
|
+
const sizeTokenFor = { small: "$3", medium: "$true", large: "$5" } as const;
|
|
51
52
|
|
|
52
53
|
// ── lodForZoom ───────────────────────────────────────────────────────────────
|
|
53
54
|
|
|
@@ -141,7 +142,7 @@ function renderNode(
|
|
|
141
142
|
) {
|
|
142
143
|
return renderWithProviders(
|
|
143
144
|
<Preset cascade={false} overrides={overrides}>
|
|
144
|
-
<Surface size={size}
|
|
145
|
+
<Surface size={sizeTokenFor[size]}>
|
|
145
146
|
<ReactFlowProvider>
|
|
146
147
|
<GraphNode
|
|
147
148
|
id="n1"
|
|
@@ -255,7 +256,7 @@ describe("OverviewNode measures from the same recipe the detail body does", () =
|
|
|
255
256
|
function renderOverviewNode(size: GraphNodeSizeKnob) {
|
|
256
257
|
return renderWithProviders(
|
|
257
258
|
<Preset cascade={false}>
|
|
258
|
-
<Surface size={size}
|
|
259
|
+
<Surface size={sizeTokenFor[size]}>
|
|
259
260
|
<ReactFlowProvider>
|
|
260
261
|
<OverviewNode id="n1" ports={ports} />
|
|
261
262
|
</ReactFlowProvider>
|