@juo/blocks 1.11.0-alpha.0 → 1.12.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,617 +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-WoHZvF51.js";
5
- import { a as createEditorBridge, e as createSelectBlockMessage } from "./bridge-w5KQ-czl.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
- const baseLabelFor = (step) => {
297
- var _a;
298
- return ((_a = getDefinedBlocks().find((b) => b.name === `action:${step.actionType}`)) == null ? void 0 : _a.displayName) ?? step.stepId;
299
- };
300
- const totalCounts = /* @__PURE__ */ new Map();
301
- for (const step of allSteps) {
302
- const baseLabel = baseLabelFor(step);
303
- totalCounts.set(baseLabel, (totalCounts.get(baseLabel) ?? 0) + 1);
304
- }
305
- const seenCounts = /* @__PURE__ */ new Map();
306
- for (const step of allSteps) {
307
- const baseLabel = baseLabelFor(step);
308
- const occurrence = (seenCounts.get(baseLabel) ?? 0) + 1;
309
- seenCounts.set(baseLabel, occurrence);
310
- const isDuplicated = (totalCounts.get(baseLabel) ?? 0) > 1;
311
- const label = isDuplicated ? `${baseLabel} (${occurrence})` : baseLabel;
312
- presets[step.stepId] = {
313
- label,
314
- description: step.actionType,
315
- state: step.stepId,
316
- // @ts-expect-error Untyped
317
- context: step
318
- };
319
- }
320
- registerBlock(
321
- defineBlock(`workflow:${data.workflowId}`, {
322
- group: "workflow",
323
- schema: {
324
- type: "object",
325
- properties: {
326
- props: { type: "object" },
327
- slots: {
328
- type: "object",
329
- properties: slotProperties
330
- }
331
- },
332
- required: ["props", "slots"],
333
- additionalProperties: true
334
- },
335
- initialValue: () => ({
336
- props: {},
337
- slots: Object.fromEntries(
338
- allSteps.map((step) => [
339
- step.stepId,
340
- [
341
- getDefaultBlockForAction(
342
- step.actionType,
343
- step.actionConfig,
344
- createStableStepBlockId(data.workflowId, step.stepId)
345
- )
346
- ]
347
- ])
348
- )
349
- }),
350
- presets,
351
- renderer() {
352
- return null;
353
- }
354
- })
355
- );
356
- _registeredRootBlocks.add(data.workflowId);
357
- }
358
- async function initWorkflowState(data) {
359
- const { blocks } = await deps.themeState.resolve(
360
- "overlay",
361
- `workflow:${data.workflowId}`
362
- );
363
- if (blocks.length === 0) return;
364
- editor.onBlockPresetChange(blocks[0].id, (preset) => {
365
- if (!preset) {
366
- return;
367
- }
368
- updateState({
369
- // @ts-expect-error Untyped
370
- currentStepId: preset.state,
371
- // @ts-expect-error Untyped
372
- currentInteraction: { ...preset == null ? void 0 : preset.context }
373
- });
374
- });
375
- }
376
- const service = {
377
- // State signals (readonly)
378
- state: _state,
379
- /** True when the portal is running inside the Blocks Editor (editorMode dep
380
- * or ?trigger=... URL param). Consumers use this to suppress auto-start. */
381
- isPreviewMode: isEditorMode,
382
- // Computed signals
383
- isActive,
384
- isLoading,
385
- isCompleted,
386
- hasError,
387
- canRespond,
388
- currentInteraction,
389
- currentStepInfo,
390
- // Methods
391
- async startFlow(flowType, context, workflowId) {
392
- const isDryRun = isEditorMode;
393
- updateState({
394
- status: "loading",
395
- error: null,
396
- flowParams: context,
397
- flowType,
398
- dryRun: isDryRun,
399
- firstPollReason: null
400
- });
401
- try {
402
- const data = await api.start(
403
- flowType,
404
- context,
405
- workflowId,
406
- isDryRun || void 0
407
- );
408
- if (data.status === "no_workflow") {
409
- service.reset();
410
- return;
411
- }
412
- if (data.status === "completed_silently") {
413
- updateState({
414
- status: "completed",
415
- currentStepId: null,
416
- currentInteraction: null,
417
- result: null,
418
- invalidatedResources: data.invalidatedResources ?? [],
419
- outcome: data.outcome || null,
420
- completionData: data.completionData || null
421
- });
422
- return;
423
- }
424
- initWorkflowRootBlock(data);
425
- await initWorkflowState(data);
426
- handleFlowResponse(data);
427
- maybeApplyPreviewStep(flowType, data);
428
- } catch (error) {
429
- handleError(error);
430
- }
431
- },
432
- async respond(responseType, responseData) {
433
- const { runId, runVersion, currentInteraction: currentInteraction2 } = _state.value;
434
- if (!runId || !currentInteraction2) {
435
- throw new Error("No active interaction to respond to");
436
- }
437
- updateState({ status: "loading" });
438
- try {
439
- const data = await api.respond(
440
- runId,
441
- currentInteraction2.interactionId,
442
- responseType,
443
- responseData,
444
- runVersion ?? void 0
445
- );
446
- if (_state.value.firstPollReason == null && currentInteraction2.actionType === "poll") {
447
- const reason = responseData == null ? void 0 : responseData.reason;
448
- if (typeof reason === "string" && reason.length > 0) {
449
- updateState({ firstPollReason: reason });
450
- }
451
- }
452
- handleFlowResponse(data);
453
- } catch (error) {
454
- handleError(error);
455
- }
456
- },
457
- async cancel() {
458
- const { runId, status } = _state.value;
459
- const isTerminal = status === "completed" || status === "error";
460
- if (runId && !isTerminal) {
461
- try {
462
- const data = await api.cancel(runId);
463
- if (data.outcome) {
464
- updateState({
465
- status: "completed",
466
- flowType: null,
467
- workflowId: null,
468
- workflowVersion: null,
469
- runId: null,
470
- runVersion: null,
471
- currentStepId: null,
472
- currentInteraction: null,
473
- error: null,
474
- result: null,
475
- flowParams: null,
476
- invalidatedResources: data.invalidatedResources ?? [],
477
- outcome: data.outcome || null,
478
- dryRun: null
479
- });
480
- return;
481
- }
482
- } catch {
483
- }
484
- }
485
- service.reset();
486
- },
487
- reset() {
488
- _backendInteractionId.value = null;
489
- updateState({
490
- status: "idle",
491
- flowType: null,
492
- workflowId: null,
493
- workflowVersion: null,
494
- runId: null,
495
- runVersion: null,
496
- currentStepId: null,
497
- currentInteraction: null,
498
- error: null,
499
- result: null,
500
- flowParams: null,
501
- invalidatedResources: [],
502
- outcome: null,
503
- completionData: null,
504
- firstPollReason: null
505
- });
506
- },
507
- async refresh() {
508
- const { runId } = _state.value;
509
- if (!runId) return;
510
- try {
511
- const data = await api.getState(runId);
512
- handleFlowResponse(data);
513
- } catch (error) {
514
- handleError(error);
515
- }
516
- }
517
- };
518
- if (previewWorkflowTrigger) {
519
- void service.startFlow(
520
- previewWorkflowTrigger,
521
- {},
522
- previewWorkflowId ?? void 0
523
- );
524
- }
525
- return service;
526
- }
527
- class JuoWorkflowElement extends HTMLElement {
528
- constructor() {
529
- super(...arguments);
530
- __publicField(this, "workflowService", null);
531
- __publicField(this, "unsubscribeContext", null);
532
- __publicField(this, "unsubscribeEffect", null);
533
- /** Tracks which step is currently mounted to avoid unnecessary DOM teardown. */
534
- __publicField(this, "renderedStepId", null);
535
- /**
536
- * Optional function to wrap rendered content (step blocks).
537
- * Must be set before workflow becomes active for correct behavior.
538
- */
539
- __publicField(this, "blockWrapper", null);
540
- }
541
- connectedCallback() {
542
- this.style.display = "contents";
543
- this.unsubscribeContext = subscribeContext(
544
- this,
545
- WorkflowServiceContext,
546
- (service) => {
547
- this.workflowService = service;
548
- this.setupSubscription();
549
- }
550
- );
551
- }
552
- disconnectedCallback() {
553
- var _a, _b;
554
- (_a = this.unsubscribeContext) == null ? void 0 : _a.call(this);
555
- (_b = this.unsubscribeEffect) == null ? void 0 : _b.call(this);
556
- this.unsubscribeContext = null;
557
- this.unsubscribeEffect = null;
558
- this.renderedStepId = null;
559
- }
560
- setupSubscription() {
561
- var _a;
562
- if (!this.workflowService) return;
563
- (_a = this.unsubscribeEffect) == null ? void 0 : _a.call(this);
564
- this.unsubscribeEffect = E(() => {
565
- var _a2, _b;
566
- const status = ((_a2 = this.workflowService) == null ? void 0 : _a2.state.value.status) ?? "idle";
567
- const stepInfo = (_b = this.workflowService) == null ? void 0 : _b.currentStepInfo.value;
568
- const stepId = (stepInfo == null ? void 0 : stepInfo.stepId) ?? null;
569
- queueMicrotask(() => {
570
- if (this.isConnected) {
571
- this.render(status, stepId);
572
- }
573
- });
574
- });
575
- }
576
- render(status, stepId) {
577
- var _a;
578
- if ((status === "active" || status === "loading") && stepId) {
579
- if (this.renderedStepId !== stepId) {
580
- this.innerHTML = "";
581
- this.renderedStepId = stepId;
582
- const extensionRoot = document.createElement(
583
- "juo-extension-root"
584
- );
585
- extensionRoot.setAttribute("surface", "overlay");
586
- extensionRoot.setAttribute("name", stepId);
587
- extensionRoot.blockWrapper = this.blockWrapper;
588
- this.appendChild(extensionRoot);
589
- }
590
- return;
591
- }
592
- this.innerHTML = "";
593
- this.renderedStepId = null;
594
- if (status === "error") {
595
- const error = ((_a = this.workflowService) == null ? void 0 : _a.state.value.error) ?? "Unknown error";
596
- this.dispatchEvent(
597
- new CustomEvent("juo-workflow-error", {
598
- bubbles: true,
599
- composed: true,
600
- detail: { error }
601
- })
602
- );
603
- }
604
- }
605
- }
606
- if (typeof customElements !== "undefined" && !customElements.get("juo-workflow")) {
607
- customElements.define("juo-workflow", JuoWorkflowElement);
608
- }
609
- export {
610
- TranslationContext as T,
611
- WorkflowServiceContext as W,
612
- createWorkflowApiAdapter as a,
613
- createWorkflowService as b,
614
- createTranslationService as c,
615
- getDefaultBlockForAction as g,
616
- hasDefaultBlockForAction as h
617
- };