@kotao/storefront 0.2.4 → 0.2.6

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.
Files changed (69) hide show
  1. package/README.md +50 -2
  2. package/dist/bridge-ZKbKqgFd.d.ts +239 -0
  3. package/dist/cart-documents-BLavRDiu.d.ts +89 -0
  4. package/dist/cart-documents.d.ts +1 -2
  5. package/dist/cart-documents.js +1 -1
  6. package/dist/{chunk-M7BPQ54D.js → chunk-2MRB6SMP.js} +4 -3
  7. package/dist/chunk-2MRB6SMP.js.map +1 -0
  8. package/dist/chunk-CKGI3Y7L.js +19 -0
  9. package/dist/chunk-CKGI3Y7L.js.map +1 -0
  10. package/dist/{chunk-4TEFGD42.js → chunk-EG34HLSX.js} +209 -162
  11. package/dist/chunk-EG34HLSX.js.map +1 -0
  12. package/dist/chunk-O6YZEY7X.js +1382 -0
  13. package/dist/chunk-O6YZEY7X.js.map +1 -0
  14. package/dist/{chunk-7MTCGXIG.js → chunk-R4N565GX.js} +8 -4
  15. package/dist/chunk-R4N565GX.js.map +1 -0
  16. package/dist/{chunk-XMTWFJCZ.js → chunk-RFM27BKZ.js} +31 -7
  17. package/dist/chunk-RFM27BKZ.js.map +1 -0
  18. package/dist/{chunk-MYDKIJRH.js → chunk-SVMYFYIA.js} +2 -2
  19. package/dist/{chunk-MYDKIJRH.js.map → chunk-SVMYFYIA.js.map} +1 -1
  20. package/dist/{chunk-H3D7BAOL.js → chunk-VOTBNOWH.js} +21 -3
  21. package/dist/chunk-VOTBNOWH.js.map +1 -0
  22. package/dist/{chunk-PXVDVUFX.js → chunk-W74FGUZH.js} +10 -3
  23. package/dist/chunk-W74FGUZH.js.map +1 -0
  24. package/dist/{chunk-2IWNUDSZ.js → chunk-WNXI3MPM.js} +2 -2
  25. package/dist/chunk-WNXI3MPM.js.map +1 -0
  26. package/dist/chunk-XXNCFWXT.js +2118 -0
  27. package/dist/chunk-XXNCFWXT.js.map +1 -0
  28. package/dist/{client-bsiNPHBi.d.ts → client-CwOPPpFF.d.ts} +11 -1
  29. package/dist/{createCartHandler-C4RrPwSG.d.ts → createCartHandler-7DdKB-GF.d.ts} +1 -1
  30. package/dist/custom-css-Bl7Ur3-H.d.ts +6 -0
  31. package/dist/editor.d.ts +57 -104
  32. package/dist/editor.js +270 -1
  33. package/dist/editor.js.map +1 -1
  34. package/dist/events-react.d.ts +1 -1
  35. package/dist/events-react.js +1 -1
  36. package/dist/events.d.ts +3 -3
  37. package/dist/events.js +1 -1
  38. package/dist/{fetch-M3Y-fQrW.d.ts → fetch-Bq_cc8_G.d.ts} +5 -0
  39. package/dist/hydrogen-Bu7k0kE4.d.ts +259 -0
  40. package/dist/index.d.ts +74 -51
  41. package/dist/index.js +19 -15
  42. package/dist/index.js.map +1 -1
  43. package/dist/react-router.d.ts +30 -9
  44. package/dist/react-router.js +16 -7
  45. package/dist/react-router.js.map +1 -1
  46. package/dist/react.d.ts +79 -11
  47. package/dist/react.js +279 -10
  48. package/dist/react.js.map +1 -1
  49. package/dist/{routing-sEonP93J.d.ts → routing-Bz3Cg0BW.d.ts} +36 -6
  50. package/dist/seo.d.ts +7 -4
  51. package/dist/seo.js +2 -2
  52. package/dist/settings.d.ts +14 -4
  53. package/dist/settings.js +2 -2
  54. package/dist/{shop-BJSvtmnV.d.ts → shop-CnrnSAJw.d.ts} +33 -6
  55. package/dist/{store-CtCWHI8H.d.ts → store-DO08fzJi.d.ts} +1 -1
  56. package/dist/storefront-api-types.d.ts +12 -0
  57. package/dist/{types-BDhHWZsP.d.ts → types-BkY0yET-.d.ts} +3 -3
  58. package/dist/{types-bcbQzx87.d.ts → types-DZX8ScsJ.d.ts} +2 -0
  59. package/package.json +4 -4
  60. package/dist/cart-documents-CwwRTvm8.d.ts +0 -21
  61. package/dist/chunk-2IWNUDSZ.js.map +0 -1
  62. package/dist/chunk-4TEFGD42.js.map +0 -1
  63. package/dist/chunk-7MTCGXIG.js.map +0 -1
  64. package/dist/chunk-H3D7BAOL.js.map +0 -1
  65. package/dist/chunk-M7BPQ54D.js.map +0 -1
  66. package/dist/chunk-PXVDVUFX.js.map +0 -1
  67. package/dist/chunk-PXZ737N3.js +0 -439
  68. package/dist/chunk-PXZ737N3.js.map +0 -1
  69. package/dist/chunk-XMTWFJCZ.js.map +0 -1
