@usefragments/core 1.10.2 → 1.11.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/{chunk-MZ4SW3TP.js → chunk-3IOWHECM.js} +8 -1
- package/dist/{chunk-MZ4SW3TP.js.map → chunk-3IOWHECM.js.map} +1 -1
- package/dist/chunk-7ULGH74M.js +66 -0
- package/dist/chunk-7ULGH74M.js.map +1 -0
- package/dist/{chunk-YF65VYRY.js → chunk-ML5S6QNU.js} +191 -169
- package/dist/chunk-ML5S6QNU.js.map +1 -0
- package/dist/chunk-PWIJMOI4.js +202 -0
- package/dist/chunk-PWIJMOI4.js.map +1 -0
- package/dist/{chunk-XN3LSDPY.js → chunk-RPSEABY3.js} +622 -344
- package/dist/chunk-RPSEABY3.js.map +1 -0
- package/dist/chunk-RYFULE43.js +578 -0
- package/dist/chunk-RYFULE43.js.map +1 -0
- package/dist/codes/index.d.ts +2 -2
- package/dist/codes/index.js +3 -2
- package/dist/compiled-types/index.d.ts +327 -2
- package/dist/compiled-types/index.js +1 -1
- package/dist/generate/index.d.ts +3 -2
- package/dist/{governance-CLk_wkP9.d.ts → governance-BAsy1k2H.d.ts} +472 -513
- package/dist/governance-telemetry.d.ts +6 -0
- package/dist/governance-telemetry.js +1 -1
- package/dist/{index-_sxhUNqx.d.ts → index-C8bcXVav.d.ts} +448 -448
- package/dist/index.d.ts +9165 -1297
- package/dist/index.js +3041 -350
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +228 -0
- package/dist/manifest.js +24 -0
- package/dist/manifest.js.map +1 -0
- package/dist/preview/index.js +45 -1
- package/dist/preview/index.js.map +1 -1
- package/dist/preview-runtime.d.ts +1 -2
- package/dist/preview-runtime.js +150 -14
- package/dist/preview-runtime.js.map +1 -1
- package/dist/react-types.d.ts +1 -2
- package/dist/registry.d.ts +1412 -203
- package/dist/registry.js +30 -3
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +3 -2
- package/dist/storyAdapter.d.ts +1 -2
- package/dist/storyAdapter.js +11 -49
- package/dist/storyAdapter.js.map +1 -1
- package/dist/test-utils.d.ts +3 -2
- package/package.json +8 -2
- package/src/__tests__/contract-parser.test.ts +318 -277
- package/src/__tests__/preview-runtime-hook.test.tsx +315 -0
- package/src/__tests__/preview-runtime.test.tsx +30 -8
- package/src/__tests__/schema.test.ts +191 -14
- package/src/analysis-plan/analysis-plan-v1.test.ts +320 -0
- package/src/analysis-plan/coverage.ts +181 -0
- package/src/analysis-plan/digest.ts +141 -0
- package/src/analysis-plan/index.ts +34 -0
- package/src/analysis-plan/types.ts +207 -0
- package/src/codes/__tests__/codes.test.ts +9 -0
- package/src/codes/codes.ts +30 -0
- package/src/compiled-types/index.ts +640 -39
- package/src/compiled-types/parse.test.ts +145 -4
- package/src/component-contract.ts +95 -53
- package/src/composition.ts +7 -13
- package/src/constants.ts +3 -6
- package/src/contract/hash.test.ts +20 -0
- package/src/contract/hash.ts +66 -9
- package/src/contract/index.ts +24 -1
- package/src/contract/manifest.test.ts +94 -0
- package/src/contract/manifest.ts +68 -0
- package/src/contract/preimage.test.ts +219 -1
- package/src/contract/preimage.ts +326 -6
- package/src/contract/stamp.test.ts +3 -0
- package/src/contract/stamp.ts +1 -1
- package/src/contract-parser.ts +54 -30
- package/src/defineFragment.test.ts +476 -91
- package/src/defineFragment.ts +204 -114
- package/src/domain-ids.test.ts +52 -0
- package/src/domain-ids.ts +106 -0
- package/src/evaluation/evaluate.test.ts +522 -0
- package/src/evaluation/evaluate.ts +680 -0
- package/src/evaluation/evaluation-v2-receipt-v1.test.ts +760 -0
- package/src/evaluation/index.ts +58 -0
- package/src/evaluation/receipt.ts +753 -0
- package/src/evaluation/types.ts +406 -0
- package/src/facts/builders.ts +2 -0
- package/src/facts/compile.ts +29 -6
- package/src/facts/fact-index.ts +13 -3
- package/src/facts/fact-integrity-v1.test.ts +172 -0
- package/src/facts/facts.test.ts +15 -0
- package/src/facts/ids.ts +46 -3
- package/src/facts/index.ts +14 -1
- package/src/facts/integrity.ts +134 -0
- package/src/facts/types.ts +36 -0
- package/src/feature-plan/digest.ts +217 -0
- package/src/feature-plan/feature-plan-v1.test.ts +529 -0
- package/src/feature-plan/index.ts +65 -0
- package/src/feature-plan/types.ts +628 -0
- package/src/governance-integrity.test.ts +1 -0
- package/src/governance-integrity.ts +5 -3
- package/src/governance-telemetry.ts +8 -0
- package/src/governance.ts +52 -8
- package/src/index.ts +299 -37
- package/src/preview/validation.test.ts +62 -0
- package/src/preview/validation.ts +48 -2
- package/src/preview-runtime.tsx +227 -20
- package/src/registry-install-plan.ts +200 -109
- package/src/registry-shards.test.ts +263 -0
- package/src/registry.ts +237 -0
- package/src/repository-binding.test.ts +50 -0
- package/src/repository-binding.ts +96 -0
- package/src/rules/families.test.ts +36 -0
- package/src/rules/finding.ts +7 -2
- package/src/rules/index.ts +17 -1
- package/src/rules/rule-config.test.ts +66 -0
- package/src/rules/rule-config.ts +73 -0
- package/src/rules/rules.test.ts +26 -0
- package/src/rules/tokens-css-vars-must-be-defined.test.ts +51 -2
- package/src/rules/tokens-css-vars-must-be-defined.ts +34 -1
- package/src/schema.ts +293 -113
- package/src/schemas/index.ts +1 -1
- package/src/storyAdapter.test.ts +68 -12
- package/src/storyAdapter.ts +44 -75
- package/src/types.ts +258 -37
- package/dist/chunk-RANPUC6C.js +0 -72
- package/dist/chunk-RANPUC6C.js.map +0 -1
- package/dist/chunk-XN3LSDPY.js.map +0 -1
- package/dist/chunk-YF65VYRY.js.map +0 -1
- package/src/fragment-types.ts +0 -214
- package/src/react-create-element.test.ts +0 -22
- package/src/react-create-element.ts +0 -12
- package/src/storyFilters.test.ts +0 -350
- package/src/storyFilters.ts +0 -253
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
import type { PreviewArtifactManifest } from "./types";
|
|
1
|
+
import type { Previewability, PreviewArtifactManifest } from "./types";
|
|
2
|
+
|
|
3
|
+
const PREVIEWABILITY_VALUES = new Set<Previewability>([
|
|
4
|
+
"ready",
|
|
5
|
+
"building",
|
|
6
|
+
"needs-preview-provider",
|
|
7
|
+
"missing-dependency",
|
|
8
|
+
"missing-asset",
|
|
9
|
+
"server-only",
|
|
10
|
+
"unsupported-framework",
|
|
11
|
+
"runtime-error",
|
|
12
|
+
"build-error",
|
|
13
|
+
"docs-only",
|
|
14
|
+
]);
|
|
2
15
|
|
|
3
16
|
export interface PreviewArtifactValidationResult {
|
|
4
17
|
ok: boolean;
|
|
@@ -96,12 +109,25 @@ function validateComponents(value: unknown, issues: string[]) {
|
|
|
96
109
|
issues.push("components must be an object");
|
|
97
110
|
return;
|
|
98
111
|
}
|
|
112
|
+
const normalizedComponentKeys = new Set<string>();
|
|
99
113
|
for (const [key, component] of Object.entries(value)) {
|
|
100
114
|
if (!isRecord(component)) {
|
|
101
115
|
issues.push(`components.${key} must be an object`);
|
|
102
116
|
continue;
|
|
103
117
|
}
|
|
104
|
-
|
|
118
|
+
const componentKey = component.componentKey;
|
|
119
|
+
if (typeof componentKey !== "string" || !componentKey.trim()) {
|
|
120
|
+
issues.push(`components.${key}.componentKey must be a non-empty string`);
|
|
121
|
+
} else {
|
|
122
|
+
if (key !== componentKey) {
|
|
123
|
+
issues.push(`components.${key}.componentKey must match its components map key`);
|
|
124
|
+
}
|
|
125
|
+
const normalizedComponentKey = componentKey.trim();
|
|
126
|
+
if (normalizedComponentKeys.has(normalizedComponentKey)) {
|
|
127
|
+
issues.push(`components contains duplicate normalized componentKey ${componentKey}`);
|
|
128
|
+
}
|
|
129
|
+
normalizedComponentKeys.add(normalizedComponentKey);
|
|
130
|
+
}
|
|
105
131
|
requireString(component, "publicRef", issues, `components.${key}`);
|
|
106
132
|
requireString(component, "displayName", issues, `components.${key}`);
|
|
107
133
|
requireString(component, "exportName", issues, `components.${key}`);
|
|
@@ -112,17 +138,33 @@ function validateComponents(value: unknown, issues: string[]) {
|
|
|
112
138
|
`components.${key}.definitionSource must be one of typescript, fragment, manual, synthesized`
|
|
113
139
|
);
|
|
114
140
|
}
|
|
141
|
+
if (!isPreviewability(component.previewability)) {
|
|
142
|
+
issues.push(
|
|
143
|
+
`components.${key}.previewability must be one of ${[...PREVIEWABILITY_VALUES].join(", ")}`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
115
146
|
if (!isRecord(component.props)) {
|
|
116
147
|
issues.push(`components.${key}.props must be an object`);
|
|
117
148
|
}
|
|
118
149
|
if (!Array.isArray(component.variants)) {
|
|
119
150
|
issues.push(`components.${key}.variants must be an array`);
|
|
120
151
|
} else {
|
|
152
|
+
const normalizedVariantIds = new Set<string>();
|
|
121
153
|
component.variants.forEach((variant, index) => {
|
|
122
154
|
if (!isRecord(variant)) {
|
|
123
155
|
issues.push(`components.${key}.variants.${index} must be an object`);
|
|
124
156
|
return;
|
|
125
157
|
}
|
|
158
|
+
const variantPath = `components.${key}.variants.${index}`;
|
|
159
|
+
if (typeof variant.id !== "string" || !variant.id.trim()) {
|
|
160
|
+
issues.push(`${variantPath}.id must be a non-empty string`);
|
|
161
|
+
} else {
|
|
162
|
+
const normalizedVariantId = variant.id.trim();
|
|
163
|
+
if (normalizedVariantIds.has(normalizedVariantId)) {
|
|
164
|
+
issues.push(`${variantPath}.id must be unique within the component`);
|
|
165
|
+
}
|
|
166
|
+
normalizedVariantIds.add(normalizedVariantId);
|
|
167
|
+
}
|
|
126
168
|
validateDesignReference(
|
|
127
169
|
variant.design,
|
|
128
170
|
issues,
|
|
@@ -297,6 +339,10 @@ function isPreviewDefinitionSource(value: unknown): boolean {
|
|
|
297
339
|
);
|
|
298
340
|
}
|
|
299
341
|
|
|
342
|
+
function isPreviewability(value: unknown): value is Previewability {
|
|
343
|
+
return typeof value === "string" && PREVIEWABILITY_VALUES.has(value as Previewability);
|
|
344
|
+
}
|
|
345
|
+
|
|
300
346
|
function isFiniteNumber(value: unknown): value is number {
|
|
301
347
|
return typeof value === "number" && Number.isFinite(value);
|
|
302
348
|
}
|
package/src/preview-runtime.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState, type ReactNode } from "react";
|
|
1
|
+
import { useEffect, useRef, useState, type ReactNode } from "react";
|
|
2
2
|
import type { VariantLoader, VariantRenderOptions } from "./types.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -29,17 +29,208 @@ const EMPTY_STATE: PreviewRuntimeState = {
|
|
|
29
29
|
loadedData: undefined,
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
const LOADING_STATE: PreviewRuntimeState = {
|
|
33
|
+
content: null,
|
|
34
|
+
isLoading: true,
|
|
35
|
+
error: null,
|
|
36
|
+
loadedData: undefined,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const LOADED_DATA_COMPARISON_MAX_DEPTH = 32;
|
|
40
|
+
const LOADED_DATA_COMPARISON_MAX_OBJECTS = 1_000;
|
|
41
|
+
const LOADED_DATA_COMPARISON_MAX_PROPERTIES = 4_000;
|
|
42
|
+
|
|
43
|
+
const LOADED_DATA_COMPARISON_ERROR = new Error(
|
|
44
|
+
"Preview loadedData could not be compared safely. Pass a stable object reference, or use " +
|
|
45
|
+
`JSON-like data within ${LOADED_DATA_COMPARISON_MAX_DEPTH} levels, ` +
|
|
46
|
+
`${LOADED_DATA_COMPARISON_MAX_OBJECTS} objects, and ` +
|
|
47
|
+
`${LOADED_DATA_COMPARISON_MAX_PROPERTIES} property inspections.`
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const LOADED_DATA_COMPARISON_ERROR_STATE: PreviewRuntimeState = {
|
|
51
|
+
content: null,
|
|
52
|
+
isLoading: false,
|
|
53
|
+
error: LOADED_DATA_COMPARISON_ERROR,
|
|
54
|
+
loadedData: undefined,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
interface PreviewRuntimeResolution {
|
|
58
|
+
variant: PreviewVariantLike | null;
|
|
59
|
+
loadedData?: Record<string, unknown>;
|
|
60
|
+
state: PreviewRuntimeState;
|
|
61
|
+
}
|
|
62
|
+
|
|
32
63
|
function toError(error: unknown): Error {
|
|
33
64
|
return error instanceof Error ? error : new Error(String(error));
|
|
34
65
|
}
|
|
35
66
|
|
|
67
|
+
type LoadedDataComparison = "equal" | "different" | "indeterminate";
|
|
68
|
+
const NO_PENDING_LOADED_DATA = Symbol("no-pending-loaded-data");
|
|
69
|
+
|
|
70
|
+
interface LoadedDataComparisonContext {
|
|
71
|
+
comparedObjects: number;
|
|
72
|
+
inspectedProperties: number;
|
|
73
|
+
leftToRight: WeakMap<object, object>;
|
|
74
|
+
rightToLeft: WeakMap<object, object>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isPlainRecord(value: object): value is Record<string, unknown> {
|
|
78
|
+
const prototype = Object.getPrototypeOf(value);
|
|
79
|
+
return prototype === Object.prototype || prototype === null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function compareLoadedDataProperties(
|
|
83
|
+
left: object,
|
|
84
|
+
right: object,
|
|
85
|
+
depth: number,
|
|
86
|
+
context: LoadedDataComparisonContext
|
|
87
|
+
): LoadedDataComparison {
|
|
88
|
+
let leftPropertyCount = 0;
|
|
89
|
+
for (const key in left) {
|
|
90
|
+
if (context.inspectedProperties >= LOADED_DATA_COMPARISON_MAX_PROPERTIES) {
|
|
91
|
+
return "indeterminate";
|
|
92
|
+
}
|
|
93
|
+
context.inspectedProperties += 1;
|
|
94
|
+
|
|
95
|
+
if (!Object.prototype.hasOwnProperty.call(left, key)) continue;
|
|
96
|
+
leftPropertyCount += 1;
|
|
97
|
+
if (!Object.prototype.hasOwnProperty.call(right, key)) return "different";
|
|
98
|
+
|
|
99
|
+
const leftDescriptor = Object.getOwnPropertyDescriptor(left, key);
|
|
100
|
+
const rightDescriptor = Object.getOwnPropertyDescriptor(right, key);
|
|
101
|
+
if (
|
|
102
|
+
!leftDescriptor ||
|
|
103
|
+
!rightDescriptor ||
|
|
104
|
+
!("value" in leftDescriptor) ||
|
|
105
|
+
!("value" in rightDescriptor)
|
|
106
|
+
) {
|
|
107
|
+
return "indeterminate";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const comparison = compareLoadedDataValue(
|
|
111
|
+
leftDescriptor.value,
|
|
112
|
+
rightDescriptor.value,
|
|
113
|
+
depth + 1,
|
|
114
|
+
context
|
|
115
|
+
);
|
|
116
|
+
if (comparison !== "equal") return comparison;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let rightPropertyCount = 0;
|
|
120
|
+
for (const key in right) {
|
|
121
|
+
if (context.inspectedProperties >= LOADED_DATA_COMPARISON_MAX_PROPERTIES) {
|
|
122
|
+
return "indeterminate";
|
|
123
|
+
}
|
|
124
|
+
context.inspectedProperties += 1;
|
|
125
|
+
if (Object.prototype.hasOwnProperty.call(right, key)) rightPropertyCount += 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return leftPropertyCount === rightPropertyCount ? "equal" : "different";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function compareLoadedDataValue(
|
|
132
|
+
left: unknown,
|
|
133
|
+
right: unknown,
|
|
134
|
+
depth: number,
|
|
135
|
+
context: LoadedDataComparisonContext
|
|
136
|
+
): LoadedDataComparison {
|
|
137
|
+
if (Object.is(left, right)) return "equal";
|
|
138
|
+
|
|
139
|
+
const leftType = typeof left;
|
|
140
|
+
const rightType = typeof right;
|
|
141
|
+
if (leftType !== rightType || left === null || right === null) return "different";
|
|
142
|
+
if (leftType === "function" || leftType === "symbol") return "indeterminate";
|
|
143
|
+
if (leftType !== "object") return "different";
|
|
144
|
+
|
|
145
|
+
const leftObject = left as object;
|
|
146
|
+
const rightObject = right as object;
|
|
147
|
+
const pairedRight = context.leftToRight.get(leftObject);
|
|
148
|
+
const pairedLeft = context.rightToLeft.get(rightObject);
|
|
149
|
+
if (pairedRight || pairedLeft) {
|
|
150
|
+
return pairedRight === rightObject && pairedLeft === leftObject ? "equal" : "different";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (depth >= LOADED_DATA_COMPARISON_MAX_DEPTH) return "indeterminate";
|
|
154
|
+
if (context.comparedObjects >= LOADED_DATA_COMPARISON_MAX_OBJECTS) {
|
|
155
|
+
return "indeterminate";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const leftIsArray = Array.isArray(leftObject);
|
|
159
|
+
const rightIsArray = Array.isArray(rightObject);
|
|
160
|
+
if (leftIsArray !== rightIsArray) return "different";
|
|
161
|
+
if (leftIsArray && (leftObject as unknown[]).length !== (rightObject as unknown[]).length) {
|
|
162
|
+
return "different";
|
|
163
|
+
}
|
|
164
|
+
if (!leftIsArray) {
|
|
165
|
+
const leftIsPlainRecord = isPlainRecord(leftObject);
|
|
166
|
+
const rightIsPlainRecord = isPlainRecord(rightObject);
|
|
167
|
+
if (leftIsPlainRecord !== rightIsPlainRecord) return "different";
|
|
168
|
+
if (!leftIsPlainRecord) return "indeterminate";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
context.comparedObjects += 1;
|
|
172
|
+
context.leftToRight.set(leftObject, rightObject);
|
|
173
|
+
context.rightToLeft.set(rightObject, leftObject);
|
|
174
|
+
|
|
175
|
+
return compareLoadedDataProperties(leftObject, rightObject, depth, context);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function compareLoadedData(
|
|
179
|
+
left: Record<string, unknown> | undefined,
|
|
180
|
+
right: Record<string, unknown> | undefined
|
|
181
|
+
): LoadedDataComparison {
|
|
182
|
+
try {
|
|
183
|
+
return compareLoadedDataValue(left, right, 0, {
|
|
184
|
+
comparedObjects: 0,
|
|
185
|
+
inspectedProperties: 0,
|
|
186
|
+
leftToRight: new WeakMap(),
|
|
187
|
+
rightToLeft: new WeakMap(),
|
|
188
|
+
});
|
|
189
|
+
} catch {
|
|
190
|
+
// Host data must not make render fail when an accessor or proxy cannot be inspected safely.
|
|
191
|
+
return "indeterminate";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
interface StableLoadedDataResult {
|
|
196
|
+
loadedData: Record<string, unknown> | undefined;
|
|
197
|
+
comparisonError: Error | null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function useStableLoadedData(
|
|
201
|
+
loadedData: Record<string, unknown> | undefined
|
|
202
|
+
): StableLoadedDataResult {
|
|
203
|
+
const stableLoadedData = useRef(loadedData);
|
|
204
|
+
const pendingLoadedData = useRef<
|
|
205
|
+
Record<string, unknown> | undefined | typeof NO_PENDING_LOADED_DATA
|
|
206
|
+
>(NO_PENDING_LOADED_DATA);
|
|
207
|
+
const comparison = compareLoadedData(stableLoadedData.current, loadedData);
|
|
208
|
+
|
|
209
|
+
if (comparison === "equal") {
|
|
210
|
+
pendingLoadedData.current = NO_PENDING_LOADED_DATA;
|
|
211
|
+
return { loadedData: stableLoadedData.current, comparisonError: null };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (comparison === "different" || pendingLoadedData.current === loadedData) {
|
|
215
|
+
stableLoadedData.current = loadedData;
|
|
216
|
+
pendingLoadedData.current = NO_PENDING_LOADED_DATA;
|
|
217
|
+
return { loadedData, comparisonError: null };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
pendingLoadedData.current = loadedData;
|
|
221
|
+
return {
|
|
222
|
+
loadedData: stableLoadedData.current,
|
|
223
|
+
comparisonError: LOADED_DATA_COMPARISON_ERROR,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
36
227
|
/**
|
|
37
228
|
* Execute all variant loaders and merge their payloads.
|
|
38
229
|
* `loadedData` is applied last so host-level overrides win.
|
|
39
230
|
*/
|
|
40
231
|
export async function executeVariantLoaders(
|
|
41
232
|
loaders: VariantLoader[] | undefined,
|
|
42
|
-
loadedData?: Record<string, unknown
|
|
233
|
+
loadedData?: Record<string, unknown>
|
|
43
234
|
): Promise<Record<string, unknown> | undefined> {
|
|
44
235
|
const hasLoaders = !!loaders && loaders.length > 0;
|
|
45
236
|
if (!hasLoaders) {
|
|
@@ -49,7 +240,7 @@ export async function executeVariantLoaders(
|
|
|
49
240
|
const results = await Promise.all(loaders.map((loader) => loader()));
|
|
50
241
|
const mergedFromLoaders = results.reduce<Record<string, unknown>>(
|
|
51
242
|
(acc, result) => ({ ...acc, ...result }),
|
|
52
|
-
{}
|
|
243
|
+
{}
|
|
53
244
|
);
|
|
54
245
|
|
|
55
246
|
return loadedData ? { ...mergedFromLoaders, ...loadedData } : mergedFromLoaders;
|
|
@@ -60,7 +251,7 @@ export async function executeVariantLoaders(
|
|
|
60
251
|
* This is useful for testing and for hook/component orchestration.
|
|
61
252
|
*/
|
|
62
253
|
export async function resolvePreviewRuntimeState(
|
|
63
|
-
options: PreviewRuntimeOptions
|
|
254
|
+
options: PreviewRuntimeOptions
|
|
64
255
|
): Promise<PreviewRuntimeState> {
|
|
65
256
|
const { variant, loadedData } = options;
|
|
66
257
|
if (!variant) {
|
|
@@ -89,42 +280,58 @@ export async function resolvePreviewRuntimeState(
|
|
|
89
280
|
/**
|
|
90
281
|
* Hook for rendering a preview variant with loader support.
|
|
91
282
|
*/
|
|
92
|
-
export function usePreviewVariantRuntime(
|
|
93
|
-
options
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
283
|
+
export function usePreviewVariantRuntime(options: PreviewRuntimeOptions): PreviewRuntimeState {
|
|
284
|
+
const { variant } = options;
|
|
285
|
+
const { loadedData, comparisonError } = useStableLoadedData(options.loadedData);
|
|
286
|
+
const [resolution, setResolution] = useState<PreviewRuntimeResolution>({
|
|
287
|
+
variant: null,
|
|
288
|
+
loadedData: undefined,
|
|
289
|
+
state: EMPTY_STATE,
|
|
290
|
+
});
|
|
97
291
|
|
|
98
292
|
useEffect(() => {
|
|
99
293
|
let cancelled = false;
|
|
100
294
|
|
|
101
295
|
if (!variant) {
|
|
102
|
-
|
|
296
|
+
setResolution({ variant: null, loadedData, state: EMPTY_STATE });
|
|
103
297
|
return () => {
|
|
104
298
|
cancelled = true;
|
|
105
299
|
};
|
|
106
300
|
}
|
|
107
301
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
302
|
+
if (comparisonError) {
|
|
303
|
+
setResolution({ variant, loadedData, state: LOADED_DATA_COMPARISON_ERROR_STATE });
|
|
304
|
+
return () => {
|
|
305
|
+
cancelled = true;
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
setResolution({ variant, loadedData, state: LOADING_STATE });
|
|
115
310
|
|
|
116
311
|
resolvePreviewRuntimeState({ variant, loadedData }).then((nextState) => {
|
|
117
312
|
if (!cancelled) {
|
|
118
|
-
|
|
313
|
+
setResolution({ variant, loadedData, state: nextState });
|
|
119
314
|
}
|
|
120
315
|
});
|
|
121
316
|
|
|
122
317
|
return () => {
|
|
123
318
|
cancelled = true;
|
|
124
319
|
};
|
|
125
|
-
}, [variant, loadedData]);
|
|
320
|
+
}, [variant, loadedData, comparisonError]);
|
|
321
|
+
|
|
322
|
+
if (!variant) {
|
|
323
|
+
return EMPTY_STATE;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (comparisonError) {
|
|
327
|
+
return LOADED_DATA_COMPARISON_ERROR_STATE;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (resolution.variant !== variant || resolution.loadedData !== loadedData) {
|
|
331
|
+
return LOADING_STATE;
|
|
332
|
+
}
|
|
126
333
|
|
|
127
|
-
return state;
|
|
334
|
+
return resolution.state;
|
|
128
335
|
}
|
|
129
336
|
|
|
130
337
|
interface PreviewVariantRuntimeProps extends PreviewRuntimeOptions {
|