@juo/blocks 1.9.0 → 1.10.1

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.
@@ -1,602 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { c as createContext, d, E, m as getDefinedBlocks, f as createBlockInstance, w, r as registerBlock, k as defineBlock, v as subscribeContext } from "./block-C8lPBCF8.js";
5
- import { a as createEditorBridge, e as createSelectBlockMessage } from "./bridge-BOcO9toS.js";
6
- const TranslationContext = createContext("translation");
7
- function toStringRecord(input) {
8
- if (input == null) return {};
9
- return Object.fromEntries(
10
- Object.entries(input).filter(([, value]) => typeof value === "string")
11
- );
12
- }
13
- function createTranslationService(locales, options = {}) {
14
- const loadedThemeDefaults = d({});
15
- if (options.loadDefaultTranslations) {
16
- let loadVersion = 0;
17
- E(() => {
18
- const locale = locales.locale.value;
19
- const currentVersion = ++loadVersion;
20
- void options.loadDefaultTranslations(locale).then((result) => toStringRecord(result)).catch(() => ({})).then((result) => {
21
- if (currentVersion !== loadVersion) return;
22
- loadedThemeDefaults.value = result;
23
- });
24
- });
25
- }
26
- return {
27
- locale: locales.locale,
28
- resolve(blockType, blockId, key) {
29
- var _a, _b, _c;
30
- const data = locales.catalogs.value;
31
- const themeDefault = loadedThemeDefaults.value[key] ?? (data == null ? void 0 : data.theme.defaults[key]);
32
- if (data == null) {
33
- return themeDefault ?? key;
34
- }
35
- if (blockId && ((_a = data.blocks.overrides[blockId]) == null ? void 0 : _a[key]) !== void 0) {
36
- return data.blocks.overrides[blockId][key];
37
- }
38
- const blockDefault = blockType != null ? (_b = data.blocks.defaults[blockType]) == null ? void 0 : _b[key] : void 0;
39
- const mode = (blockType != null ? (_c = data.blocks.modes[blockType]) == null ? void 0 : _c[key] : void 0) ?? "override";
40
- if (blockDefault !== void 0 && mode === "override") {
41
- return blockDefault;
42
- }
43
- if (data.theme.overrides[key] !== void 0) {
44
- return data.theme.overrides[key];
45
- }
46
- if (themeDefault !== void 0) {
47
- return themeDefault;
48
- }
49
- if (blockDefault !== void 0) {
50
- return blockDefault;
51
- }
52
- return key;
53
- }
54
- };
55
- }
56
- function getDefaultBlockForAction(actionType, actionConfig, blockId) {
57
- const blockName = `action:${actionType}`;
58
- const definedBlocks = getDefinedBlocks();
59
- const exists = definedBlocks.some((b) => b.name === blockName);
60
- if (!exists) {
61
- throw new Error(
62
- `No default block registered for action type "${actionType}". Expected a block named "${blockName}" to be registered in blocks-extensions.`
63
- );
64
- }
65
- return createBlockInstance(blockName, { props: actionConfig, id: blockId });
66
- }
67
- function hasDefaultBlockForAction(actionType) {
68
- const blockName = `action:${actionType}`;
69
- return getDefinedBlocks().some((b) => b.name === blockName);
70
- }
71
- const WorkflowServiceContext = createContext("services/workflow");
72
- function createWorkflowApiAdapter(sdk) {
73
- return {
74
- async start(flowType, context, workflowId, dryRun) {
75
- const { data } = await sdk.post("/customer/v1/workflows/start", {
76
- flowType,
77
- context,
78
- ...workflowId ? { workflowId } : {},
79
- ...dryRun ? { dryRun: true } : {}
80
- });
81
- return data;
82
- },
83
- async respond(runId, interactionId, responseType, responseData, expectedRunVersion, eventId) {
84
- const { data } = await sdk.post(
85
- "/customer/v1/workflows/{runId}/respond",
86
- {
87
- runId,
88
- interactionId,
89
- responseType,
90
- responseData,
91
- expectedRunVersion,
92
- eventId
93
- }
94
- );
95
- return data;
96
- },
97
- async getState(runId) {
98
- const { data } = await sdk.get("/customer/v1/workflows/{runId}", {
99
- runId
100
- });
101
- return data;
102
- },
103
- async cancel(runId) {
104
- const { data } = await sdk.post("/customer/v1/workflows/{runId}/cancel", {
105
- runId
106
- });
107
- return data;
108
- }
109
- };
110
- }
111
- function createWorkflowService(deps) {
112
- const { api } = deps;
113
- const editor = createEditorBridge();
114
- const urlParams = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
115
- const previewWorkflowTrigger = (urlParams == null ? void 0 : urlParams.get("trigger")) ?? null;
116
- const previewStepId = (urlParams == null ? void 0 : urlParams.get("step-id")) ?? null;
117
- const previewWorkflowId = (urlParams == null ? void 0 : urlParams.get("workflow-id")) ?? null;
118
- const isEditorMode = editor.isEditorMode || previewWorkflowTrigger !== null;
119
- const _state = d({
120
- status: "idle",
121
- flowType: null,
122
- workflowId: null,
123
- workflowVersion: null,
124
- runId: null,
125
- runVersion: null,
126
- currentStepId: null,
127
- currentInteraction: null,
128
- error: null,
129
- result: null,
130
- flowParams: null,
131
- invalidatedResources: [],
132
- outcome: null,
133
- completionData: null,
134
- dryRun: null,
135
- firstPollReason: null
136
- });
137
- const _backendInteractionId = d(null);
138
- const _registeredRootBlocks = /* @__PURE__ */ new Set();
139
- const isActive = w(() => _state.value.status === "active");
140
- const isLoading = w(() => _state.value.status === "loading");
141
- const isCompleted = w(() => _state.value.status === "completed");
142
- const hasError = w(() => _state.value.status === "error");
143
- const currentInteraction = w(() => _state.value.currentInteraction);
144
- const canRespond = w(() => {
145
- const interaction = _state.value.currentInteraction;
146
- const backendId = _backendInteractionId.value;
147
- if (!interaction || !backendId) return false;
148
- return interaction.interactionId === backendId;
149
- });
150
- const currentStepInfo = w(() => {
151
- const interaction = _state.value.currentInteraction;
152
- if (!interaction) return null;
153
- const actionConfig = deps.shopCurrencyCode ? { ...interaction.actionConfig, currency: deps.shopCurrencyCode } : interaction.actionConfig;
154
- return {
155
- interactionId: interaction.interactionId,
156
- stepId: interaction.stepId,
157
- actionType: interaction.actionType,
158
- actionConfig,
159
- availableResponses: interaction.availableResponses
160
- };
161
- });
162
- function updateState(updates) {
163
- _state.value = { ..._state.value, ...updates };
164
- }
165
- function hashString(value) {
166
- let hash = 2166136261;
167
- for (let index = 0; index < value.length; index += 1) {
168
- hash ^= value.charCodeAt(index);
169
- hash = Math.imul(hash, 16777619);
170
- }
171
- return (hash >>> 0).toString(36);
172
- }
173
- function shortenId(value, slugLength = 6) {
174
- const compact = value.toLowerCase().replace(/[^a-z0-9]/g, "");
175
- const slug = (compact || "id").slice(0, slugLength);
176
- return `${slug}-${hashString(value)}`;
177
- }
178
- function createStableStepBlockId(workflowId, stepId) {
179
- return `wf-${shortenId(workflowId)}-${shortenId(stepId)}`;
180
- }
181
- function handleFlowResponse(data) {
182
- var _a, _b, _c, _d, _e, _f, _g;
183
- const mergedInvalidated = [
184
- ..._state.value.invalidatedResources,
185
- ...data.invalidatedResources ?? []
186
- ];
187
- if (data.status === "waiting_interaction") {
188
- const currentStepId = ((_a = data.pendingInteraction) == null ? void 0 : _a.stepId) ?? (isEditorMode ? (_c = (_b = data.interactiveSteps) == null ? void 0 : _b[0]) == null ? void 0 : _c.stepId : null);
189
- _backendInteractionId.value = ((_d = data.pendingInteraction) == null ? void 0 : _d.interactionId) ?? null;
190
- const isInitialResponse = _state.value.runId == null;
191
- updateState({
192
- status: "active",
193
- workflowId: data.workflowId,
194
- workflowVersion: data.workflowVersion,
195
- runId: data.runId,
196
- runVersion: data.runVersion,
197
- currentStepId,
198
- currentInteraction: data.pendingInteraction,
199
- invalidatedResources: mergedInvalidated,
200
- outcome: null
201
- });
202
- if (editor.isEditorMode) {
203
- const displayStepId = isInitialResponse && previewStepId && ((_e = data.interactiveSteps) == null ? void 0 : _e.some((s) => s.stepId === previewStepId)) ? previewStepId : currentStepId;
204
- const overlayBlocks = deps.themeState.overlay.blocks.value;
205
- const rootBlock = overlayBlocks[0];
206
- const stepSlot = displayStepId ? (_f = rootBlock == null ? void 0 : rootBlock.slots) == null ? void 0 : _f[displayStepId] : null;
207
- const stepBlock = Array.isArray(stepSlot) ? stepSlot[0] : null;
208
- const blockId = stepBlock && typeof stepBlock === "object" && "id" in stepBlock ? stepBlock.id : null;
209
- if (blockId) {
210
- editor.sendMessage(
211
- createSelectBlockMessage({ blockId }, blockId)
212
- );
213
- }
214
- }
215
- } else if (data.status === "completed") {
216
- _backendInteractionId.value = null;
217
- updateState({
218
- status: "completed",
219
- currentStepId: null,
220
- currentInteraction: null,
221
- runVersion: data.runVersion,
222
- result: data.result || null,
223
- invalidatedResources: mergedInvalidated,
224
- outcome: data.outcome || null,
225
- completionData: data.completionData || null
226
- });
227
- } else if (data.status === "failed") {
228
- _backendInteractionId.value = null;
229
- handleError(new Error(((_g = data.error) == null ? void 0 : _g.message) || "Flow failed"));
230
- } else if (data.status === "cancelled") {
231
- _backendInteractionId.value = null;
232
- updateState({
233
- status: "idle",
234
- flowType: null,
235
- workflowId: null,
236
- workflowVersion: null,
237
- runId: null,
238
- runVersion: null,
239
- currentStepId: null,
240
- currentInteraction: null,
241
- error: null,
242
- result: null,
243
- invalidatedResources: [],
244
- outcome: null,
245
- completionData: null,
246
- firstPollReason: null
247
- });
248
- }
249
- }
250
- function handleError(error) {
251
- const message = error instanceof Error ? error.message : "Unknown error";
252
- updateState({
253
- status: "error",
254
- error: message
255
- });
256
- }
257
- function maybeApplyPreviewStep(flowType, data) {
258
- var _a;
259
- if (!previewWorkflowTrigger) return;
260
- if (flowType !== previewWorkflowTrigger) return;
261
- if (data.status !== "waiting_interaction") return;
262
- const step = (_a = data.interactiveSteps) == null ? void 0 : _a.find(
263
- (interactiveStep) => interactiveStep.stepId === previewStepId
264
- );
265
- if (!step) return;
266
- updateState({
267
- currentStepId: step.stepId,
268
- currentInteraction: {
269
- interactionId: `preview-${data.runId}-${step.stepId}`,
270
- stepId: step.stepId,
271
- actionType: step.actionType,
272
- actionBehavior: "input",
273
- actionConfig: step.actionConfig,
274
- availableResponses: step.availableResponses,
275
- presentedAt: /* @__PURE__ */ new Date()
276
- }
277
- });
278
- }
279
- function initWorkflowRootBlock(data) {
280
- if (_registeredRootBlocks.has(data.workflowId)) {
281
- return;
282
- }
283
- const allSteps = data.interactiveSteps ?? (data.pendingInteraction ? [
284
- {
285
- stepId: data.pendingInteraction.stepId,
286
- actionType: data.pendingInteraction.actionType,
287
- actionConfig: data.pendingInteraction.actionConfig,
288
- availableResponses: data.pendingInteraction.availableResponses
289
- }
290
- ] : []);
291
- const slotProperties = {};
292
- for (const step of allSteps) {
293
- slotProperties[step.stepId] = {};
294
- }
295
- const presets = {};
296
- for (const step of allSteps) {
297
- presets[step.stepId] = {
298
- label: step.stepId,
299
- description: step.actionType,
300
- state: step.stepId,
301
- // @ts-expect-error Untyped
302
- context: step
303
- };
304
- }
305
- registerBlock(
306
- defineBlock(`workflow:${data.workflowId}`, {
307
- group: "workflow",
308
- schema: {
309
- type: "object",
310
- properties: {
311
- props: { type: "object" },
312
- slots: {
313
- type: "object",
314
- properties: slotProperties
315
- }
316
- },
317
- required: ["props", "slots"],
318
- additionalProperties: true
319
- },
320
- initialValue: () => ({
321
- props: {},
322
- slots: Object.fromEntries(
323
- allSteps.map((step) => [
324
- step.stepId,
325
- [
326
- getDefaultBlockForAction(
327
- step.actionType,
328
- step.actionConfig,
329
- createStableStepBlockId(data.workflowId, step.stepId)
330
- )
331
- ]
332
- ])
333
- )
334
- }),
335
- presets,
336
- renderer() {
337
- return null;
338
- }
339
- })
340
- );
341
- _registeredRootBlocks.add(data.workflowId);
342
- }
343
- async function initWorkflowState(data) {
344
- const { blocks } = await deps.themeState.resolve(
345
- "overlay",
346
- `workflow:${data.workflowId}`
347
- );
348
- if (blocks.length === 0) return;
349
- editor.onBlockPresetChange(blocks[0].id, (preset) => {
350
- if (!preset) {
351
- return;
352
- }
353
- updateState({
354
- // @ts-expect-error Untyped
355
- currentStepId: preset.state,
356
- // @ts-expect-error Untyped
357
- currentInteraction: { ...preset == null ? void 0 : preset.context }
358
- });
359
- });
360
- }
361
- const service = {
362
- // State signals (readonly)
363
- state: _state,
364
- /** True when the portal is running inside the Blocks Editor (editorMode dep
365
- * or ?trigger=... URL param). Consumers use this to suppress auto-start. */
366
- isPreviewMode: isEditorMode,
367
- // Computed signals
368
- isActive,
369
- isLoading,
370
- isCompleted,
371
- hasError,
372
- canRespond,
373
- currentInteraction,
374
- currentStepInfo,
375
- // Methods
376
- async startFlow(flowType, context, workflowId) {
377
- const isDryRun = isEditorMode;
378
- updateState({
379
- status: "loading",
380
- error: null,
381
- flowParams: context,
382
- flowType,
383
- dryRun: isDryRun,
384
- firstPollReason: null
385
- });
386
- try {
387
- const data = await api.start(
388
- flowType,
389
- context,
390
- workflowId,
391
- isDryRun || void 0
392
- );
393
- if (data.status === "no_workflow") {
394
- service.reset();
395
- return;
396
- }
397
- if (data.status === "completed_silently") {
398
- updateState({
399
- status: "completed",
400
- currentStepId: null,
401
- currentInteraction: null,
402
- result: null,
403
- invalidatedResources: data.invalidatedResources ?? [],
404
- outcome: data.outcome || null,
405
- completionData: data.completionData || null
406
- });
407
- return;
408
- }
409
- initWorkflowRootBlock(data);
410
- await initWorkflowState(data);
411
- handleFlowResponse(data);
412
- maybeApplyPreviewStep(flowType, data);
413
- } catch (error) {
414
- handleError(error);
415
- }
416
- },
417
- async respond(responseType, responseData) {
418
- const { runId, runVersion, currentInteraction: currentInteraction2 } = _state.value;
419
- if (!runId || !currentInteraction2) {
420
- throw new Error("No active interaction to respond to");
421
- }
422
- updateState({ status: "loading" });
423
- try {
424
- const data = await api.respond(
425
- runId,
426
- currentInteraction2.interactionId,
427
- responseType,
428
- responseData,
429
- runVersion ?? void 0
430
- );
431
- if (_state.value.firstPollReason == null && currentInteraction2.actionType === "poll") {
432
- const reason = responseData == null ? void 0 : responseData.reason;
433
- if (typeof reason === "string" && reason.length > 0) {
434
- updateState({ firstPollReason: reason });
435
- }
436
- }
437
- handleFlowResponse(data);
438
- } catch (error) {
439
- handleError(error);
440
- }
441
- },
442
- async cancel() {
443
- const { runId, status } = _state.value;
444
- const isTerminal = status === "completed" || status === "error";
445
- if (runId && !isTerminal) {
446
- try {
447
- const data = await api.cancel(runId);
448
- if (data.outcome) {
449
- updateState({
450
- status: "completed",
451
- flowType: null,
452
- workflowId: null,
453
- workflowVersion: null,
454
- runId: null,
455
- runVersion: null,
456
- currentStepId: null,
457
- currentInteraction: null,
458
- error: null,
459
- result: null,
460
- flowParams: null,
461
- invalidatedResources: data.invalidatedResources ?? [],
462
- outcome: data.outcome || null,
463
- dryRun: null
464
- });
465
- return;
466
- }
467
- } catch {
468
- }
469
- }
470
- service.reset();
471
- },
472
- reset() {
473
- _backendInteractionId.value = null;
474
- updateState({
475
- status: "idle",
476
- flowType: null,
477
- workflowId: null,
478
- workflowVersion: null,
479
- runId: null,
480
- runVersion: null,
481
- currentStepId: null,
482
- currentInteraction: null,
483
- error: null,
484
- result: null,
485
- flowParams: null,
486
- invalidatedResources: [],
487
- outcome: null,
488
- completionData: null,
489
- firstPollReason: null
490
- });
491
- },
492
- async refresh() {
493
- const { runId } = _state.value;
494
- if (!runId) return;
495
- try {
496
- const data = await api.getState(runId);
497
- handleFlowResponse(data);
498
- } catch (error) {
499
- handleError(error);
500
- }
501
- }
502
- };
503
- if (previewWorkflowTrigger) {
504
- void service.startFlow(
505
- previewWorkflowTrigger,
506
- {},
507
- previewWorkflowId ?? void 0
508
- );
509
- }
510
- return service;
511
- }
512
- class JuoWorkflowElement extends HTMLElement {
513
- constructor() {
514
- super(...arguments);
515
- __publicField(this, "workflowService", null);
516
- __publicField(this, "unsubscribeContext", null);
517
- __publicField(this, "unsubscribeEffect", null);
518
- /** Tracks which step is currently mounted to avoid unnecessary DOM teardown. */
519
- __publicField(this, "renderedStepId", null);
520
- /**
521
- * Optional function to wrap rendered content (step blocks).
522
- * Must be set before workflow becomes active for correct behavior.
523
- */
524
- __publicField(this, "blockWrapper", null);
525
- }
526
- connectedCallback() {
527
- this.style.display = "contents";
528
- this.unsubscribeContext = subscribeContext(
529
- this,
530
- WorkflowServiceContext,
531
- (service) => {
532
- this.workflowService = service;
533
- this.setupSubscription();
534
- }
535
- );
536
- }
537
- disconnectedCallback() {
538
- var _a, _b;
539
- (_a = this.unsubscribeContext) == null ? void 0 : _a.call(this);
540
- (_b = this.unsubscribeEffect) == null ? void 0 : _b.call(this);
541
- this.unsubscribeContext = null;
542
- this.unsubscribeEffect = null;
543
- this.renderedStepId = null;
544
- }
545
- setupSubscription() {
546
- var _a;
547
- if (!this.workflowService) return;
548
- (_a = this.unsubscribeEffect) == null ? void 0 : _a.call(this);
549
- this.unsubscribeEffect = E(() => {
550
- var _a2, _b;
551
- const status = ((_a2 = this.workflowService) == null ? void 0 : _a2.state.value.status) ?? "idle";
552
- const stepInfo = (_b = this.workflowService) == null ? void 0 : _b.currentStepInfo.value;
553
- const stepId = (stepInfo == null ? void 0 : stepInfo.stepId) ?? null;
554
- queueMicrotask(() => {
555
- if (this.isConnected) {
556
- this.render(status, stepId);
557
- }
558
- });
559
- });
560
- }
561
- render(status, stepId) {
562
- var _a;
563
- if ((status === "active" || status === "loading") && stepId) {
564
- if (this.renderedStepId !== stepId) {
565
- this.innerHTML = "";
566
- this.renderedStepId = stepId;
567
- const extensionRoot = document.createElement(
568
- "juo-extension-root"
569
- );
570
- extensionRoot.setAttribute("surface", "overlay");
571
- extensionRoot.setAttribute("name", stepId);
572
- extensionRoot.blockWrapper = this.blockWrapper;
573
- this.appendChild(extensionRoot);
574
- }
575
- return;
576
- }
577
- this.innerHTML = "";
578
- this.renderedStepId = null;
579
- if (status === "error") {
580
- const error = ((_a = this.workflowService) == null ? void 0 : _a.state.value.error) ?? "Unknown error";
581
- this.dispatchEvent(
582
- new CustomEvent("juo-workflow-error", {
583
- bubbles: true,
584
- composed: true,
585
- detail: { error }
586
- })
587
- );
588
- }
589
- }
590
- }
591
- if (typeof customElements !== "undefined" && !customElements.get("juo-workflow")) {
592
- customElements.define("juo-workflow", JuoWorkflowElement);
593
- }
594
- export {
595
- TranslationContext as T,
596
- WorkflowServiceContext as W,
597
- createWorkflowApiAdapter as a,
598
- createWorkflowService as b,
599
- createTranslationService as c,
600
- getDefaultBlockForAction as g,
601
- hasDefaultBlockForAction as h
602
- };