@@ -0,0 +1,2118 @@
1
+ import { HydrogenEditorManifestSchema, HYDROGEN_EDITOR_MANIFEST_LIMITS, HydrogenInstanceSchema, HydrogenInstanceStylesSchema, HydrogenContentBindingSchema, HydrogenDocumentSchema, HydrogenBreakpointSchema, HydrogenComponentDefinitionSchema, HydrogenEditCapabilitySchema, HydrogenSourceLocationSchema, HydrogenUnsupportedFindingSchema } from '@kotao/storefront-contracts';
2
+
3
+ // src/editor/hydrogen.ts
4
+ var HOST_SOURCE = "kotao:workspaces-hydrogen-editor";
5
+ var PREVIEW_SOURCE = "kotao:storefront-hydrogen-preview";
6
+ var SHA_1 = /^[a-f0-9]{40}$/;
7
+ var SHA_256 = /^[a-f0-9]{64}$/;
8
+ var OPAQUE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
9
+ var CHANNEL_NONCE = /^[A-Za-z0-9_-]{32,256}$/;
10
+ var OPERATION_FAILURE_CODES = /* @__PURE__ */ new Set([
11
+ "CAPABILITY_DENIED",
12
+ "COMPONENT_NOT_FOUND",
13
+ "DOCUMENT_MISMATCH",
14
+ "DUPLICATE_OPERATION_CONFLICT",
15
+ "INSTANCE_NOT_FOUND",
16
+ "INVALID_OPERATION",
17
+ "OPERATION_LIMIT_EXCEEDED",
18
+ "REVISION_MISMATCH",
19
+ "SLOT_CONSTRAINT",
20
+ "UNSUPPORTED_BOUNDARY"
21
+ ]);
22
+ var PROTOCOL_FAILURE_CODES = /* @__PURE__ */ new Set([
23
+ "AUTH_CONTEXT_MISMATCH",
24
+ "BUFFER_LIMIT_EXCEEDED",
25
+ "DUPLICATE_MESSAGE_CONFLICT",
26
+ "MESSAGE_EXPIRED",
27
+ "MESSAGE_TOO_DEEP",
28
+ "MESSAGE_TOO_LARGE",
29
+ "SEQUENCE_STALE",
30
+ "UNKNOWN_MESSAGE",
31
+ "UNSUPPORTED_PROTOCOL"
32
+ ]);
33
+ var HYDROGEN_EDITOR_PROTOCOL_VERSION = 1;
34
+ var HYDROGEN_EDITOR_BRIDGE_LIMITS = {
35
+ maxMessageBytes: 131072,
36
+ maxMessageDepth: 32,
37
+ maxBufferedMessages: 100,
38
+ maxDraftOperations: 1e3,
39
+ maxProcessedMessages: 2048
40
+ };
41
+ var HydrogenEditorConfigurationError = class extends Error {
42
+ constructor(message) {
43
+ super(message);
44
+ this.name = "HydrogenEditorConfigurationError";
45
+ }
46
+ };
47
+ function isRecord(value) {
48
+ return typeof value === "object" && value !== null && !Array.isArray(value);
49
+ }
50
+ function hasOnlyKeys(value, keys) {
51
+ const allowed = new Set(keys);
52
+ return Object.keys(value).every((key) => allowed.has(key));
53
+ }
54
+ function isOpaqueId(value) {
55
+ return typeof value === "string" && OPAQUE_ID.test(value);
56
+ }
57
+ function isNonnegativeInteger(value) {
58
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
59
+ }
60
+ function isPositiveInteger(value) {
61
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
62
+ }
63
+ function exactOrigin(value, label) {
64
+ try {
65
+ const parsed = new URL(value);
66
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:" || parsed.username !== "" || parsed.password !== "" || parsed.origin !== value || parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "") {
67
+ throw new Error("not an exact origin");
68
+ }
69
+ return value;
70
+ } catch {
71
+ throw new HydrogenEditorConfigurationError(`${label} must be an exact http(s) origin`);
72
+ }
73
+ }
74
+ function validateSecurityContext(security, now) {
75
+ if (!isOpaqueId(security.sessionId)) {
76
+ throw new HydrogenEditorConfigurationError("sessionId must be a bounded opaque identifier");
77
+ }
78
+ if (!SHA_1.test(security.baseSha)) {
79
+ throw new HydrogenEditorConfigurationError("baseSha must be a 40-character lowercase Git SHA");
80
+ }
81
+ if (!SHA_256.test(security.manifestHash)) {
82
+ throw new HydrogenEditorConfigurationError("manifestHash must be a lowercase SHA-256 digest");
83
+ }
84
+ if (!CHANNEL_NONCE.test(security.channelNonce)) {
85
+ throw new HydrogenEditorConfigurationError(
86
+ "channelNonce must be a 32..256 character base64url value"
87
+ );
88
+ }
89
+ if (security.previewToken.length < 32 || security.previewToken.length > 4096) {
90
+ throw new HydrogenEditorConfigurationError("previewToken must contain 32..4096 characters");
91
+ }
92
+ if (!Number.isSafeInteger(security.expiresAt) || security.expiresAt <= now) {
93
+ throw new HydrogenEditorConfigurationError("editor preview security context is expired");
94
+ }
95
+ }
96
+ function jsonSignature(value) {
97
+ const ancestors = /* @__PURE__ */ new Set();
98
+ const visit = (candidate, depth) => {
99
+ if (depth > HYDROGEN_EDITOR_BRIDGE_LIMITS.maxMessageDepth) return false;
100
+ if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean" || typeof candidate === "number" && Number.isFinite(candidate)) {
101
+ return true;
102
+ }
103
+ if (typeof candidate !== "object") return false;
104
+ if (ancestors.has(candidate)) return false;
105
+ ancestors.add(candidate);
106
+ let valid;
107
+ if (Array.isArray(candidate)) {
108
+ valid = candidate.every((item) => visit(item, depth + 1));
109
+ } else {
110
+ const record = candidate;
111
+ valid = Object.values(record).every((item) => visit(item, depth + 1));
112
+ }
113
+ ancestors.delete(candidate);
114
+ return valid;
115
+ };
116
+ if (!visit(value, 0)) return null;
117
+ try {
118
+ return JSON.stringify(value);
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+ function protocolFailure(code, message) {
124
+ return { code, message };
125
+ }
126
+ function parseProtocolFailure(value) {
127
+ return isRecord(value) && typeof value.code === "string" && PROTOCOL_FAILURE_CODES.has(value.code) && typeof value.message === "string" && value.message.length <= 2048 ? value : null;
128
+ }
129
+ function operationFailure(code, message, instance, capability) {
130
+ return {
131
+ code,
132
+ message,
133
+ ...instance !== void 0 ? { instanceId: instance.instanceId, source: instance.source } : {},
134
+ ...capability !== void 0 ? { capability } : {}
135
+ };
136
+ }
137
+ function rejectOperation(operation, revision, failure) {
138
+ return {
139
+ status: "rejected",
140
+ revision,
141
+ operationId: operation.operationId,
142
+ failure
143
+ };
144
+ }
145
+ function parseOperation(value) {
146
+ if (!isRecord(value) || !isOpaqueId(value.operationId) || !isPositiveInteger(value.revision) || typeof value.type !== "string") {
147
+ return null;
148
+ }
149
+ const base = {
150
+ operationId: value.operationId,
151
+ revision: value.revision
152
+ };
153
+ switch (value.type) {
154
+ case "prop.patch": {
155
+ if (!hasOnlyKeys(value, [
156
+ "type",
157
+ "operationId",
158
+ "revision",
159
+ "instanceId",
160
+ "propName",
161
+ "binding"
162
+ ]) || !isOpaqueId(value.instanceId) || typeof value.propName !== "string" || value.propName.length === 0 || value.propName.length > 128) {
163
+ return null;
164
+ }
165
+ const binding = HydrogenContentBindingSchema.safeParse(value.binding);
166
+ return binding.success ? {
167
+ ...base,
168
+ type: "prop.patch",
169
+ instanceId: value.instanceId,
170
+ propName: value.propName,
171
+ binding: binding.data
172
+ } : null;
173
+ }
174
+ case "styles.patch": {
175
+ if (!hasOnlyKeys(value, ["type", "operationId", "revision", "instanceId", "styles"]) || !isOpaqueId(value.instanceId)) {
176
+ return null;
177
+ }
178
+ const styles = HydrogenInstanceStylesSchema.safeParse(value.styles);
179
+ return styles.success ? {
180
+ ...base,
181
+ type: "styles.patch",
182
+ instanceId: value.instanceId,
183
+ styles: styles.data
184
+ } : null;
185
+ }
186
+ case "variant.set":
187
+ return hasOnlyKeys(value, ["type", "operationId", "revision", "instanceId", "variantId"]) && isOpaqueId(value.instanceId) && (value.variantId === null || isOpaqueId(value.variantId)) ? {
188
+ ...base,
189
+ type: "variant.set",
190
+ instanceId: value.instanceId,
191
+ variantId: value.variantId
192
+ } : null;
193
+ case "slot.insert": {
194
+ if (!hasOnlyKeys(value, [
195
+ "type",
196
+ "operationId",
197
+ "revision",
198
+ "parentInstanceId",
199
+ "slotId",
200
+ "index",
201
+ "instance"
202
+ ]) || !isOpaqueId(value.parentInstanceId) || !isOpaqueId(value.slotId) || !isNonnegativeInteger(value.index) || !isRecord(value.instance)) {
203
+ return null;
204
+ }
205
+ const parsed = HydrogenInstanceSchema.safeParse({ ...value.instance, parent: null });
206
+ if (!parsed.success) return null;
207
+ const instance = {
208
+ instanceId: parsed.data.instanceId,
209
+ componentId: parsed.data.componentId,
210
+ documentId: parsed.data.documentId,
211
+ source: parsed.data.source,
212
+ ...parsed.data.variantId !== void 0 ? { variantId: parsed.data.variantId } : {},
213
+ props: parsed.data.props,
214
+ styles: parsed.data.styles
215
+ };
216
+ return {
217
+ ...base,
218
+ type: "slot.insert",
219
+ parentInstanceId: value.parentInstanceId,
220
+ slotId: value.slotId,
221
+ index: value.index,
222
+ instance
223
+ };
224
+ }
225
+ case "instance.move":
226
+ return hasOnlyKeys(value, [
227
+ "type",
228
+ "operationId",
229
+ "revision",
230
+ "instanceId",
231
+ "parentInstanceId",
232
+ "slotId",
233
+ "index"
234
+ ]) && isOpaqueId(value.instanceId) && isOpaqueId(value.parentInstanceId) && isOpaqueId(value.slotId) && isNonnegativeInteger(value.index) ? {
235
+ ...base,
236
+ type: "instance.move",
237
+ instanceId: value.instanceId,
238
+ parentInstanceId: value.parentInstanceId,
239
+ slotId: value.slotId,
240
+ index: value.index
241
+ } : null;
242
+ case "instance.remove":
243
+ return hasOnlyKeys(value, ["type", "operationId", "revision", "instanceId"]) && isOpaqueId(value.instanceId) ? {
244
+ ...base,
245
+ type: "instance.remove",
246
+ instanceId: value.instanceId
247
+ } : null;
248
+ default:
249
+ return null;
250
+ }
251
+ }
252
+ var DIRECT_CANVAS_LAYOUT_CONTROLS = /* @__PURE__ */ new Set(["gap", "height", "margin", "padding", "width"]);
253
+ function parseHydrogenEditorCanvasIntent(value) {
254
+ if (!isRecord(value) || typeof value.type !== "string") return null;
255
+ switch (value.type) {
256
+ case "instance.move":
257
+ return hasOnlyKeys(value, ["type", "instanceId", "parentInstanceId", "slotId", "index"]) && isOpaqueId(value.instanceId) && isOpaqueId(value.parentInstanceId) && isOpaqueId(value.slotId) && isNonnegativeInteger(value.index) ? {
258
+ type: "instance.move",
259
+ instanceId: value.instanceId,
260
+ parentInstanceId: value.parentInstanceId,
261
+ slotId: value.slotId,
262
+ index: value.index
263
+ } : null;
264
+ case "prop.inline":
265
+ return hasOnlyKeys(value, ["type", "instanceId", "propName", "value"]) && isOpaqueId(value.instanceId) && typeof value.propName === "string" && value.propName.length > 0 && value.propName.length <= 128 && typeof value.value === "string" && value.value.length <= 1e4 ? {
266
+ type: "prop.inline",
267
+ instanceId: value.instanceId,
268
+ propName: value.propName,
269
+ value: value.value
270
+ } : null;
271
+ case "styles.adjust":
272
+ return hasOnlyKeys(value, ["type", "instanceId", "controlId", "value", "breakpointId"]) && isOpaqueId(value.instanceId) && isOpaqueId(value.controlId) && typeof value.value === "number" && Number.isFinite(value.value) && value.value >= 0 && value.value <= 4096 && (value.breakpointId === void 0 || isOpaqueId(value.breakpointId)) ? {
273
+ type: "styles.adjust",
274
+ instanceId: value.instanceId,
275
+ controlId: value.controlId,
276
+ value: Math.round(value.value * 100) / 100,
277
+ ...value.breakpointId !== void 0 ? { breakpointId: value.breakpointId } : {}
278
+ } : null;
279
+ case "slot.insert.request":
280
+ return hasOnlyKeys(value, ["type", "parentInstanceId", "slotId", "index"]) && isOpaqueId(value.parentInstanceId) && isOpaqueId(value.slotId) && isNonnegativeInteger(value.index) ? {
281
+ type: "slot.insert.request",
282
+ parentInstanceId: value.parentInstanceId,
283
+ slotId: value.slotId,
284
+ index: value.index
285
+ } : null;
286
+ default:
287
+ return null;
288
+ }
289
+ }
290
+ function parseEnvelope(value, source, security, state, now) {
291
+ const signature = jsonSignature(value);
292
+ if (signature === null) {
293
+ return protocolFailure("MESSAGE_TOO_DEEP", "message is cyclic, non-JSON, or exceeds depth 32");
294
+ }
295
+ if (new TextEncoder().encode(signature).byteLength > HYDROGEN_EDITOR_BRIDGE_LIMITS.maxMessageBytes) {
296
+ return protocolFailure("MESSAGE_TOO_LARGE", "message exceeds 128 KiB");
297
+ }
298
+ if (!isRecord(value)) {
299
+ return protocolFailure("UNKNOWN_MESSAGE", "message must be an object");
300
+ }
301
+ if (value.protocolVersion !== HYDROGEN_EDITOR_PROTOCOL_VERSION) {
302
+ return protocolFailure("UNSUPPORTED_PROTOCOL", "unsupported Hydrogen editor protocol version");
303
+ }
304
+ if (value.source !== source || value.sessionId !== security.sessionId || value.baseSha !== security.baseSha || value.manifestHash !== security.manifestHash || value.channelNonce !== security.channelNonce || value.previewToken !== security.previewToken) {
305
+ return protocolFailure(
306
+ "AUTH_CONTEXT_MISMATCH",
307
+ "message does not match the active editor session"
308
+ );
309
+ }
310
+ if (now >= security.expiresAt) {
311
+ return protocolFailure("MESSAGE_EXPIRED", "editor preview token has expired");
312
+ }
313
+ if (!isOpaqueId(value.messageId) || !isPositiveInteger(value.sequence) || typeof value.type !== "string" || value.type.length > 128) {
314
+ return protocolFailure("UNKNOWN_MESSAGE", "message envelope is invalid");
315
+ }
316
+ const previous = state.messages.get(value.messageId);
317
+ if (previous !== void 0) {
318
+ return previous === signature ? { envelope: value, duplicate: true } : protocolFailure(
319
+ "DUPLICATE_MESSAGE_CONFLICT",
320
+ "messageId was reused with a different payload"
321
+ );
322
+ }
323
+ if (value.sequence <= state.sequence) {
324
+ return protocolFailure("SEQUENCE_STALE", "message sequence is not newer than the last message");
325
+ }
326
+ state.sequence = value.sequence;
327
+ state.messages.set(value.messageId, signature);
328
+ if (state.messages.size > HYDROGEN_EDITOR_BRIDGE_LIMITS.maxProcessedMessages) {
329
+ const oldest = state.messages.keys().next().value;
330
+ if (typeof oldest === "string") state.messages.delete(oldest);
331
+ }
332
+ return { envelope: value, duplicate: false };
333
+ }
334
+ function createEnvelope(source, security, sequence, type, payload) {
335
+ return {
336
+ source,
337
+ protocolVersion: HYDROGEN_EDITOR_PROTOCOL_VERSION,
338
+ sessionId: security.sessionId,
339
+ baseSha: security.baseSha,
340
+ manifestHash: security.manifestHash,
341
+ channelNonce: security.channelNonce,
342
+ previewToken: security.previewToken,
343
+ messageId: `${source === HOST_SOURCE ? "host" : "preview"}:${sequence}`,
344
+ sequence,
345
+ type,
346
+ ...payload !== void 0 ? { payload } : {}
347
+ };
348
+ }
349
+ function postEnvelope(target, targetOrigin, envelope) {
350
+ const signature = jsonSignature(envelope);
351
+ if (signature === null || new TextEncoder().encode(signature).byteLength > HYDROGEN_EDITOR_BRIDGE_LIMITS.maxMessageBytes) {
352
+ throw new HydrogenEditorConfigurationError("outbound editor message exceeds protocol bounds");
353
+ }
354
+ target.postMessage(envelope, targetOrigin);
355
+ }
356
+ function findingsForInstance(manifest, instance, component) {
357
+ const anchors = /* @__PURE__ */ new Set([instance.source.anchorId, component.source.anchorId]);
358
+ return manifest.unsupportedFindings.filter((finding) => {
359
+ switch (finding.target.kind) {
360
+ case "manifest":
361
+ return true;
362
+ case "component":
363
+ return finding.target.componentId === component.componentId;
364
+ case "instance":
365
+ return finding.target.instanceId === instance.instanceId;
366
+ case "source":
367
+ return anchors.has(finding.target.anchorId);
368
+ }
369
+ }).map((finding) => ({
370
+ code: finding.code,
371
+ severity: finding.severity,
372
+ message: finding.message,
373
+ ...finding.capability !== void 0 ? { capability: finding.capability } : {},
374
+ ...finding.source !== void 0 ? { source: finding.source } : {}
375
+ }));
376
+ }
377
+ function instanceIdsForDocument(instances, documentId) {
378
+ return [...instances.values()].filter((instance) => instance.documentId === documentId).sort((left, right) => left.instanceId.localeCompare(right.instanceId)).map((instance) => instance.instanceId);
379
+ }
380
+ function childrenForSlot(instances, parentInstanceId, slotId) {
381
+ return [...instances.values()].filter(
382
+ (instance) => instance.parent?.instanceId === parentInstanceId && instance.parent.slotId === slotId
383
+ ).sort((left, right) => (left.parent?.index ?? 0) - (right.parent?.index ?? 0));
384
+ }
385
+ function resolvedProps(instance, component) {
386
+ const variant = component.variants.find((candidate) => candidate.variantId === instance.variantId);
387
+ const defaults = Object.fromEntries(
388
+ component.props.flatMap(
389
+ (control) => control.defaultBinding === void 0 ? [] : [[control.propName, control.defaultBinding]]
390
+ )
391
+ );
392
+ return Object.fromEntries(
393
+ Object.entries({ ...defaults, ...instance.props, ...variant?.props }).flatMap(
394
+ ([propName, binding]) => binding.kind === "inline" ? [[propName, binding.value]] : []
395
+ )
396
+ );
397
+ }
398
+ function bindingMatchesControl(binding, control) {
399
+ if (!control.allowedBindings.includes(binding.kind)) return false;
400
+ if (binding.kind === "external") return true;
401
+ const value = binding.value;
402
+ switch (control.control) {
403
+ case "number":
404
+ return typeof value === "number" && Number.isFinite(value) && (control.min === void 0 || value >= control.min) && (control.max === void 0 || value <= control.max);
405
+ case "boolean":
406
+ return typeof value === "boolean";
407
+ case "select":
408
+ return typeof value === "string" && control.options.some((option) => option.value === value);
409
+ case "text":
410
+ case "textarea":
411
+ case "richtext":
412
+ case "color":
413
+ case "image":
414
+ case "url":
415
+ case "product":
416
+ case "collection":
417
+ return typeof value === "string";
418
+ }
419
+ }
420
+ function styleValueAllowed(value, allowedValues) {
421
+ return (typeof value === "string" || typeof value === "number" && Number.isFinite(value)) && (allowedValues === void 0 || allowedValues.includes(String(value)));
422
+ }
423
+ function instanceMatchesComponentContract(manifest, instance, component) {
424
+ const controls = new Map(component.props.map((control) => [control.propName, control]));
425
+ for (const [propName, binding] of Object.entries(instance.props)) {
426
+ const control = controls.get(propName);
427
+ if (control === void 0 || !bindingMatchesControl(binding, control)) return false;
428
+ }
429
+ if (component.props.some(
430
+ (control) => control.required && instance.props[control.propName] === void 0 && control.defaultBinding === void 0
431
+ )) {
432
+ return false;
433
+ }
434
+ if (instance.variantId !== void 0 && !component.variants.some((variant) => variant.variantId === instance.variantId)) {
435
+ return false;
436
+ }
437
+ const tokenControls = new Map(component.styles.tokens.map((token) => [token.tokenId, token]));
438
+ const layoutControls = new Map(
439
+ component.styles.layout.map((control) => [control.controlId, control])
440
+ );
441
+ const breakpoints = new Set(manifest.breakpoints.map((breakpoint) => breakpoint.breakpointId));
442
+ const overrideIds = /* @__PURE__ */ new Set();
443
+ for (const override of instance.styles.overrides) {
444
+ if (!component.styles.responsive || !breakpoints.has(override.breakpointId) || overrideIds.has(override.breakpointId)) {
445
+ return false;
446
+ }
447
+ overrideIds.add(override.breakpointId);
448
+ }
449
+ for (const values of [
450
+ instance.styles.base,
451
+ ...instance.styles.overrides.map((override) => override.values)
452
+ ]) {
453
+ for (const [tokenId, value] of Object.entries(values.tokens)) {
454
+ const control = tokenControls.get(tokenId);
455
+ if (control === void 0 || !styleValueAllowed(value, control.allowedValues)) return false;
456
+ }
457
+ for (const [controlId, value] of Object.entries(values.layout)) {
458
+ const control = layoutControls.get(controlId);
459
+ if (control === void 0 || !styleValueAllowed(value, control.allowedValues)) return false;
460
+ }
461
+ }
462
+ return true;
463
+ }
464
+ function cloneManifest(manifest) {
465
+ return HydrogenEditorManifestSchema.parse(manifest);
466
+ }
467
+ var HydrogenEditorPreviewStoreImpl = class {
468
+ manifest;
469
+ components;
470
+ documents;
471
+ instances = /* @__PURE__ */ new Map();
472
+ mounted = /* @__PURE__ */ new Set();
473
+ operations = /* @__PURE__ */ new Map();
474
+ listeners = /* @__PURE__ */ new Set();
475
+ instanceListeners = /* @__PURE__ */ new Map();
476
+ snapshotCache = null;
477
+ instanceSnapshotCache = /* @__PURE__ */ new Map();
478
+ documentId;
479
+ route;
480
+ revision = 0;
481
+ selectedInstanceId = null;
482
+ hoveredInstanceId = null;
483
+ constructor(manifest, documentId, route) {
484
+ this.manifest = cloneManifest(manifest);
485
+ this.components = new Map(
486
+ this.manifest.components.map((component) => [component.componentId, component])
487
+ );
488
+ this.documents = new Map(
489
+ this.manifest.documents.map((document) => [document.documentId, document])
490
+ );
491
+ for (const instance of this.manifest.instances)
492
+ this.instances.set(instance.instanceId, instance);
493
+ if (!this.documents.has(documentId)) {
494
+ throw new HydrogenEditorConfigurationError(`unknown Hydrogen document "${documentId}"`);
495
+ }
496
+ this.documentId = documentId;
497
+ this.route = route;
498
+ }
499
+ getSnapshot() {
500
+ if (this.snapshotCache !== null) return this.snapshotCache;
501
+ const document = this.documents.get(this.documentId);
502
+ if (document === void 0) {
503
+ throw new HydrogenEditorConfigurationError(`unknown Hydrogen document "${this.documentId}"`);
504
+ }
505
+ const instanceIds = instanceIdsForDocument(this.instances, this.documentId);
506
+ const selected = this.selectedInstanceId;
507
+ const selectedInstance = selected === null ? void 0 : this.instances.get(selected);
508
+ this.snapshotCache = {
509
+ document,
510
+ ...this.route !== void 0 ? { route: this.route } : {},
511
+ revision: this.revision,
512
+ selectedInstanceId: selected,
513
+ hoveredInstanceId: this.hoveredInstanceId,
514
+ selectionStatus: selected === null ? "none" : selectedInstance?.documentId === this.documentId ? "active" : "out-of-route",
515
+ instanceIds,
516
+ mountedInstanceIds: instanceIds.filter((instanceId) => this.mounted.has(instanceId))
517
+ };
518
+ return this.snapshotCache;
519
+ }
520
+ getInstanceSnapshot(instanceId) {
521
+ if (this.instanceSnapshotCache.has(instanceId)) {
522
+ return this.instanceSnapshotCache.get(instanceId) ?? null;
523
+ }
524
+ const instance = this.instances.get(instanceId);
525
+ const component = instance === void 0 ? void 0 : this.components.get(instance.componentId);
526
+ if (instance === void 0 || component === void 0) {
527
+ this.instanceSnapshotCache.set(instanceId, null);
528
+ return null;
529
+ }
530
+ const childInstanceIds = Object.fromEntries(
531
+ component.slots.map((slot) => [
532
+ slot.slotId,
533
+ childrenForSlot(this.instances, instanceId, slot.slotId).map((child) => child.instanceId)
534
+ ])
535
+ );
536
+ const capabilityReasons = findingsForInstance(this.manifest, instance, component);
537
+ const snapshot = {
538
+ instance,
539
+ component,
540
+ childInstanceIds,
541
+ resolvedProps: resolvedProps(instance, component),
542
+ mounted: this.mounted.has(instanceId),
543
+ editable: component.capabilities.length > 0 && !capabilityReasons.some((reason) => reason.severity === "blocking"),
544
+ capabilityReasons
545
+ };
546
+ this.instanceSnapshotCache.set(instanceId, snapshot);
547
+ return snapshot;
548
+ }
549
+ subscribe(listener) {
550
+ this.listeners.add(listener);
551
+ return () => this.listeners.delete(listener);
552
+ }
553
+ subscribeInstance(instanceId, listener) {
554
+ const listeners = this.instanceListeners.get(instanceId) ?? /* @__PURE__ */ new Set();
555
+ listeners.add(listener);
556
+ this.instanceListeners.set(instanceId, listeners);
557
+ return () => {
558
+ listeners.delete(listener);
559
+ if (listeners.size === 0) this.instanceListeners.delete(instanceId);
560
+ };
561
+ }
562
+ setMounted(instanceId, mounted) {
563
+ if (!this.instances.has(instanceId)) return;
564
+ const changed = mounted ? !this.mounted.has(instanceId) : this.mounted.has(instanceId);
565
+ if (!changed) return;
566
+ if (mounted) this.mounted.add(instanceId);
567
+ else this.mounted.delete(instanceId);
568
+ this.emit(/* @__PURE__ */ new Set([instanceId]));
569
+ }
570
+ setSelection(instanceId) {
571
+ if (instanceId !== null && !this.instances.has(instanceId)) return false;
572
+ if (this.selectedInstanceId === instanceId) return true;
573
+ this.selectedInstanceId = instanceId;
574
+ this.emit(/* @__PURE__ */ new Set());
575
+ return true;
576
+ }
577
+ setHover(instanceId) {
578
+ if (instanceId !== null && !this.instances.has(instanceId)) return false;
579
+ if (this.hoveredInstanceId === instanceId) return true;
580
+ this.hoveredInstanceId = instanceId;
581
+ this.emit(/* @__PURE__ */ new Set());
582
+ return true;
583
+ }
584
+ reconcile(documentId, route) {
585
+ if (!this.documents.has(documentId)) {
586
+ throw new HydrogenEditorConfigurationError(`unknown Hydrogen document "${documentId}"`);
587
+ }
588
+ this.documentId = documentId;
589
+ this.route = route;
590
+ if (this.hoveredInstanceId !== null && this.instances.get(this.hoveredInstanceId)?.documentId !== documentId) {
591
+ this.hoveredInstanceId = null;
592
+ }
593
+ this.emit(/* @__PURE__ */ new Set());
594
+ }
595
+ emit(changedInstanceIds) {
596
+ this.snapshotCache = null;
597
+ for (const instanceId of changedInstanceIds) {
598
+ this.instanceSnapshotCache.delete(instanceId);
599
+ for (const listener of this.instanceListeners.get(instanceId) ?? []) listener();
600
+ }
601
+ for (const listener of this.listeners) listener();
602
+ }
603
+ componentFor(instance) {
604
+ const component = this.components.get(instance.componentId);
605
+ return component ?? operationFailure(
606
+ "COMPONENT_NOT_FOUND",
607
+ `component "${instance.componentId}" is not declared`,
608
+ instance
609
+ );
610
+ }
611
+ rejectUnsupported(instance, capability) {
612
+ const component = this.components.get(instance.componentId);
613
+ if (component === void 0) {
614
+ return operationFailure(
615
+ "COMPONENT_NOT_FOUND",
616
+ `component "${instance.componentId}" is not declared`,
617
+ instance
618
+ );
619
+ }
620
+ const blocking = findingsForInstance(this.manifest, instance, component).find(
621
+ (finding) => finding.severity === "blocking" && (finding.capability === void 0 || finding.capability === capability)
622
+ );
623
+ return blocking === void 0 ? null : operationFailure(
624
+ "UNSUPPORTED_BOUNDARY",
625
+ blocking.message,
626
+ instance,
627
+ blocking.capability ?? capability
628
+ );
629
+ }
630
+ requireCapability(instance, capability) {
631
+ const component = this.componentFor(instance);
632
+ if ("code" in component) return component;
633
+ if (!component.capabilities.includes(capability)) {
634
+ return operationFailure(
635
+ "CAPABILITY_DENIED",
636
+ `component "${component.componentId}" does not allow "${capability}" edits`,
637
+ instance,
638
+ capability
639
+ );
640
+ }
641
+ return this.rejectUnsupported(instance, capability);
642
+ }
643
+ validateStyles(instance, component, styles) {
644
+ const capabilityFailure = this.requireCapability(instance, "styles") ?? (styles.overrides.length > 0 ? this.requireCapability(instance, "responsive") : null);
645
+ if (capabilityFailure !== null) return capabilityFailure;
646
+ const tokenControls = new Map(component.styles.tokens.map((token) => [token.tokenId, token]));
647
+ const layoutControls = new Map(
648
+ component.styles.layout.map((control) => [control.controlId, control])
649
+ );
650
+ const breakpoints = new Set(this.manifest.breakpoints.map((item) => item.breakpointId));
651
+ const overrideIds = /* @__PURE__ */ new Set();
652
+ for (const override of styles.overrides) {
653
+ if (overrideIds.has(override.breakpointId) || !breakpoints.has(override.breakpointId)) {
654
+ return operationFailure(
655
+ "CAPABILITY_DENIED",
656
+ `responsive breakpoint "${override.breakpointId}" is unknown or duplicated`,
657
+ instance,
658
+ "responsive"
659
+ );
660
+ }
661
+ overrideIds.add(override.breakpointId);
662
+ }
663
+ const values = [styles.base, ...styles.overrides.map((override) => override.values)];
664
+ for (const value of values) {
665
+ for (const [tokenId, tokenValue] of Object.entries(value.tokens)) {
666
+ const control = tokenControls.get(tokenId);
667
+ if (control === void 0 || !styleValueAllowed(tokenValue, control.allowedValues)) {
668
+ return operationFailure(
669
+ "CAPABILITY_DENIED",
670
+ `style token "${tokenId}" is undeclared or outside its allowed values`,
671
+ instance,
672
+ "styles"
673
+ );
674
+ }
675
+ }
676
+ for (const [controlId, layoutValue] of Object.entries(value.layout)) {
677
+ const control = layoutControls.get(controlId);
678
+ if (control === void 0 || !styleValueAllowed(layoutValue, control.allowedValues)) {
679
+ return operationFailure(
680
+ "CAPABILITY_DENIED",
681
+ `layout control "${controlId}" is undeclared or outside its allowed values`,
682
+ instance,
683
+ "styles"
684
+ );
685
+ }
686
+ }
687
+ }
688
+ return null;
689
+ }
690
+ validateInsertedInstance(instance, component) {
691
+ const propControls = new Map(component.props.map((control) => [control.propName, control]));
692
+ for (const [propName, binding] of Object.entries(instance.props)) {
693
+ const control = propControls.get(propName);
694
+ const contentCapability = binding.kind === "external" ? "external-content" : "inline-content";
695
+ const capabilityFailure = this.requireCapability(instance, "props") ?? this.requireCapability(instance, contentCapability);
696
+ if (control === void 0 || !bindingMatchesControl(binding, control) || capabilityFailure !== null) {
697
+ return capabilityFailure ?? operationFailure(
698
+ "CAPABILITY_DENIED",
699
+ `inserted prop "${propName}" is not declared or violates its control`,
700
+ instance,
701
+ contentCapability
702
+ );
703
+ }
704
+ }
705
+ for (const control of component.props) {
706
+ if (control.required && instance.props[control.propName] === void 0 && control.defaultBinding === void 0) {
707
+ return operationFailure(
708
+ "CAPABILITY_DENIED",
709
+ `inserted instance is missing required prop "${control.propName}"`,
710
+ instance,
711
+ "props"
712
+ );
713
+ }
714
+ }
715
+ if (instance.variantId !== void 0 && !component.variants.some((variant) => variant.variantId === instance.variantId)) {
716
+ return operationFailure(
717
+ "CAPABILITY_DENIED",
718
+ `variant "${instance.variantId}" is not declared`,
719
+ instance,
720
+ "variants"
721
+ );
722
+ }
723
+ const styleValuesPresent = Object.keys(instance.styles.base.tokens).length > 0 || Object.keys(instance.styles.base.layout).length > 0 || instance.styles.overrides.length > 0;
724
+ if (styleValuesPresent) {
725
+ const styleFailure = this.validateStyles(instance, component, instance.styles);
726
+ if (styleFailure !== null) return styleFailure;
727
+ }
728
+ const sourceAnchorCollision = this.manifest.components.some(
729
+ (candidate) => candidate.source.anchorId === instance.source.anchorId
730
+ ) || [...this.instances.values()].some(
731
+ (candidate) => candidate.source.anchorId === instance.source.anchorId
732
+ );
733
+ if (sourceAnchorCollision) {
734
+ return operationFailure(
735
+ "INVALID_OPERATION",
736
+ `source anchor "${instance.source.anchorId}" is already in use`,
737
+ instance,
738
+ "insert"
739
+ );
740
+ }
741
+ return this.rejectUnsupported(instance, "insert");
742
+ }
743
+ normalizeSlot(parentInstanceId, slotId) {
744
+ const changed = /* @__PURE__ */ new Set([parentInstanceId]);
745
+ for (const [index, child] of childrenForSlot(
746
+ this.instances,
747
+ parentInstanceId,
748
+ slotId
749
+ ).entries()) {
750
+ if (child.parent?.index === index) continue;
751
+ this.instances.set(child.instanceId, {
752
+ ...child,
753
+ parent: { instanceId: parentInstanceId, slotId, index }
754
+ });
755
+ changed.add(child.instanceId);
756
+ }
757
+ return changed;
758
+ }
759
+ insertAt(instance, parentInstanceId, slotId, index) {
760
+ const siblings = childrenForSlot(this.instances, parentInstanceId, slotId);
761
+ const boundedIndex = Math.min(index, siblings.length);
762
+ for (const sibling of siblings) {
763
+ const siblingIndex = sibling.parent?.index ?? 0;
764
+ if (siblingIndex < boundedIndex) continue;
765
+ this.instances.set(sibling.instanceId, {
766
+ ...sibling,
767
+ parent: { instanceId: parentInstanceId, slotId, index: siblingIndex + 1 }
768
+ });
769
+ }
770
+ this.instances.set(instance.instanceId, {
771
+ ...instance,
772
+ parent: { instanceId: parentInstanceId, slotId, index: boundedIndex }
773
+ });
774
+ return /* @__PURE__ */ new Set([
775
+ instance.instanceId,
776
+ parentInstanceId,
777
+ ...siblings.map((item) => item.instanceId)
778
+ ]);
779
+ }
780
+ validateSlotTarget(parent, slotId, childComponentId, mode) {
781
+ const capability = mode === "insert" ? "insert" : "reorder";
782
+ const capabilityFailure = this.requireCapability(parent, capability);
783
+ if (capabilityFailure !== null) return capabilityFailure;
784
+ const component = this.components.get(parent.componentId);
785
+ const slot = component?.slots.find((candidate) => candidate.slotId === slotId);
786
+ if (slot === void 0) {
787
+ return operationFailure(
788
+ "SLOT_CONSTRAINT",
789
+ `component "${parent.componentId}" has no slot "${slotId}"`,
790
+ parent,
791
+ capability
792
+ );
793
+ }
794
+ if (slot.insertion === "fixed" || mode === "reorder" && slot.insertion !== "reorder" || slot.accepts.mode === "include" && !slot.accepts.componentIds.includes(childComponentId)) {
795
+ return operationFailure(
796
+ "SLOT_CONSTRAINT",
797
+ `slot "${slotId}" does not accept this ${mode} operation`,
798
+ parent,
799
+ capability
800
+ );
801
+ }
802
+ return null;
803
+ }
804
+ applyValidatedOperation(operation) {
805
+ switch (operation.type) {
806
+ case "prop.patch": {
807
+ const instance = this.instances.get(operation.instanceId);
808
+ if (instance === void 0) {
809
+ return {
810
+ changedInstanceIds: /* @__PURE__ */ new Set(),
811
+ result: rejectOperation(
812
+ operation,
813
+ this.revision,
814
+ operationFailure(
815
+ "INSTANCE_NOT_FOUND",
816
+ `instance "${operation.instanceId}" is not declared`
817
+ )
818
+ )
819
+ };
820
+ }
821
+ const component = this.components.get(instance.componentId);
822
+ const control = component?.props.find((prop) => prop.propName === operation.propName);
823
+ const requiredCapability = operation.binding.kind === "external" ? "external-content" : "inline-content";
824
+ const capabilityFailure = this.requireCapability(instance, "props") ?? this.requireCapability(instance, requiredCapability);
825
+ if (capabilityFailure !== null || control === void 0 || !bindingMatchesControl(operation.binding, control)) {
826
+ return {
827
+ changedInstanceIds: /* @__PURE__ */ new Set(),
828
+ result: rejectOperation(
829
+ operation,
830
+ this.revision,
831
+ capabilityFailure ?? operationFailure(
832
+ "CAPABILITY_DENIED",
833
+ `prop "${operation.propName}" does not allow "${operation.binding.kind}" content`,
834
+ instance,
835
+ requiredCapability
836
+ )
837
+ )
838
+ };
839
+ }
840
+ this.instances.set(instance.instanceId, {
841
+ ...instance,
842
+ props: { ...instance.props, [operation.propName]: operation.binding }
843
+ });
844
+ return {
845
+ changedInstanceIds: /* @__PURE__ */ new Set([instance.instanceId]),
846
+ result: {
847
+ status: "applied",
848
+ revision: operation.revision,
849
+ operationId: operation.operationId
850
+ }
851
+ };
852
+ }
853
+ case "styles.patch": {
854
+ const instance = this.instances.get(operation.instanceId);
855
+ if (instance === void 0) {
856
+ return {
857
+ changedInstanceIds: /* @__PURE__ */ new Set(),
858
+ result: rejectOperation(
859
+ operation,
860
+ this.revision,
861
+ operationFailure(
862
+ "INSTANCE_NOT_FOUND",
863
+ `instance "${operation.instanceId}" is not declared`
864
+ )
865
+ )
866
+ };
867
+ }
868
+ const component = this.components.get(instance.componentId);
869
+ const capabilityFailure = component === void 0 ? operationFailure(
870
+ "COMPONENT_NOT_FOUND",
871
+ `component "${instance.componentId}" is not declared`,
872
+ instance
873
+ ) : this.validateStyles(instance, component, operation.styles);
874
+ if (capabilityFailure !== null) {
875
+ return {
876
+ changedInstanceIds: /* @__PURE__ */ new Set(),
877
+ result: rejectOperation(operation, this.revision, capabilityFailure)
878
+ };
879
+ }
880
+ this.instances.set(instance.instanceId, { ...instance, styles: operation.styles });
881
+ return {
882
+ changedInstanceIds: /* @__PURE__ */ new Set([instance.instanceId]),
883
+ result: {
884
+ status: "applied",
885
+ revision: operation.revision,
886
+ operationId: operation.operationId
887
+ }
888
+ };
889
+ }
890
+ case "variant.set": {
891
+ const instance = this.instances.get(operation.instanceId);
892
+ if (instance === void 0) {
893
+ return {
894
+ changedInstanceIds: /* @__PURE__ */ new Set(),
895
+ result: rejectOperation(
896
+ operation,
897
+ this.revision,
898
+ operationFailure(
899
+ "INSTANCE_NOT_FOUND",
900
+ `instance "${operation.instanceId}" is not declared`
901
+ )
902
+ )
903
+ };
904
+ }
905
+ const capabilityFailure = this.requireCapability(instance, "variants");
906
+ const component = this.components.get(instance.componentId);
907
+ if (capabilityFailure !== null || operation.variantId !== null && !component?.variants.some((variant) => variant.variantId === operation.variantId)) {
908
+ return {
909
+ changedInstanceIds: /* @__PURE__ */ new Set(),
910
+ result: rejectOperation(
911
+ operation,
912
+ this.revision,
913
+ capabilityFailure ?? operationFailure(
914
+ "CAPABILITY_DENIED",
915
+ `variant "${operation.variantId}" is not declared`,
916
+ instance,
917
+ "variants"
918
+ )
919
+ )
920
+ };
921
+ }
922
+ const next = operation.variantId === null ? (({ variantId: _variantId, ...rest }) => rest)(instance) : { ...instance, variantId: operation.variantId };
923
+ this.instances.set(instance.instanceId, next);
924
+ return {
925
+ changedInstanceIds: /* @__PURE__ */ new Set([instance.instanceId]),
926
+ result: {
927
+ status: "applied",
928
+ revision: operation.revision,
929
+ operationId: operation.operationId
930
+ }
931
+ };
932
+ }
933
+ case "slot.insert": {
934
+ const parent = this.instances.get(operation.parentInstanceId);
935
+ const component = this.components.get(operation.instance.componentId);
936
+ if (parent === void 0 || component === void 0) {
937
+ return {
938
+ changedInstanceIds: /* @__PURE__ */ new Set(),
939
+ result: rejectOperation(
940
+ operation,
941
+ this.revision,
942
+ parent === void 0 ? operationFailure(
943
+ "INSTANCE_NOT_FOUND",
944
+ `parent instance "${operation.parentInstanceId}" is not declared`
945
+ ) : operationFailure(
946
+ "COMPONENT_NOT_FOUND",
947
+ `component "${operation.instance.componentId}" is not declared`
948
+ )
949
+ )
950
+ };
951
+ }
952
+ const documentInstanceCount = [...this.instances.values()].filter(
953
+ (instance) => instance.documentId === parent.documentId
954
+ ).length;
955
+ let parentDepth = 1;
956
+ let ancestor = parent;
957
+ while (ancestor.parent !== null) {
958
+ parentDepth += 1;
959
+ ancestor = this.instances.get(ancestor.parent.instanceId);
960
+ if (ancestor === void 0) break;
961
+ }
962
+ if (this.instances.has(operation.instance.instanceId) || operation.instance.documentId !== parent.documentId || this.instances.size >= HYDROGEN_EDITOR_MANIFEST_LIMITS.maxInstances || documentInstanceCount >= HYDROGEN_EDITOR_MANIFEST_LIMITS.maxInstancesPerDocument || parentDepth >= HYDROGEN_EDITOR_MANIFEST_LIMITS.maxDepth) {
963
+ return {
964
+ changedInstanceIds: /* @__PURE__ */ new Set(),
965
+ result: rejectOperation(
966
+ operation,
967
+ this.revision,
968
+ operationFailure(
969
+ "DOCUMENT_MISMATCH",
970
+ "inserted instance identity is duplicate, cross-document, or exceeds manifest limits",
971
+ parent,
972
+ "insert"
973
+ )
974
+ )
975
+ };
976
+ }
977
+ const targetFailure = this.validateSlotTarget(
978
+ parent,
979
+ operation.slotId,
980
+ operation.instance.componentId,
981
+ "insert"
982
+ );
983
+ const slot = this.components.get(parent.componentId)?.slots.find((candidate) => candidate.slotId === operation.slotId);
984
+ if (targetFailure !== null || slot === void 0 || childrenForSlot(this.instances, parent.instanceId, operation.slotId).length >= slot.maxInstances) {
985
+ return {
986
+ changedInstanceIds: /* @__PURE__ */ new Set(),
987
+ result: rejectOperation(
988
+ operation,
989
+ this.revision,
990
+ targetFailure ?? operationFailure(
991
+ "SLOT_CONSTRAINT",
992
+ `slot "${operation.slotId}" reached its instance limit`,
993
+ parent,
994
+ "insert"
995
+ )
996
+ )
997
+ };
998
+ }
999
+ const parsed = HydrogenInstanceSchema.safeParse({
1000
+ ...operation.instance,
1001
+ parent: {
1002
+ instanceId: parent.instanceId,
1003
+ slotId: operation.slotId,
1004
+ index: operation.index
1005
+ }
1006
+ });
1007
+ if (!parsed.success) {
1008
+ return {
1009
+ changedInstanceIds: /* @__PURE__ */ new Set(),
1010
+ result: rejectOperation(
1011
+ operation,
1012
+ this.revision,
1013
+ operationFailure(
1014
+ "INVALID_OPERATION",
1015
+ "inserted instance does not satisfy the Hydrogen instance contract",
1016
+ parent,
1017
+ "insert"
1018
+ )
1019
+ )
1020
+ };
1021
+ }
1022
+ const insertedFailure = this.validateInsertedInstance(parsed.data, component);
1023
+ if (insertedFailure !== null) {
1024
+ return {
1025
+ changedInstanceIds: /* @__PURE__ */ new Set(),
1026
+ result: rejectOperation(operation, this.revision, insertedFailure)
1027
+ };
1028
+ }
1029
+ return {
1030
+ changedInstanceIds: this.insertAt(
1031
+ parsed.data,
1032
+ parent.instanceId,
1033
+ operation.slotId,
1034
+ operation.index
1035
+ ),
1036
+ result: {
1037
+ status: "applied",
1038
+ revision: operation.revision,
1039
+ operationId: operation.operationId
1040
+ }
1041
+ };
1042
+ }
1043
+ case "instance.move": {
1044
+ const instance = this.instances.get(operation.instanceId);
1045
+ const parent = this.instances.get(operation.parentInstanceId);
1046
+ if (instance === void 0 || parent === void 0 || instance.parent === null) {
1047
+ return {
1048
+ changedInstanceIds: /* @__PURE__ */ new Set(),
1049
+ result: rejectOperation(
1050
+ operation,
1051
+ this.revision,
1052
+ operationFailure(
1053
+ "INSTANCE_NOT_FOUND",
1054
+ "move requires an existing non-root instance and parent",
1055
+ instance,
1056
+ "reorder"
1057
+ )
1058
+ )
1059
+ };
1060
+ }
1061
+ const targetFailure = this.validateSlotTarget(
1062
+ parent,
1063
+ operation.slotId,
1064
+ instance.componentId,
1065
+ "reorder"
1066
+ );
1067
+ const oldParent = this.instances.get(instance.parent.instanceId);
1068
+ const oldSlot = this.components.get(oldParent?.componentId ?? "")?.slots.find((slot) => slot.slotId === instance.parent?.slotId);
1069
+ const oldTargetFailure = oldParent === void 0 || oldSlot === void 0 ? null : this.validateSlotTarget(oldParent, oldSlot.slotId, instance.componentId, "reorder");
1070
+ const targetSlot = this.components.get(parent.componentId)?.slots.find((slot) => slot.slotId === operation.slotId);
1071
+ const movingWithinSlot = instance.parent.instanceId === parent.instanceId && instance.parent.slotId === operation.slotId;
1072
+ const targetCount = childrenForSlot(
1073
+ this.instances,
1074
+ parent.instanceId,
1075
+ operation.slotId
1076
+ ).length;
1077
+ let cursor = parent;
1078
+ let createsCycle = false;
1079
+ while (cursor?.parent !== null && cursor?.parent !== void 0) {
1080
+ if (cursor.instanceId === instance.instanceId) {
1081
+ createsCycle = true;
1082
+ break;
1083
+ }
1084
+ cursor = this.instances.get(cursor.parent.instanceId);
1085
+ }
1086
+ if (targetFailure !== null || oldTargetFailure !== null || oldParent === void 0 || oldSlot === void 0 || targetSlot === void 0 || !movingWithinSlot && targetCount >= targetSlot.maxInstances || !movingWithinSlot && childrenForSlot(this.instances, oldParent.instanceId, oldSlot.slotId).length <= oldSlot.minInstances || createsCycle || parent.documentId !== instance.documentId) {
1087
+ return {
1088
+ changedInstanceIds: /* @__PURE__ */ new Set(),
1089
+ result: rejectOperation(
1090
+ operation,
1091
+ this.revision,
1092
+ targetFailure ?? oldTargetFailure ?? operationFailure(
1093
+ "SLOT_CONSTRAINT",
1094
+ "move violates slot cardinality, document, or tree constraints",
1095
+ instance,
1096
+ "reorder"
1097
+ )
1098
+ )
1099
+ };
1100
+ }
1101
+ this.instances.delete(instance.instanceId);
1102
+ const changed = this.normalizeSlot(oldParent.instanceId, oldSlot.slotId);
1103
+ const inserted = this.insertAt(
1104
+ instance,
1105
+ parent.instanceId,
1106
+ operation.slotId,
1107
+ operation.index
1108
+ );
1109
+ for (const id of inserted) changed.add(id);
1110
+ return {
1111
+ changedInstanceIds: changed,
1112
+ result: {
1113
+ status: "applied",
1114
+ revision: operation.revision,
1115
+ operationId: operation.operationId
1116
+ }
1117
+ };
1118
+ }
1119
+ case "instance.remove": {
1120
+ const instance = this.instances.get(operation.instanceId);
1121
+ if (instance === void 0 || instance.parent === null) {
1122
+ return {
1123
+ changedInstanceIds: /* @__PURE__ */ new Set(),
1124
+ result: rejectOperation(
1125
+ operation,
1126
+ this.revision,
1127
+ operationFailure(
1128
+ "INSTANCE_NOT_FOUND",
1129
+ "remove requires an existing non-root instance",
1130
+ instance,
1131
+ "remove"
1132
+ )
1133
+ )
1134
+ };
1135
+ }
1136
+ const capabilityFailure = this.requireCapability(instance, "remove");
1137
+ const parent = this.instances.get(instance.parent.instanceId);
1138
+ const slot = this.components.get(parent?.componentId ?? "")?.slots.find((candidate) => candidate.slotId === instance.parent?.slotId);
1139
+ const siblings = parent === void 0 || slot === void 0 ? [] : childrenForSlot(this.instances, parent.instanceId, slot.slotId);
1140
+ if (capabilityFailure !== null || parent === void 0 || slot === void 0 || !slot.removableChildren || siblings.length <= slot.minInstances) {
1141
+ return {
1142
+ changedInstanceIds: /* @__PURE__ */ new Set(),
1143
+ result: rejectOperation(
1144
+ operation,
1145
+ this.revision,
1146
+ capabilityFailure ?? operationFailure(
1147
+ "SLOT_CONSTRAINT",
1148
+ "instance cannot be removed from this slot",
1149
+ instance,
1150
+ "remove"
1151
+ )
1152
+ )
1153
+ };
1154
+ }
1155
+ const removeIds = /* @__PURE__ */ new Set();
1156
+ const collect = (instanceId) => {
1157
+ if (removeIds.has(instanceId)) return;
1158
+ removeIds.add(instanceId);
1159
+ for (const candidate of this.instances.values()) {
1160
+ if (candidate.parent?.instanceId === instanceId) collect(candidate.instanceId);
1161
+ }
1162
+ };
1163
+ collect(instance.instanceId);
1164
+ for (const instanceId of removeIds) {
1165
+ this.instances.delete(instanceId);
1166
+ this.mounted.delete(instanceId);
1167
+ }
1168
+ const changed = this.normalizeSlot(parent.instanceId, slot.slotId);
1169
+ for (const instanceId of removeIds) changed.add(instanceId);
1170
+ if (removeIds.has(this.selectedInstanceId ?? "")) this.selectedInstanceId = null;
1171
+ if (removeIds.has(this.hoveredInstanceId ?? "")) this.hoveredInstanceId = null;
1172
+ return {
1173
+ changedInstanceIds: changed,
1174
+ result: {
1175
+ status: "applied",
1176
+ revision: operation.revision,
1177
+ operationId: operation.operationId
1178
+ }
1179
+ };
1180
+ }
1181
+ }
1182
+ }
1183
+ apply(operation) {
1184
+ const signature = jsonSignature(operation);
1185
+ if (signature === null) {
1186
+ return rejectOperation(
1187
+ operation,
1188
+ this.revision,
1189
+ operationFailure("INVALID_OPERATION", "operation is not bounded JSON")
1190
+ );
1191
+ }
1192
+ const previous = this.operations.get(operation.operationId);
1193
+ if (previous !== void 0) {
1194
+ return previous.signature === signature ? previous.result : rejectOperation(
1195
+ operation,
1196
+ this.revision,
1197
+ operationFailure(
1198
+ "DUPLICATE_OPERATION_CONFLICT",
1199
+ "operationId was reused with a different operation"
1200
+ )
1201
+ );
1202
+ }
1203
+ if (this.operations.size >= HYDROGEN_EDITOR_BRIDGE_LIMITS.maxDraftOperations) {
1204
+ return rejectOperation(
1205
+ operation,
1206
+ this.revision,
1207
+ operationFailure("OPERATION_LIMIT_EXCEEDED", "draft operation limit reached")
1208
+ );
1209
+ }
1210
+ if (operation.revision !== this.revision + 1) {
1211
+ const result = rejectOperation(
1212
+ operation,
1213
+ this.revision,
1214
+ operationFailure(
1215
+ "REVISION_MISMATCH",
1216
+ `expected preview revision ${this.revision + 1}, received ${operation.revision}`
1217
+ )
1218
+ );
1219
+ this.operations.set(operation.operationId, { signature, result });
1220
+ return result;
1221
+ }
1222
+ const mutation = this.applyValidatedOperation(operation);
1223
+ this.operations.set(operation.operationId, { signature, result: mutation.result });
1224
+ if (mutation.result.status === "applied") {
1225
+ this.revision = operation.revision;
1226
+ this.emit(mutation.changedInstanceIds);
1227
+ }
1228
+ return mutation.result;
1229
+ }
1230
+ };
1231
+ function canvasOperationForIntent(store, intent, operationId) {
1232
+ const revision = store.revision + 1;
1233
+ if (intent.type === "instance.move") {
1234
+ return {
1235
+ type: "instance.move",
1236
+ operationId,
1237
+ revision,
1238
+ instanceId: intent.instanceId,
1239
+ parentInstanceId: intent.parentInstanceId,
1240
+ slotId: intent.slotId,
1241
+ index: intent.index
1242
+ };
1243
+ }
1244
+ const snapshot = store.getInstanceSnapshot(intent.instanceId);
1245
+ if (snapshot === null) {
1246
+ return operationFailure("INSTANCE_NOT_FOUND", `instance "${intent.instanceId}" is not declared`);
1247
+ }
1248
+ if (intent.type === "prop.inline") {
1249
+ const control2 = snapshot.component.props.find(
1250
+ (candidate) => candidate.propName === intent.propName
1251
+ );
1252
+ if (control2 === void 0 || !control2.allowedBindings.includes("inline") || (snapshot.instance.props[intent.propName] ?? control2.defaultBinding)?.kind === "external" || !snapshot.component.capabilities.includes("props") || !snapshot.component.capabilities.includes("inline-content")) {
1253
+ return operationFailure(
1254
+ "CAPABILITY_DENIED",
1255
+ `prop "${intent.propName}" is not an inline-editable canvas control`,
1256
+ snapshot.instance,
1257
+ "inline-content"
1258
+ );
1259
+ }
1260
+ return {
1261
+ type: "prop.patch",
1262
+ operationId,
1263
+ revision,
1264
+ instanceId: intent.instanceId,
1265
+ propName: intent.propName,
1266
+ binding: { kind: "inline", value: intent.value }
1267
+ };
1268
+ }
1269
+ const control = snapshot.component.styles.layout.find(
1270
+ (candidate) => candidate.controlId === intent.controlId
1271
+ );
1272
+ if (control === void 0 || !DIRECT_CANVAS_LAYOUT_CONTROLS.has(control.control) || !snapshot.component.capabilities.includes("styles") || intent.breakpointId !== void 0 && (!snapshot.component.styles.responsive || !snapshot.component.capabilities.includes("responsive"))) {
1273
+ return operationFailure(
1274
+ "CAPABILITY_DENIED",
1275
+ `layout control "${intent.controlId}" is not directly editable on the canvas`,
1276
+ snapshot.instance,
1277
+ intent.breakpointId === void 0 ? "styles" : "responsive"
1278
+ );
1279
+ }
1280
+ const value = `${intent.value}px`;
1281
+ if (control.allowedValues !== void 0 && !control.allowedValues.includes(value)) {
1282
+ return operationFailure(
1283
+ "CAPABILITY_DENIED",
1284
+ `layout value "${value}" is outside the declared allowed values`,
1285
+ snapshot.instance,
1286
+ "styles"
1287
+ );
1288
+ }
1289
+ const styles = HydrogenInstanceStylesSchema.parse(snapshot.instance.styles);
1290
+ if (intent.breakpointId === void 0) {
1291
+ return {
1292
+ type: "styles.patch",
1293
+ operationId,
1294
+ revision,
1295
+ instanceId: intent.instanceId,
1296
+ styles: {
1297
+ ...styles,
1298
+ base: {
1299
+ ...styles.base,
1300
+ layout: { ...styles.base.layout, [intent.controlId]: value }
1301
+ }
1302
+ }
1303
+ };
1304
+ }
1305
+ if (!store.manifest.breakpoints.some(
1306
+ (breakpoint) => breakpoint.breakpointId === intent.breakpointId
1307
+ )) {
1308
+ return operationFailure(
1309
+ "CAPABILITY_DENIED",
1310
+ `responsive breakpoint "${intent.breakpointId}" is unknown`,
1311
+ snapshot.instance,
1312
+ "responsive"
1313
+ );
1314
+ }
1315
+ const existing = styles.overrides.find(
1316
+ (override2) => override2.breakpointId === intent.breakpointId
1317
+ );
1318
+ const override = {
1319
+ breakpointId: intent.breakpointId,
1320
+ values: {
1321
+ tokens: existing?.values.tokens ?? {},
1322
+ layout: { ...existing?.values.layout, [intent.controlId]: value }
1323
+ }
1324
+ };
1325
+ return {
1326
+ type: "styles.patch",
1327
+ operationId,
1328
+ revision,
1329
+ instanceId: intent.instanceId,
1330
+ styles: {
1331
+ ...styles,
1332
+ overrides: [
1333
+ ...styles.overrides.filter((candidate) => candidate.breakpointId !== intent.breakpointId),
1334
+ override
1335
+ ]
1336
+ }
1337
+ };
1338
+ }
1339
+ function validateCanvasInsertRequest(store, request) {
1340
+ const parent = store.getInstanceSnapshot(request.parentInstanceId);
1341
+ if (parent === null) {
1342
+ return operationFailure(
1343
+ "INSTANCE_NOT_FOUND",
1344
+ `parent instance "${request.parentInstanceId}" is not declared`
1345
+ );
1346
+ }
1347
+ const slot = parent.component.slots.find((candidate) => candidate.slotId === request.slotId);
1348
+ const children = parent.childInstanceIds[request.slotId];
1349
+ if (slot === void 0 || slot.insertion === "fixed" || !parent.component.capabilities.includes("insert") || children === void 0 || children.length >= slot.maxInstances || request.index > children.length) {
1350
+ return operationFailure(
1351
+ "SLOT_CONSTRAINT",
1352
+ `slot "${request.slotId}" does not accept an insertion at index ${request.index}`,
1353
+ parent.instance,
1354
+ "insert"
1355
+ );
1356
+ }
1357
+ const blocking = parent.capabilityReasons.find(
1358
+ (reason) => reason.severity === "blocking" && (reason.capability === void 0 || reason.capability === "insert")
1359
+ );
1360
+ return blocking === void 0 ? null : operationFailure(
1361
+ "UNSUPPORTED_BOUNDARY",
1362
+ blocking.message,
1363
+ parent.instance,
1364
+ blocking.capability ?? "insert"
1365
+ );
1366
+ }
1367
+ function readyDocument(store) {
1368
+ const snapshot = store.getSnapshot();
1369
+ const instances = snapshot.instanceIds.flatMap((instanceId) => {
1370
+ const instance = store.getInstanceSnapshot(instanceId);
1371
+ return instance === null ? [] : [
1372
+ {
1373
+ instance: instance.instance,
1374
+ mounted: instance.mounted,
1375
+ editable: instance.editable,
1376
+ capabilityReasons: instance.capabilityReasons
1377
+ }
1378
+ ];
1379
+ });
1380
+ const componentIds = new Set(instances.map(({ instance }) => instance.componentId));
1381
+ let discoveredComponent = true;
1382
+ while (discoveredComponent) {
1383
+ discoveredComponent = false;
1384
+ for (const componentId of [...componentIds]) {
1385
+ const component = store.components.get(componentId);
1386
+ for (const slot of component?.slots ?? []) {
1387
+ const candidates = slot.accepts.mode === "any" ? [...store.components.keys()] : slot.accepts.componentIds;
1388
+ for (const candidate of candidates) {
1389
+ if (componentIds.has(candidate)) continue;
1390
+ componentIds.add(candidate);
1391
+ discoveredComponent = true;
1392
+ }
1393
+ }
1394
+ }
1395
+ }
1396
+ const components = [...componentIds].flatMap((componentId) => {
1397
+ const component = store.components.get(componentId);
1398
+ return component === void 0 ? [] : [component];
1399
+ });
1400
+ const instanceIds = new Set(instances.map(({ instance }) => instance.instanceId));
1401
+ const componentAnchorIds = new Set(components.map((component) => component.source.anchorId));
1402
+ const instanceAnchorIds = new Set(instances.map(({ instance }) => instance.source.anchorId));
1403
+ const unsupportedFindings = store.manifest.unsupportedFindings.filter((finding) => {
1404
+ switch (finding.target.kind) {
1405
+ case "manifest":
1406
+ return true;
1407
+ case "component":
1408
+ return componentIds.has(finding.target.componentId);
1409
+ case "instance":
1410
+ return instanceIds.has(finding.target.instanceId);
1411
+ case "source":
1412
+ return componentAnchorIds.has(finding.target.anchorId) || instanceAnchorIds.has(finding.target.anchorId);
1413
+ }
1414
+ });
1415
+ return {
1416
+ protocolVersion: HYDROGEN_EDITOR_PROTOCOL_VERSION,
1417
+ document: snapshot.document,
1418
+ breakpoints: store.manifest.breakpoints,
1419
+ ...snapshot.route !== void 0 ? { route: snapshot.route } : {},
1420
+ revision: snapshot.revision,
1421
+ selectedInstanceId: snapshot.selectedInstanceId,
1422
+ hoveredInstanceId: snapshot.hoveredInstanceId,
1423
+ selectionStatus: snapshot.selectionStatus,
1424
+ components,
1425
+ instances,
1426
+ unsupportedFindings
1427
+ };
1428
+ }
1429
+ function createHydrogenEditorSourceProjection({
1430
+ manifest,
1431
+ documentId,
1432
+ route,
1433
+ revision = 0,
1434
+ selectedInstanceId
1435
+ }) {
1436
+ if (!isNonnegativeInteger(revision)) {
1437
+ throw new HydrogenEditorConfigurationError("revision must be a nonnegative integer");
1438
+ }
1439
+ const parsedManifest = HydrogenEditorManifestSchema.parse(manifest);
1440
+ const document = parsedManifest.documents.find((candidate) => candidate.documentId === documentId);
1441
+ if (document === void 0) {
1442
+ throw new HydrogenEditorConfigurationError(`unknown Hydrogen document "${documentId}"`);
1443
+ }
1444
+ const store = new HydrogenEditorPreviewStoreImpl(
1445
+ parsedManifest,
1446
+ documentId,
1447
+ route ?? document.route
1448
+ );
1449
+ store.revision = revision;
1450
+ const selection = selectedInstanceId === void 0 ? document.rootInstanceIds[0] ?? parsedManifest.instances.find((instance) => instance.documentId === documentId)?.instanceId ?? null : selectedInstanceId;
1451
+ if (!store.setSelection(selection)) {
1452
+ throw new HydrogenEditorConfigurationError(
1453
+ `unknown Hydrogen instance "${selectedInstanceId ?? ""}"`
1454
+ );
1455
+ }
1456
+ return readyDocument(store);
1457
+ }
1458
+ function reconciliationDocument(store, reason) {
1459
+ const snapshot = store.getSnapshot();
1460
+ return {
1461
+ reason,
1462
+ documentId: snapshot.document.documentId,
1463
+ ...snapshot.route !== void 0 ? { route: snapshot.route } : {},
1464
+ revision: snapshot.revision,
1465
+ selectedInstanceId: snapshot.selectedInstanceId,
1466
+ selectionStatus: snapshot.selectionStatus,
1467
+ mountedInstanceIds: snapshot.mountedInstanceIds
1468
+ };
1469
+ }
1470
+ function isNullableOpaqueId(value) {
1471
+ return value === null || isOpaqueId(value);
1472
+ }
1473
+ function parseReadyDocument(value, expectedManifest, submittedInstances) {
1474
+ if (!isRecord(value) || value.protocolVersion !== HYDROGEN_EDITOR_PROTOCOL_VERSION || !isNonnegativeInteger(value.revision) || !isNullableOpaqueId(value.selectedInstanceId) || !isNullableOpaqueId(value.hoveredInstanceId) || value.selectionStatus !== "active" && value.selectionStatus !== "out-of-route" && value.selectionStatus !== "none" || value.route !== void 0 && typeof value.route !== "string" || !Array.isArray(value.breakpoints) || !Array.isArray(value.components) || !Array.isArray(value.instances) || !Array.isArray(value.unsupportedFindings)) {
1475
+ return null;
1476
+ }
1477
+ const document = HydrogenDocumentSchema.safeParse(value.document);
1478
+ const breakpoints = value.breakpoints.map(
1479
+ (breakpoint) => HydrogenBreakpointSchema.safeParse(breakpoint)
1480
+ );
1481
+ const components = value.components.map(
1482
+ (component) => HydrogenComponentDefinitionSchema.safeParse(component)
1483
+ );
1484
+ const instances = value.instances.map((candidate) => {
1485
+ if (!isRecord(candidate) || typeof candidate.mounted !== "boolean" || typeof candidate.editable !== "boolean" || !Array.isArray(candidate.capabilityReasons)) {
1486
+ return null;
1487
+ }
1488
+ const instance = HydrogenInstanceSchema.safeParse(candidate.instance);
1489
+ if (!instance.success) return null;
1490
+ const reasons = candidate.capabilityReasons.flatMap((reason) => {
1491
+ if (!isRecord(reason) || typeof reason.code !== "string" || reason.code.length === 0 || reason.code.length > 128 || reason.severity !== "warning" && reason.severity !== "blocking" || typeof reason.message !== "string" || reason.message.length === 0 || reason.message.length > 2048) {
1492
+ return [];
1493
+ }
1494
+ const capability = reason.capability === void 0 ? void 0 : HydrogenEditCapabilitySchema.safeParse(reason.capability);
1495
+ const source = reason.source === void 0 ? void 0 : HydrogenSourceLocationSchema.safeParse(reason.source);
1496
+ if (capability !== void 0 && !capability.success) return [];
1497
+ if (source !== void 0 && !source.success) return [];
1498
+ return [
1499
+ {
1500
+ code: reason.code,
1501
+ severity: reason.severity,
1502
+ message: reason.message,
1503
+ ...capability?.success ? { capability: capability.data } : {},
1504
+ ...source?.success ? { source: source.data } : {}
1505
+ }
1506
+ ];
1507
+ });
1508
+ if (reasons.length !== candidate.capabilityReasons.length) return null;
1509
+ return {
1510
+ instance: instance.data,
1511
+ mounted: candidate.mounted,
1512
+ editable: candidate.editable,
1513
+ capabilityReasons: reasons
1514
+ };
1515
+ });
1516
+ if (!document.success || breakpoints.some((breakpoint) => !breakpoint.success) || components.some((component) => !component.success) || value.breakpoints.length > HYDROGEN_EDITOR_MANIFEST_LIMITS.maxBreakpoints || instances.some((instance) => instance === null) || value.components.length > HYDROGEN_EDITOR_MANIFEST_LIMITS.maxComponents || value.instances.length > HYDROGEN_EDITOR_MANIFEST_LIMITS.maxInstancesPerDocument) {
1517
+ return null;
1518
+ }
1519
+ const unsupportedFindings = value.unsupportedFindings.map(
1520
+ (finding) => HydrogenUnsupportedFindingSchema.safeParse(finding)
1521
+ );
1522
+ if (unsupportedFindings.some((finding) => !finding.success) || unsupportedFindings.length > HYDROGEN_EDITOR_MANIFEST_LIMITS.maxFindings) {
1523
+ return null;
1524
+ }
1525
+ const parsedComponents = components.flatMap(
1526
+ (component) => component.success ? [component.data] : []
1527
+ );
1528
+ const parsedBreakpoints = breakpoints.flatMap(
1529
+ (breakpoint) => breakpoint.success ? [breakpoint.data] : []
1530
+ );
1531
+ const parsedInstances = instances.flatMap((instance) => instance === null ? [] : [instance]);
1532
+ const parsedFindings = unsupportedFindings.flatMap(
1533
+ (finding) => finding.success ? [finding.data] : []
1534
+ );
1535
+ const expectedDocument = expectedManifest.documents.find(
1536
+ (candidate) => candidate.documentId === document.data.documentId
1537
+ );
1538
+ if (expectedDocument === void 0 || jsonSignature(expectedDocument) !== jsonSignature(document.data) || jsonSignature(expectedManifest.breakpoints) !== jsonSignature(parsedBreakpoints)) {
1539
+ return null;
1540
+ }
1541
+ const expectedComponents = new Map(
1542
+ expectedManifest.components.map((component) => [component.componentId, component])
1543
+ );
1544
+ const componentIds = /* @__PURE__ */ new Set();
1545
+ for (const component of parsedComponents) {
1546
+ const expected = expectedComponents.get(component.componentId);
1547
+ if (componentIds.has(component.componentId) || expected === void 0 || jsonSignature(expected) !== jsonSignature(component)) {
1548
+ return null;
1549
+ }
1550
+ componentIds.add(component.componentId);
1551
+ }
1552
+ const expectedInstances = new Map(
1553
+ expectedManifest.instances.map((instance) => [instance.instanceId, instance])
1554
+ );
1555
+ const instanceIds = /* @__PURE__ */ new Set();
1556
+ const sourceAnchors = new Set(parsedComponents.map((component) => component.source.anchorId));
1557
+ const normalizedInstances = [];
1558
+ for (const candidate of parsedInstances) {
1559
+ const instance = candidate.instance;
1560
+ const component = expectedComponents.get(instance.componentId);
1561
+ const expected = expectedInstances.get(instance.instanceId);
1562
+ const submitted = submittedInstances.get(instance.instanceId);
1563
+ const trustedIdentity = expected ?? submitted;
1564
+ if (instanceIds.has(instance.instanceId) || sourceAnchors.has(instance.source.anchorId) || component === void 0 || !componentIds.has(component.componentId) || trustedIdentity === void 0 || trustedIdentity.componentId !== instance.componentId || trustedIdentity.documentId !== instance.documentId || jsonSignature(trustedIdentity.source) !== jsonSignature(instance.source) || instance.documentId !== document.data.documentId || !instanceMatchesComponentContract(expectedManifest, instance, component)) {
1565
+ return null;
1566
+ }
1567
+ instanceIds.add(instance.instanceId);
1568
+ sourceAnchors.add(instance.source.anchorId);
1569
+ const capabilityReasons = findingsForInstance(expectedManifest, instance, component);
1570
+ normalizedInstances.push({
1571
+ instance,
1572
+ mounted: candidate.mounted,
1573
+ editable: component.capabilities.length > 0 && !capabilityReasons.some((reason) => reason.severity === "blocking"),
1574
+ capabilityReasons
1575
+ });
1576
+ }
1577
+ for (const candidate of normalizedInstances) {
1578
+ const parent = candidate.instance.parent;
1579
+ if (parent === null) {
1580
+ if (!document.data.rootInstanceIds.includes(candidate.instance.instanceId)) return null;
1581
+ continue;
1582
+ }
1583
+ const parentInstance = normalizedInstances.find(
1584
+ (item) => item.instance.instanceId === parent.instanceId
1585
+ )?.instance;
1586
+ const parentComponent = parentInstance === void 0 ? void 0 : expectedComponents.get(parentInstance.componentId);
1587
+ const slot = parentComponent?.slots.find((item) => item.slotId === parent.slotId);
1588
+ if (parentInstance === void 0 || slot === void 0 || slot.accepts.mode === "include" && !slot.accepts.componentIds.includes(candidate.instance.componentId)) {
1589
+ return null;
1590
+ }
1591
+ }
1592
+ if (document.data.rootInstanceIds.some((instanceId) => !instanceIds.has(instanceId)) || value.selectedInstanceId !== null && !expectedInstances.has(value.selectedInstanceId) && !submittedInstances.has(value.selectedInstanceId) || value.hoveredInstanceId !== null && !instanceIds.has(value.hoveredInstanceId)) {
1593
+ return null;
1594
+ }
1595
+ const expectedFindingSignatures = new Set(
1596
+ expectedManifest.unsupportedFindings.filter((finding) => {
1597
+ switch (finding.target.kind) {
1598
+ case "manifest":
1599
+ return true;
1600
+ case "component":
1601
+ return componentIds.has(finding.target.componentId);
1602
+ case "instance":
1603
+ return instanceIds.has(finding.target.instanceId);
1604
+ case "source":
1605
+ return sourceAnchors.has(finding.target.anchorId);
1606
+ }
1607
+ }).map((finding) => jsonSignature(finding))
1608
+ );
1609
+ const receivedFindingSignatures = new Set(parsedFindings.map((finding) => jsonSignature(finding)));
1610
+ if (receivedFindingSignatures.size !== expectedFindingSignatures.size || [...receivedFindingSignatures].some((signature) => !expectedFindingSignatures.has(signature))) {
1611
+ return null;
1612
+ }
1613
+ return {
1614
+ protocolVersion: HYDROGEN_EDITOR_PROTOCOL_VERSION,
1615
+ document: document.data,
1616
+ breakpoints: parsedBreakpoints,
1617
+ ...value.route !== void 0 ? { route: value.route } : {},
1618
+ revision: value.revision,
1619
+ selectedInstanceId: value.selectedInstanceId,
1620
+ hoveredInstanceId: value.hoveredInstanceId,
1621
+ selectionStatus: value.selectionStatus,
1622
+ components: parsedComponents,
1623
+ instances: normalizedInstances,
1624
+ unsupportedFindings: parsedFindings
1625
+ };
1626
+ }
1627
+ function parseApplyResult(value) {
1628
+ if (!isRecord(value) || value.status !== "applied" && value.status !== "duplicate" && value.status !== "rejected" || !isNonnegativeInteger(value.revision) || !isOpaqueId(value.operationId)) {
1629
+ return null;
1630
+ }
1631
+ if (value.status === "rejected") {
1632
+ if (!isRecord(value.failure) || typeof value.failure.code !== "string" || !OPERATION_FAILURE_CODES.has(value.failure.code) || typeof value.failure.message !== "string" || value.failure.message.length > 2048 || value.failure.instanceId !== void 0 && !isOpaqueId(value.failure.instanceId)) {
1633
+ return null;
1634
+ }
1635
+ const capability = value.failure.capability === void 0 ? void 0 : HydrogenEditCapabilitySchema.safeParse(value.failure.capability);
1636
+ const source = value.failure.source === void 0 ? void 0 : HydrogenSourceLocationSchema.safeParse(value.failure.source);
1637
+ if (capability !== void 0 && !capability.success) return null;
1638
+ if (source !== void 0 && !source.success) return null;
1639
+ return {
1640
+ status: "rejected",
1641
+ revision: value.revision,
1642
+ operationId: value.operationId,
1643
+ failure: {
1644
+ code: value.failure.code,
1645
+ message: value.failure.message,
1646
+ ...value.failure.instanceId !== void 0 ? { instanceId: value.failure.instanceId } : {},
1647
+ ...capability?.success ? { capability: capability.data } : {},
1648
+ ...source?.success ? { source: source.data } : {}
1649
+ }
1650
+ };
1651
+ }
1652
+ return {
1653
+ status: value.status,
1654
+ revision: value.revision,
1655
+ operationId: value.operationId
1656
+ };
1657
+ }
1658
+ function parseCanvasOperationEvent(value) {
1659
+ if (!isRecord(value) || !hasOnlyKeys(value, ["intent", "operation", "result"])) return null;
1660
+ const intent = parseHydrogenEditorCanvasIntent(value.intent);
1661
+ const operation = parseOperation(value.operation);
1662
+ const result = parseApplyResult(value.result);
1663
+ if (intent === null || intent.type === "slot.insert.request" || operation === null || result === null || operation.operationId !== result.operationId) {
1664
+ return null;
1665
+ }
1666
+ const matches = intent.type === "instance.move" && operation.type === "instance.move" && operation.instanceId === intent.instanceId && operation.parentInstanceId === intent.parentInstanceId && operation.slotId === intent.slotId && operation.index === intent.index || intent.type === "prop.inline" && operation.type === "prop.patch" && operation.instanceId === intent.instanceId && operation.propName === intent.propName && operation.binding.kind === "inline" && operation.binding.value === intent.value || intent.type === "styles.adjust" && operation.type === "styles.patch" && operation.instanceId === intent.instanceId;
1667
+ return matches ? { intent, operation, result } : null;
1668
+ }
1669
+ function parseReconciliation(value) {
1670
+ if (!isRecord(value) || value.reason !== "route" && value.reason !== "loader" && value.reason !== "suspense" && value.reason !== "hmr" || !isOpaqueId(value.documentId) || value.route !== void 0 && typeof value.route !== "string" || !isNonnegativeInteger(value.revision) || !isNullableOpaqueId(value.selectedInstanceId) || value.selectionStatus !== "active" && value.selectionStatus !== "out-of-route" && value.selectionStatus !== "none" || !Array.isArray(value.mountedInstanceIds) || !value.mountedInstanceIds.every(isOpaqueId)) {
1671
+ return null;
1672
+ }
1673
+ return value;
1674
+ }
1675
+ function createHydrogenEditorPreviewRuntime({
1676
+ adminOrigin,
1677
+ documentId,
1678
+ manifest,
1679
+ now = Date.now,
1680
+ onProtocolFailure,
1681
+ route,
1682
+ security,
1683
+ target,
1684
+ transport
1685
+ }) {
1686
+ const trustedAdminOrigin = exactOrigin(adminOrigin, "adminOrigin");
1687
+ validateSecurityContext(security, now());
1688
+ const parsedManifest = HydrogenEditorManifestSchema.parse(manifest);
1689
+ if (parsedManifest.source.baseSha !== security.baseSha) {
1690
+ throw new HydrogenEditorConfigurationError(
1691
+ "manifest source baseSha does not match the editor session"
1692
+ );
1693
+ }
1694
+ const store = new HydrogenEditorPreviewStoreImpl(parsedManifest, documentId, route);
1695
+ const inbound = { sequence: 0, messages: /* @__PURE__ */ new Map() };
1696
+ const buffered = [];
1697
+ let outboundSequence = 0;
1698
+ let canvasOperationSequence = 0;
1699
+ let ready = false;
1700
+ let disposed = false;
1701
+ const reportProtocolFailure = (failure) => {
1702
+ onProtocolFailure?.(failure);
1703
+ };
1704
+ const post = (type, payload) => {
1705
+ if (now() >= security.expiresAt) {
1706
+ reportProtocolFailure(protocolFailure("MESSAGE_EXPIRED", "editor preview token has expired"));
1707
+ return;
1708
+ }
1709
+ outboundSequence += 1;
1710
+ postEnvelope(
1711
+ target,
1712
+ trustedAdminOrigin,
1713
+ createEnvelope(PREVIEW_SOURCE, security, outboundSequence, type, payload)
1714
+ );
1715
+ };
1716
+ const postReady = () => {
1717
+ try {
1718
+ post("editor.ready", readyDocument(store));
1719
+ } catch (error) {
1720
+ const failure = protocolFailure(
1721
+ "MESSAGE_TOO_LARGE",
1722
+ error instanceof Error ? error.message : "ready document exceeds bridge bounds"
1723
+ );
1724
+ reportProtocolFailure(failure);
1725
+ post("editor.protocol.failure", failure);
1726
+ }
1727
+ };
1728
+ const process = (envelope) => {
1729
+ if (now() >= security.expiresAt) {
1730
+ reportProtocolFailure(
1731
+ protocolFailure("MESSAGE_EXPIRED", "buffered editor message expired before ready")
1732
+ );
1733
+ return;
1734
+ }
1735
+ switch (envelope.type) {
1736
+ case "editor.protocol.failure": {
1737
+ const failure = parseProtocolFailure(envelope.payload);
1738
+ onProtocolFailure?.(
1739
+ failure ?? protocolFailure("UNKNOWN_MESSAGE", "preview protocol failure payload is invalid")
1740
+ );
1741
+ return;
1742
+ }
1743
+ case "editor.ready.request":
1744
+ postReady();
1745
+ return;
1746
+ case "instance.hover": {
1747
+ const instanceId = isRecord(envelope.payload) && isNullableOpaqueId(envelope.payload.instanceId) ? envelope.payload.instanceId : void 0;
1748
+ if (instanceId === void 0 || !store.setHover(instanceId)) {
1749
+ reportProtocolFailure(
1750
+ protocolFailure("UNKNOWN_MESSAGE", "hover targets an unknown instance")
1751
+ );
1752
+ }
1753
+ return;
1754
+ }
1755
+ case "instance.select": {
1756
+ const instanceId = isRecord(envelope.payload) && isNullableOpaqueId(envelope.payload.instanceId) ? envelope.payload.instanceId : void 0;
1757
+ if (instanceId === void 0 || !store.setSelection(instanceId)) {
1758
+ reportProtocolFailure(
1759
+ protocolFailure("UNKNOWN_MESSAGE", "selection targets an unknown instance")
1760
+ );
1761
+ }
1762
+ return;
1763
+ }
1764
+ case "preview.operation.apply": {
1765
+ const operation = parseOperation(envelope.payload);
1766
+ const result = operation === null ? rejectOperation(
1767
+ { operationId: "invalid" },
1768
+ store.revision,
1769
+ operationFailure("INVALID_OPERATION", "operation payload is invalid")
1770
+ ) : store.apply(operation);
1771
+ post("preview.operation.result", result);
1772
+ return;
1773
+ }
1774
+ default:
1775
+ reportProtocolFailure(
1776
+ protocolFailure("UNKNOWN_MESSAGE", `unknown host message "${envelope.type}"`)
1777
+ );
1778
+ }
1779
+ };
1780
+ const onMessage = (event) => {
1781
+ if (disposed || event.origin !== trustedAdminOrigin) return;
1782
+ if (event.source !== target) return;
1783
+ const parsed = parseEnvelope(event.data, HOST_SOURCE, security, inbound, now());
1784
+ if ("code" in parsed) {
1785
+ reportProtocolFailure(parsed);
1786
+ return;
1787
+ }
1788
+ if (parsed.duplicate) return;
1789
+ if (!ready) {
1790
+ if (buffered.length >= HYDROGEN_EDITOR_BRIDGE_LIMITS.maxBufferedMessages) {
1791
+ reportProtocolFailure(
1792
+ protocolFailure(
1793
+ "BUFFER_LIMIT_EXCEEDED",
1794
+ "pre-ready Hydrogen editor message buffer is full"
1795
+ )
1796
+ );
1797
+ return;
1798
+ }
1799
+ buffered.push(parsed.envelope);
1800
+ return;
1801
+ }
1802
+ process(parsed.envelope);
1803
+ };
1804
+ transport.addEventListener("message", onMessage);
1805
+ return {
1806
+ manifest: parsedManifest,
1807
+ store,
1808
+ ready: () => {
1809
+ if (disposed) return;
1810
+ ready = true;
1811
+ postReady();
1812
+ while (buffered.length > 0) {
1813
+ const envelope = buffered.shift();
1814
+ if (envelope !== void 0) process(envelope);
1815
+ }
1816
+ },
1817
+ reconcile: (nextDocumentId, reason, nextRoute) => {
1818
+ if (disposed) return;
1819
+ store.reconcile(nextDocumentId, nextRoute);
1820
+ if (ready) post("editor.reconciled", reconciliationDocument(store, reason));
1821
+ },
1822
+ registerInstance: (instanceId) => {
1823
+ if (disposed || store.getInstanceSnapshot(instanceId) === null) return () => void 0;
1824
+ store.setMounted(instanceId, true);
1825
+ if (ready && store.selectedInstanceId === instanceId) {
1826
+ post("editor.reconciled", reconciliationDocument(store, "suspense"));
1827
+ }
1828
+ return () => {
1829
+ store.setMounted(instanceId, false);
1830
+ if (ready && store.selectedInstanceId === instanceId) {
1831
+ post("editor.reconciled", reconciliationDocument(store, "suspense"));
1832
+ }
1833
+ };
1834
+ },
1835
+ hoverInstance: (instanceId) => {
1836
+ if (disposed || !store.setHover(instanceId)) return;
1837
+ if (ready) post("instance.hover", { instanceId });
1838
+ },
1839
+ selectInstance: (instanceId) => {
1840
+ if (disposed || !store.setSelection(instanceId)) return;
1841
+ if (ready) post("instance.select", { instanceId });
1842
+ },
1843
+ dispatchCanvasIntent: (value) => {
1844
+ const intent = parseHydrogenEditorCanvasIntent(value);
1845
+ if (disposed || !ready || intent === null) {
1846
+ return {
1847
+ status: "rejected",
1848
+ revision: store.revision,
1849
+ failure: operationFailure(
1850
+ "INVALID_OPERATION",
1851
+ disposed ? "editor preview runtime is disposed" : !ready ? "editor preview runtime is not ready" : "canvas intent is invalid"
1852
+ )
1853
+ };
1854
+ }
1855
+ if (intent.type === "slot.insert.request") {
1856
+ const failure = validateCanvasInsertRequest(store, intent);
1857
+ if (failure !== null) {
1858
+ return { status: "rejected", revision: store.revision, failure };
1859
+ }
1860
+ post("canvas.insert.request", intent);
1861
+ return { status: "insert-requested", intent, revision: store.revision };
1862
+ }
1863
+ canvasOperationSequence += 1;
1864
+ const operation = canvasOperationForIntent(store, intent, `canvas:${canvasOperationSequence}`);
1865
+ if ("code" in operation) {
1866
+ return {
1867
+ status: "rejected",
1868
+ revision: store.revision,
1869
+ failure: operation
1870
+ };
1871
+ }
1872
+ const result = store.apply(operation);
1873
+ post("canvas.operation", { intent, operation, result });
1874
+ return { status: "operation", intent, operation, result };
1875
+ },
1876
+ dispose: () => {
1877
+ if (disposed) return;
1878
+ disposed = true;
1879
+ buffered.length = 0;
1880
+ transport.removeEventListener("message", onMessage);
1881
+ }
1882
+ };
1883
+ }
1884
+ function createHydrogenEditorHostBridge({
1885
+ manifest,
1886
+ now = Date.now,
1887
+ onCanvasInsertRequest,
1888
+ onCanvasOperation,
1889
+ onHoverInstance,
1890
+ onOperationResult,
1891
+ onProtocolFailure,
1892
+ onReady,
1893
+ onReconcile,
1894
+ onSelectInstance,
1895
+ previewOrigin,
1896
+ resolveTarget,
1897
+ security,
1898
+ target,
1899
+ transport
1900
+ }) {
1901
+ const trustedPreviewOrigin = exactOrigin(previewOrigin, "previewOrigin");
1902
+ validateSecurityContext(security, now());
1903
+ const expectedManifest = cloneManifest(manifest);
1904
+ if (expectedManifest.source.baseSha !== security.baseSha) {
1905
+ throw new HydrogenEditorConfigurationError(
1906
+ "manifest source baseSha does not match the editor session"
1907
+ );
1908
+ }
1909
+ const inbound = { sequence: 0, messages: /* @__PURE__ */ new Map() };
1910
+ const buffered = [];
1911
+ const submittedInstances = /* @__PURE__ */ new Map();
1912
+ let outboundSequence = 0;
1913
+ let ready = false;
1914
+ let disposed = false;
1915
+ const currentTarget = () => resolveTarget?.() ?? target;
1916
+ const post = (type, payload) => {
1917
+ if (now() >= security.expiresAt) {
1918
+ throw new HydrogenEditorConfigurationError("editor preview security context is expired");
1919
+ }
1920
+ outboundSequence += 1;
1921
+ postEnvelope(
1922
+ currentTarget(),
1923
+ trustedPreviewOrigin,
1924
+ createEnvelope(HOST_SOURCE, security, outboundSequence, type, payload)
1925
+ );
1926
+ };
1927
+ const process = (envelope) => {
1928
+ switch (envelope.type) {
1929
+ case "editor.protocol.failure": {
1930
+ const failure = parseProtocolFailure(envelope.payload);
1931
+ onProtocolFailure?.(
1932
+ failure ?? protocolFailure("UNKNOWN_MESSAGE", "preview protocol failure payload is invalid")
1933
+ );
1934
+ return;
1935
+ }
1936
+ case "instance.hover":
1937
+ case "instance.select": {
1938
+ const instanceId = isRecord(envelope.payload) && isNullableOpaqueId(envelope.payload.instanceId) ? envelope.payload.instanceId : void 0;
1939
+ if (instanceId === void 0) {
1940
+ onProtocolFailure?.(
1941
+ protocolFailure("UNKNOWN_MESSAGE", `${envelope.type} payload is invalid`)
1942
+ );
1943
+ return;
1944
+ }
1945
+ if (envelope.type === "instance.hover") onHoverInstance?.(instanceId);
1946
+ else onSelectInstance?.(instanceId);
1947
+ return;
1948
+ }
1949
+ case "preview.operation.result": {
1950
+ const result = parseApplyResult(envelope.payload);
1951
+ if (result === null) {
1952
+ onProtocolFailure?.(
1953
+ protocolFailure("UNKNOWN_MESSAGE", "operation result payload is invalid")
1954
+ );
1955
+ return;
1956
+ }
1957
+ onOperationResult?.(result);
1958
+ return;
1959
+ }
1960
+ case "canvas.operation": {
1961
+ const event = parseCanvasOperationEvent(envelope.payload);
1962
+ if (event === null) {
1963
+ onProtocolFailure?.(
1964
+ protocolFailure("UNKNOWN_MESSAGE", "canvas operation payload is invalid")
1965
+ );
1966
+ return;
1967
+ }
1968
+ onOperationResult?.(event.result);
1969
+ onCanvasOperation?.(event.intent, event.operation, event.result);
1970
+ return;
1971
+ }
1972
+ case "canvas.insert.request": {
1973
+ const request = parseHydrogenEditorCanvasIntent(envelope.payload);
1974
+ if (request === null || request.type !== "slot.insert.request") {
1975
+ onProtocolFailure?.(
1976
+ protocolFailure("UNKNOWN_MESSAGE", "canvas insertion request payload is invalid")
1977
+ );
1978
+ return;
1979
+ }
1980
+ onCanvasInsertRequest?.(request);
1981
+ return;
1982
+ }
1983
+ case "editor.reconciled": {
1984
+ const reconciliation = parseReconciliation(envelope.payload);
1985
+ if (reconciliation === null) {
1986
+ onProtocolFailure?.(
1987
+ protocolFailure("UNKNOWN_MESSAGE", "reconciliation payload is invalid")
1988
+ );
1989
+ return;
1990
+ }
1991
+ onReconcile?.(reconciliation);
1992
+ return;
1993
+ }
1994
+ default:
1995
+ onProtocolFailure?.(
1996
+ protocolFailure("UNKNOWN_MESSAGE", `unknown preview message "${envelope.type}"`)
1997
+ );
1998
+ }
1999
+ };
2000
+ const onMessage = (event) => {
2001
+ if (disposed || event.origin !== trustedPreviewOrigin) return;
2002
+ const expectedTarget = currentTarget();
2003
+ if (event.source !== expectedTarget) return;
2004
+ const parsed = parseEnvelope(event.data, PREVIEW_SOURCE, security, inbound, now());
2005
+ if ("code" in parsed) {
2006
+ onProtocolFailure?.(parsed);
2007
+ return;
2008
+ }
2009
+ if (parsed.duplicate) return;
2010
+ if (parsed.envelope.type === "editor.protocol.failure") {
2011
+ process(parsed.envelope);
2012
+ return;
2013
+ }
2014
+ if (parsed.envelope.type === "editor.ready") {
2015
+ const readyDocumentPayload = parseReadyDocument(
2016
+ parsed.envelope.payload,
2017
+ expectedManifest,
2018
+ submittedInstances
2019
+ );
2020
+ if (readyDocumentPayload === null) {
2021
+ onProtocolFailure?.(protocolFailure("UNKNOWN_MESSAGE", "ready payload is invalid"));
2022
+ return;
2023
+ }
2024
+ ready = true;
2025
+ onReady?.(readyDocumentPayload);
2026
+ while (buffered.length > 0) {
2027
+ const envelope = buffered.shift();
2028
+ if (envelope !== void 0) process(envelope);
2029
+ }
2030
+ return;
2031
+ }
2032
+ if (!ready) {
2033
+ if (buffered.length >= HYDROGEN_EDITOR_BRIDGE_LIMITS.maxBufferedMessages) {
2034
+ onProtocolFailure?.(
2035
+ protocolFailure(
2036
+ "BUFFER_LIMIT_EXCEEDED",
2037
+ "pre-ready Hydrogen preview message buffer is full"
2038
+ )
2039
+ );
2040
+ return;
2041
+ }
2042
+ buffered.push(parsed.envelope);
2043
+ return;
2044
+ }
2045
+ process(parsed.envelope);
2046
+ };
2047
+ transport.addEventListener("message", onMessage);
2048
+ return {
2049
+ requestReady: () => post("editor.ready.request"),
2050
+ hoverInstance: (instanceId) => {
2051
+ if (instanceId !== null && !isOpaqueId(instanceId)) return;
2052
+ post("instance.hover", { instanceId });
2053
+ },
2054
+ selectInstance: (instanceId) => {
2055
+ if (instanceId !== null && !isOpaqueId(instanceId)) return;
2056
+ post("instance.select", { instanceId });
2057
+ },
2058
+ applyOperation: (operation) => {
2059
+ const parsed = parseOperation(operation);
2060
+ if (parsed === null) {
2061
+ throw new HydrogenEditorConfigurationError("operation does not satisfy the bridge contract");
2062
+ }
2063
+ if (parsed.type === "slot.insert") {
2064
+ submittedInstances.set(parsed.instance.instanceId, parsed.instance);
2065
+ }
2066
+ post("preview.operation.apply", parsed);
2067
+ },
2068
+ dispose: () => {
2069
+ if (disposed) return;
2070
+ disposed = true;
2071
+ buffered.length = 0;
2072
+ transport.removeEventListener("message", onMessage);
2073
+ }
2074
+ };
2075
+ }
2076
+ function resolveHydrogenEditorStyles(manifest, instance, viewportWidth) {
2077
+ const component = manifest.components.find(
2078
+ (candidate) => candidate.componentId === instance.componentId
2079
+ );
2080
+ if (component === void 0) return {};
2081
+ const matchingBreakpoints = viewportWidth === void 0 ? [] : [...manifest.breakpoints].filter((breakpoint) => breakpoint.minWidth <= viewportWidth).sort(
2082
+ (left, right) => left.minWidth - right.minWidth || left.breakpointId.localeCompare(right.breakpointId)
2083
+ );
2084
+ const variant = component.variants.find((candidate) => candidate.variantId === instance.variantId);
2085
+ const tokens = {
2086
+ ...variant?.styles.tokens,
2087
+ ...instance.styles.base.tokens
2088
+ };
2089
+ const layout = {
2090
+ ...variant?.styles.layout,
2091
+ ...instance.styles.base.layout
2092
+ };
2093
+ for (const breakpoint of matchingBreakpoints) {
2094
+ const override = instance.styles.overrides.find(
2095
+ (candidate) => candidate.breakpointId === breakpoint.breakpointId
2096
+ );
2097
+ Object.assign(tokens, override?.values.tokens);
2098
+ Object.assign(layout, override?.values.layout);
2099
+ }
2100
+ const styles = {};
2101
+ for (const token of component.styles.tokens) {
2102
+ const value = tokens[token.tokenId];
2103
+ if (typeof value === "string" || typeof value === "number") {
2104
+ styles[token.cssVariable] = value;
2105
+ }
2106
+ }
2107
+ for (const control of component.styles.layout) {
2108
+ const value = layout[control.controlId];
2109
+ if (typeof value === "string" || typeof value === "number") {
2110
+ styles[control.cssProperty] = value;
2111
+ }
2112
+ }
2113
+ return styles;
2114
+ }
2115
+
2116
+ export { HYDROGEN_EDITOR_BRIDGE_LIMITS, HYDROGEN_EDITOR_PROTOCOL_VERSION, HydrogenEditorConfigurationError, createHydrogenEditorHostBridge, createHydrogenEditorPreviewRuntime, createHydrogenEditorSourceProjection, parseHydrogenEditorCanvasIntent, resolveHydrogenEditorStyles };
2117
+ //# sourceMappingURL=chunk-XXNCFWXT.js.map
2118
+ //# sourceMappingURL=chunk-XXNCFWXT.js.map