api_binding_ui 1.0.0 → 2.0.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.
Files changed (75) hide show
  1. package/README.md +74 -384
  2. package/SKILL.md +49 -294
  3. package/dist/framework/contract.d.ts +268 -0
  4. package/dist/framework/engine.d.ts +88 -0
  5. package/dist/framework/expr.d.ts +34 -0
  6. package/dist/framework/loadCatalog.d.ts +6 -0
  7. package/dist/framework/names.d.ts +7 -0
  8. package/dist/framework/openapi.d.ts +7 -0
  9. package/dist/framework/runner.d.ts +33 -0
  10. package/dist/framework/schema.d.ts +22 -0
  11. package/dist/framework/store.d.ts +17 -0
  12. package/dist/framework/validate.d.ts +7 -0
  13. package/dist/index-e9twc25y.js +754 -0
  14. package/dist/index-e9twc25y.js.map +16 -0
  15. package/dist/index.d.ts +11 -26
  16. package/dist/index.js +345 -3790
  17. package/dist/index.js.map +14 -0
  18. package/dist/runtime/BindingProvider.d.ts +42 -0
  19. package/dist/runtime/BindingScreen.d.ts +23 -0
  20. package/dist/runtime/hooks.d.ts +22 -0
  21. package/dist/runtime/screenStore.d.ts +7 -0
  22. package/dist/studio/ApiPicker.d.ts +15 -0
  23. package/dist/studio/BindingStudio.d.ts +21 -0
  24. package/dist/studio/BindingWidget.d.ts +9 -0
  25. package/dist/studio/Collapse.d.ts +6 -0
  26. package/dist/studio/ComponentList.d.ts +10 -0
  27. package/dist/studio/FindAnything.d.ts +7 -0
  28. package/dist/studio/Inspector.d.ts +12 -0
  29. package/dist/studio/Navigator.d.ts +17 -0
  30. package/dist/studio/SettingsPanel.d.ts +15 -0
  31. package/dist/studio/Tip.d.ts +23 -0
  32. package/dist/studio/editors/ConsumeEditor.d.ts +4 -0
  33. package/dist/studio/editors/RequestInputs.d.ts +11 -0
  34. package/dist/studio/editors/ScopedEditor.d.ts +3 -0
  35. package/dist/studio/editors/SlotSection.d.ts +3 -0
  36. package/dist/studio/editors/SourceSelect.d.ts +10 -0
  37. package/dist/studio/editors/StepCard.d.ts +12 -0
  38. package/dist/studio/editors/TriggerEditor.d.ts +4 -0
  39. package/dist/studio/editors/types.d.ts +14 -0
  40. package/dist/studio/icons.d.ts +65 -0
  41. package/dist/studio/index.css +2021 -0
  42. package/dist/studio/index.d.ts +3 -0
  43. package/dist/studio/index.js +2806 -0
  44. package/dist/studio/index.js.map +33 -0
  45. package/dist/studio/useBindings.d.ts +20 -0
  46. package/dist/studio/useCatalog.d.ts +15 -0
  47. package/dist/studio/useGlide.d.ts +11 -0
  48. package/dist/studio/useSettings.d.ts +8 -0
  49. package/package.json +20 -9
  50. package/spec/FRAMEWORK.md +271 -0
  51. package/dist/api-binding-ui.css +0 -244
  52. package/dist/components/ApiBindingWorkspace.d.ts +0 -10
  53. package/dist/components/ApiMappings.d.ts +0 -21
  54. package/dist/components/BindApiButton.d.ts +0 -41
  55. package/dist/components/RequestPreview.d.ts +0 -7
  56. package/dist/components/SimulationDialog.d.ts +0 -8
  57. package/dist/components/WorkspaceContext.d.ts +0 -7
  58. package/dist/components/ui/AppGrid.d.ts +0 -27
  59. package/dist/components/ui/Badge.d.ts +0 -9
  60. package/dist/components/ui/Banner.d.ts +0 -8
  61. package/dist/components/ui/Button.d.ts +0 -31
  62. package/dist/components/ui/Card.d.ts +0 -14
  63. package/dist/components/ui/Dialog.d.ts +0 -61
  64. package/dist/components/ui/Icons.d.ts +0 -21
  65. package/dist/components/ui/Input.d.ts +0 -31
  66. package/dist/core/bindingContext.d.ts +0 -67
  67. package/dist/core/catalog.d.ts +0 -18
  68. package/dist/core/componentId.d.ts +0 -12
  69. package/dist/core/model.d.ts +0 -57
  70. package/dist/core/transport.d.ts +0 -33
  71. package/dist/core/valueCoercion.d.ts +0 -14
  72. package/dist/hooks/useAppliedBinding.d.ts +0 -6
  73. package/dist/index.css +0 -244
  74. package/dist/style.css +0 -244
  75. package/dist/types.d.ts +0 -232
package/dist/index.js CHANGED
@@ -1,3790 +1,345 @@
1
- var __defProp = Object.defineProperty;
2
- var __returnValue = (v) => v;
3
- function __exportSetter(name, newValue) {
4
- this[name] = __returnValue.bind(null, newValue);
5
- }
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, {
9
- get: all[name],
10
- enumerable: true,
11
- configurable: true,
12
- set: __exportSetter.bind(all, name)
13
- });
14
- };
15
-
16
- // src/components/ui/Icons.tsx
17
- var exports_Icons = {};
18
- __export(exports_Icons, {
19
- AlertCircleIcon: () => AlertCircleIcon,
20
- AlertTriangleIcon: () => AlertTriangleIcon,
21
- ArrowDownIcon: () => ArrowDownIcon,
22
- ArrowUpIcon: () => ArrowUpIcon,
23
- CheckIcon: () => CheckIcon,
24
- LinkIcon: () => LinkIcon,
25
- PencilIcon: () => PencilIcon,
26
- PlayIcon: () => PlayIcon,
27
- PlusIcon: () => PlusIcon,
28
- SearchIcon: () => SearchIcon,
29
- SettingsIcon: () => SettingsIcon,
30
- Trash2Icon: () => Trash2Icon,
31
- XIcon: () => XIcon,
32
- iconMap: () => iconMap,
33
- renderIcon: () => renderIcon
34
- });
35
-
36
- // src/types.ts
37
- var BINDING_CHANNEL = "api-binding-studio";
38
- var BINDING_READY = "api-binding-ready";
39
- var BINDING_CONTEXT = "api-binding-context";
40
- var BINDING_APPLIED = "api-binding-applied";
41
- var BINDING_SELECTION_UPDATE = "api-binding-selection-update";
42
- // src/core/componentId.ts
43
- function componentId(c) {
44
- if (c.kind === "button")
45
- return `button:${c.name}`;
46
- return c.name;
47
- }
48
- function isButtonId(id) {
49
- return id.startsWith("button:");
50
- }
51
- function parseComponentId(id) {
52
- if (id.startsWith("button:")) {
53
- return { kind: "button", name: id.slice(7) };
54
- }
55
- if (id.startsWith("field:")) {
56
- return { kind: "field", name: id.slice(6) };
57
- }
58
- return { kind: "field", name: id };
59
- }
60
- function normalizeComponent(id) {
61
- if (id.startsWith("field:"))
62
- return id.slice(6);
63
- return id;
64
- }
65
-
66
- // src/core/bindingContext.ts
67
- var FREE_TEXT_SOURCE = "freeText";
68
- var FREE_TEXT_OPTION = { value: FREE_TEXT_SOURCE, label: "Free Text" };
69
- function storageKey(key) {
70
- return `api-binding-snapshot:${key}`;
71
- }
72
- function storeSnapshot(snapshot) {
73
- try {
74
- localStorage.setItem(storageKey(snapshot.key), JSON.stringify(snapshot));
75
- } catch {}
76
- }
77
- function readSnapshot(key) {
78
- try {
79
- const raw = localStorage.getItem(storageKey(key));
80
- return raw ? JSON.parse(raw) : null;
81
- } catch {
82
- return null;
83
- }
84
- }
85
- function windowIds({
86
- appId,
87
- windowId,
88
- gridId,
89
- parentWindowIds
90
- }) {
91
- const parts = [appId];
92
- if (gridId)
93
- parts.push(gridId);
94
- if (parentWindowIds?.length)
95
- parts.push(...parentWindowIds);
96
- parts.push(windowId);
97
- return {
98
- appId,
99
- windowId,
100
- gridId,
101
- parentWindowIds,
102
- key: parts.join(".")
103
- };
104
- }
105
- function gridPropertyPaths({ gridId, rows }) {
106
- if (!rows || rows.length === 0)
107
- return [];
108
- const paths = new Set;
109
- function collect(obj, prefix) {
110
- if (obj === null || obj === undefined)
111
- return;
112
- if (typeof obj === "object" && !Array.isArray(obj)) {
113
- for (const [k, v] of Object.entries(obj)) {
114
- const next = prefix ? `${prefix}.${k}` : k;
115
- if (typeof v === "object" && v !== null && !Array.isArray(v)) {
116
- collect(v, next);
117
- } else {
118
- paths.add(`${gridId}.${next}`);
119
- }
120
- }
121
- }
122
- }
123
- rows.slice(0, 5).forEach((r) => collect(r, ""));
124
- return [...paths].sort();
125
- }
126
- function contextComponents(snapshot) {
127
- const fields = snapshot.components.map((c) => ({
128
- value: c.name,
129
- label: c.label || c.name
130
- }));
131
- const buttons = snapshot.buttons.map((b) => ({
132
- value: componentId({ kind: "button", name: b }),
133
- label: `Button: ${b}`
134
- }));
135
- return [...fields, ...buttons];
136
- }
137
- function sourceComponents(snapshot) {
138
- const fields = snapshot.components.map((c) => ({
139
- value: c.name,
140
- label: c.label || c.name
141
- }));
142
- const gridOption = snapshot.grid ? [{ value: snapshot.grid.id, label: snapshot.grid.label || snapshot.grid.id }] : [];
143
- const parents = (snapshot.contexts ?? []).flatMap((p) => p.components.map((c) => ({
144
- value: `${p.id}.${c.name}`,
145
- label: `${p.label || p.id} → ${c.label || c.name}`
146
- })));
147
- return [...fields, ...gridOption, ...parents];
148
- }
149
- function findEnclosingContextElement(triggerElement, options) {
150
- if (options?.rootElement)
151
- return options.rootElement;
152
- if (options?.containerRef?.current)
153
- return options.containerRef.current;
154
- if (options?.formId) {
155
- const el = document.getElementById(options.formId);
156
- if (el)
157
- return el;
158
- }
159
- if (options?.containerSelector && triggerElement) {
160
- const el = triggerElement.closest(options.containerSelector) || document.querySelector(options.containerSelector);
161
- if (el)
162
- return el;
163
- }
164
- if (!triggerElement)
165
- return document.body;
166
- const dialog = triggerElement.closest('[role="dialog"], [role="alertdialog"], dialog, .ds-dialog, [data-modal], [data-drawer], [data-panel]');
167
- if (dialog)
168
- return dialog;
169
- const form = triggerElement.closest('form, [role="form"]');
170
- if (form)
171
- return form;
172
- let curr = triggerElement.parentElement;
173
- let candidateWithInputs = null;
174
- while (curr && curr !== document.body && curr !== document.documentElement) {
175
- if (curr.matches('[data-screen], [data-view], [role="region"], main, [role="main"], article, section, .screen, .view, .page, .page-container, .card, .panel')) {
176
- return curr;
177
- }
178
- const inputs = curr.querySelectorAll("[name], input:not([type='hidden']), select, textarea, [role='combobox']");
179
- if (inputs.length > 0 && !candidateWithInputs) {
180
- candidateWithInputs = curr;
181
- }
182
- curr = curr.parentElement;
183
- }
184
- if (candidateWithInputs)
185
- return candidateWithInputs;
186
- return document.querySelector("main") || document.getElementById("root") || document.body;
187
- }
188
- function discoverFormComponents(root) {
189
- if (!root)
190
- return [];
191
- const components = [];
192
- const seen = new Set;
193
- function extractLabel(el, name) {
194
- const labelled = el.labels?.[0];
195
- const byId = el.getAttribute("aria-labelledby");
196
- const labelById = byId ? byId.split(" ").map((id) => document.getElementById(id)?.textContent).filter(Boolean).join(" ") : null;
197
- const parentLabel = el.closest("label");
198
- const ariaLabel = el.getAttribute("aria-label");
199
- const placeholder = el.getAttribute("placeholder");
200
- const title = el.getAttribute("title");
201
- const raw = labelled?.textContent ?? labelById ?? ariaLabel ?? (parentLabel ? Array.from(parentLabel.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => n.textContent).join("").trim() : null) ?? placeholder ?? title ?? name;
202
- return raw.replace(/\*/g, "").trim() || name;
203
- }
204
- root.querySelectorAll("[name]").forEach((el) => {
205
- const name = el.getAttribute("name");
206
- if (!name || seen.has(name))
207
- return;
208
- if (el instanceof HTMLInputElement && (el.type === "submit" || el.type === "reset" || el.type === "button"))
209
- return;
210
- seen.add(name);
211
- const label = extractLabel(el, name);
212
- const role = el.getAttribute("role");
213
- const rawType = role ?? (el instanceof HTMLInputElement ? el.type : el.tagName.toLowerCase());
214
- const isDropdown = rawType === "select" || rawType === "combobox" || rawType === "listbox" || el.tagName.toLowerCase() === "select";
215
- const isTextArea = rawType === "textarea" || el.tagName.toLowerCase() === "textarea";
216
- const isCheckbox = rawType === "checkbox" || rawType === "switch";
217
- const kind = isDropdown ? "dropdown" : isTextArea ? "text-area" : isCheckbox ? "checkbox" : "text";
218
- const isRequired = el.hasAttribute("required") || el.getAttribute("aria-required") === "true" || (el.getAttribute("aria-label")?.includes("*") ?? false);
219
- let value;
220
- if (el instanceof HTMLInputElement) {
221
- value = el.type === "checkbox" ? String(el.checked) : el.value;
222
- } else if (el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {
223
- value = el.value;
224
- } else {
225
- value = el.getAttribute("data-value") ?? el.getAttribute("aria-valuenow") ?? el.textContent?.trim() ?? undefined;
226
- }
227
- components.push({
228
- name,
229
- label,
230
- type: rawType,
231
- kind,
232
- readable: isDropdown,
233
- required: isRequired,
234
- value: value || undefined
235
- });
236
- });
237
- root.querySelectorAll('[role="combobox"]:not([name]), [role="textbox"]:not([name]), [role="checkbox"]:not([name]), [role="switch"]:not([name]), select:not([name]), textarea:not([name]), input:not([name]):not([type="submit"]):not([type="button"]):not([type="reset"])').forEach((el) => {
238
- const id = el.id || el.getAttribute("data-testid") || el.getAttribute("data-field");
239
- const ariaLabel = el.getAttribute("aria-label");
240
- const name = id || (ariaLabel ? ariaLabel.toLowerCase().replace(/[^a-z0-9]/g, "_") : undefined);
241
- if (!name || seen.has(name))
242
- return;
243
- seen.add(name);
244
- const label = extractLabel(el, name);
245
- const role = el.getAttribute("role");
246
- const rawType = role ?? (el instanceof HTMLInputElement ? el.type : el.tagName.toLowerCase());
247
- const isDropdown = rawType === "select" || rawType === "combobox" || rawType === "listbox";
248
- const isTextArea = rawType === "textarea" || el.tagName.toLowerCase() === "textarea";
249
- const isCheckbox = rawType === "checkbox" || rawType === "switch";
250
- const kind = isDropdown ? "dropdown" : isTextArea ? "text-area" : isCheckbox ? "checkbox" : "text";
251
- const isRequired = el.hasAttribute("required") || el.getAttribute("aria-required") === "true";
252
- components.push({
253
- name,
254
- label,
255
- type: rawType,
256
- kind,
257
- readable: isDropdown,
258
- required: isRequired,
259
- value: el.value || el.getAttribute("data-value") || undefined
260
- });
261
- });
262
- return components;
263
- }
264
- function discoverButtons(root) {
265
- if (!root)
266
- return ["Submit", "Cancel"];
267
- const TOOL_BUTTONS = new Set([
268
- "Bind API",
269
- "Dynamic Bind",
270
- "API Binding",
271
- "Close",
272
- "Minimize",
273
- "Maximize",
274
- "Help",
275
- "Dismiss"
276
- ]);
277
- const buttons = [];
278
- root.querySelectorAll("button, [role='button'], input[type='submit']").forEach((b) => {
279
- if (b.offsetParent === null && !b.getAttribute("aria-label"))
280
- return;
281
- if (b.closest(".ds-dialog__title-adornment, .ds-dialog__heading, .ds-dialog__close, .ds-dialog__minimize, [data-dismiss], header .close-btn")) {
282
- return;
283
- }
284
- const text = (b.textContent ?? "").trim() || b.getAttribute("aria-label") || (b instanceof HTMLInputElement ? b.value : "");
285
- if (!text || TOOL_BUTTONS.has(text) || text.includes("Bind API") || buttons.includes(text))
286
- return;
287
- if (text.length <= 1 && !/[a-zA-Z0-9]/.test(text))
288
- return;
289
- buttons.push(text);
290
- });
291
- return buttons.length ? buttons : ["Submit", "Cancel"];
292
- }
293
- function buildSnapshot({
294
- ids,
295
- source,
296
- parent,
297
- root,
298
- grid,
299
- kind = "screen",
300
- trigger,
301
- contexts,
302
- components: explicitComponents,
303
- buttons: explicitButtons
304
- }) {
305
- const discoveredComponents = discoverFormComponents(root);
306
- const discoveredButtons = discoverButtons(root);
307
- const components = explicitComponents && explicitComponents.length > 0 ? explicitComponents : discoveredComponents;
308
- const buttons = explicitButtons && explicitButtons.length > 0 ? explicitButtons : discoveredButtons;
309
- return {
310
- key: ids.key,
311
- name: `${source} - API Binding : ${parent}`,
312
- ids: {
313
- appId: ids.appId,
314
- windowId: ids.windowId,
315
- gridId: ids.gridId,
316
- parentWindowIds: ids.parentWindowIds
317
- },
318
- source,
319
- parent,
320
- kind,
321
- trigger,
322
- components,
323
- buttons,
324
- contexts,
325
- grid: grid ? {
326
- id: grid.gridId,
327
- label: grid.label || grid.gridId,
328
- columns: grid.columns ?? (grid.rows.length ? Object.keys(grid.rows[0]) : []),
329
- rows: grid.rows
330
- } : undefined,
331
- capturedAt: new Date().toISOString()
332
- };
333
- }
334
-
335
- // src/core/valueCoercion.ts
336
- var TRUE = new Set(["true", "1", "yes", "y"]);
337
- var FALSE = new Set(["false", "0", "no", "n"]);
338
- function baseType(type) {
339
- let t = type.replace(/\s*\|\s*(null|undefined)\b/g, "").trim();
340
- let array = false;
341
- const arr = t.match(/^Array<\s*([\s\S]+)\s*>$/);
342
- if (arr) {
343
- t = arr[1].trim();
344
- array = true;
345
- } else if (/\[\]$/.test(t)) {
346
- t = t.replace(/\[\]$/, "").trim();
347
- array = true;
348
- }
349
- return { base: t, array };
350
- }
351
- function coerceScalar(text, base) {
352
- const t = text.trim();
353
- if (/^number$/.test(base)) {
354
- if (t === "")
355
- return;
356
- const n = Number(t);
357
- return Number.isFinite(n) ? n : undefined;
358
- }
359
- if (/^boolean$/.test(base)) {
360
- const l = t.toLowerCase();
361
- return TRUE.has(l) ? true : FALSE.has(l) ? false : undefined;
362
- }
363
- return text;
364
- }
365
- function coerce(text, type) {
366
- const { base, array } = baseType(type);
367
- if (!array)
368
- return coerceScalar(text, base);
369
- const items = text.split(",").map((s) => s.trim()).filter((s) => s !== "");
370
- const out = items.map((s) => coerceScalar(s, base));
371
- if (out.some((v) => v === undefined))
372
- return;
373
- return out.length ? out : undefined;
374
- }
375
- function readPath(obj, path) {
376
- if (path === "")
377
- return obj;
378
- return path.split(".").reduce((curr, key) => {
379
- if (curr === null || curr === undefined || typeof curr !== "object")
380
- return;
381
- return curr[key];
382
- }, obj);
383
- }
384
- var asText = (v) => v === null || v === undefined ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
385
- var FREE_TEXT_SOURCE2 = "freeText";
386
- function resolveParam(p, ctx) {
387
- if (!p.source)
388
- return;
389
- let value;
390
- if (p.source === FREE_TEXT_SOURCE2) {
391
- value = p.property;
392
- } else if (ctx.grid && p.source === ctx.grid.gridId) {
393
- const prefix = `${ctx.grid.gridId}.`;
394
- if (!p.property.startsWith(prefix) || !ctx.grid.rows.length)
395
- return;
396
- const path = p.property.slice(prefix.length);
397
- const parts = ctx.grid.rows.map((r) => asText(readPath(r, path)));
398
- value = parts.every((s) => s === "") ? "" : parts.join(",");
399
- } else {
400
- const parents = ctx.parents ?? [];
401
- let parentId;
402
- let fieldName = p.source;
403
- for (const parent of parents) {
404
- if (p.source.startsWith(`${parent.id}.`)) {
405
- parentId = parent.id;
406
- fieldName = p.source.slice(parent.id.length + 1);
407
- break;
408
- }
409
- }
410
- const values = parentId ? parents.find((x) => x.id === parentId)?.values ?? {} : ctx.form;
411
- value = asText(values[fieldName]);
412
- }
413
- return value === "" ? undefined : value;
414
- }
415
- function setPath(target, path, value) {
416
- const keys = path.split(".");
417
- let cur = target;
418
- keys.slice(0, -1).forEach((k) => {
419
- if (!cur[k] || typeof cur[k] !== "object")
420
- cur[k] = {};
421
- cur = cur[k];
422
- });
423
- cur[keys[keys.length - 1]] = value;
424
- }
425
- function setArrayOfObjects(target, name, text, leafType) {
426
- const i = name.indexOf("[].");
427
- const head = name.slice(0, i);
428
- const rest = name.slice(i + 3);
429
- const values = text.split(",").map((s) => s.trim());
430
- const coerced = values.map((v) => v === "" ? undefined : coerce(v, leafType));
431
- if (coerced.some((v, idx) => v === undefined && values[idx] !== ""))
432
- return false;
433
- let arr = head.split(".").reduce((o, k) => o?.[k], target);
434
- if (!Array.isArray(arr)) {
435
- arr = [];
436
- setPath(target, head, arr);
437
- }
438
- coerced.forEach((v, idx) => {
439
- if (v === undefined)
440
- return;
441
- arr[idx] ??= {};
442
- if (rest.includes("[]."))
443
- setArrayOfObjects(arr[idx], rest, String(v), leafType);
444
- else
445
- setPath(arr[idx], rest, v);
446
- });
447
- return true;
448
- }
449
- function buildRequest(mappings, ctx, spec) {
450
- const out = { data: {}, params: {}, path: {} };
451
- const byName = new Map(spec.map((p) => [p.name, p]));
452
- for (const m of mappings) {
453
- const text = resolveParam(m, ctx);
454
- if (text === undefined)
455
- continue;
456
- const s = byName.get(m.param);
457
- const type = s?.type ?? "string";
458
- const target = s?.in === "query" ? out.params : s?.in === "path" ? out.path : out.data;
459
- if (m.param.includes("[].")) {
460
- setArrayOfObjects(target, m.param, text, type);
461
- continue;
462
- }
463
- const value = coerce(text, type);
464
- if (value === undefined)
465
- continue;
466
- setPath(target, m.param, value);
467
- }
468
- return out;
469
- }
470
-
471
- // src/core/model.ts
472
- var CONFIG_VERSION = 1;
473
- var draftKey = (key) => `api-binding:draft:${key}`;
474
- var appliedKey = (key) => `api-binding:applied:${key}`;
475
- var fingerprint = (config) => JSON.stringify(config);
476
- var SUBMIT = componentId({ kind: "button", name: "Submit" });
477
- var CANCEL = componentId({ kind: "button", name: "Cancel" });
478
- var CUSTOMER_PARAM = "customerIds";
479
- var seq = 0;
480
- var newCallId = () => `call-${Date.now().toString(36)}-${(seq++).toString(36)}`;
481
- var definitionFor = (context, api, method) => context.definitions.find((d) => d.api === api && (!method || d.method === method));
482
- var methodsFor = (context, api) => context.definitions.filter((d) => d.api === api).map((d) => d.method);
483
- var apisOf = (context) => [...new Set(context.definitions.map((d) => d.api))];
484
- var fieldId = (name) => componentId({ kind: "field", name });
485
- function defaultField(c) {
486
- return {
487
- name: c.name,
488
- label: c.label,
489
- required: !!c.required,
490
- input: c.name === "subject" || c.name === "notes" ? "template" : "user",
491
- excluded: false
492
- };
493
- }
494
- function reconcileCallParams(call, context) {
495
- const def = definitionFor(context, call.api, call.method);
496
- if (!def)
497
- return call;
498
- const saved = new Map(call.params.map((p) => [p.param, p]));
499
- const params = def.params.map((p) => ({
500
- param: p.name,
501
- required: p.required,
502
- source: saved.get(p.name)?.source ?? "",
503
- property: saved.get(p.name)?.property ?? ""
504
- }));
505
- for (const p of call.params) {
506
- if (!def.params.some((d) => d.name === p.param))
507
- params.push(p);
508
- }
509
- return JSON.stringify(params) === JSON.stringify(call.params) ? call : { ...call, params };
510
- }
511
- function fromFile(file, context) {
512
- if (!file)
513
- return [];
514
- const rows = (purpose) => (file[purpose] ?? []).map((r, i) => ({
515
- id: `${purpose}-${i + 1}`,
516
- purpose,
517
- component: normalizeComponent(r.component),
518
- api: r.api,
519
- method: r.method || methodsFor(context, r.api)[0] || "",
520
- timing: purpose === "read" ? r.timing || "lazy" : "",
521
- ...purpose === "read" ? { freshness: r.freshness || "fresh" } : {},
522
- ...purpose === "read" && r.pollSeconds ? { pollSeconds: r.pollSeconds } : {},
523
- params: r.params.map((p) => ({
524
- ...p,
525
- source: normalizeComponent(p.source),
526
- property: p.source === FREE_TEXT_SOURCE2 ? p.property : normalizeComponent(p.property)
527
- }))
528
- }));
529
- return [...rows("read"), ...rows("write")].map((c) => reconcileCallParams(c, context));
530
- }
531
- function proposeConfig(context) {
532
- return {
533
- version: 2,
534
- customerRule: "each",
535
- success: "close",
536
- templateFills: true,
537
- fields: context.snapshot.components.map(defaultField),
538
- calls: fromFile(context.mappings, context)
539
- };
540
- }
541
- function reconcileConfig(config, context) {
542
- const fields = context.snapshot.components.map((c) => {
543
- const old = config.fields.find((f) => f.name === c.name);
544
- return old ? { ...old, label: c.label, required: !!c.required } : defaultField(c);
545
- });
546
- const next = {
547
- ...config,
548
- fields,
549
- calls: config.calls.map((c) => reconcileCallParams(c, context))
550
- };
551
- return fingerprint(next) === fingerprint(config) ? config : next;
552
- }
553
- function migrateConfig(raw, context) {
554
- if (!raw || typeof raw !== "object")
555
- return null;
556
- const value = raw;
557
- if (value.version === 2 && Array.isArray(value.calls) && Array.isArray(value.fields)) {
558
- return reconcileConfig(value, context);
559
- }
560
- if (!Array.isArray(value.connections))
561
- return null;
562
- const old = value;
563
- const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
564
- const base = proposeConfig(context);
565
- let calls = base.calls;
566
- const grid = context.snapshot.grid?.id;
567
- const conflicts = [];
568
- const legacy = new Set(old.connections.map((c) => c.name));
569
- if (has(old, "saveApi") && !old.saveApi) {
570
- calls = calls.filter((c) => !(c.purpose === "write" && c.component === SUBMIT));
571
- } else if (old.saveApi || !has(old, "saveApi")) {
572
- let submit = calls.find((c) => c.purpose === "write" && c.component === SUBMIT);
573
- if (!submit && old.saveApi) {
574
- submit = { id: "write-1", purpose: "write", component: SUBMIT, api: old.saveApi, method: "", timing: "", params: [] };
575
- calls = [...calls, submit];
576
- }
577
- if (submit) {
578
- const api = old.saveApi || submit.api;
579
- const pick = methodsFor(context, api).includes("POST") ? "POST" : methodsFor(context, api)[0] ?? "";
580
- const patched = reconcileCallParams({ ...submit, api, method: submit.api === api && submit.method ? submit.method : pick }, context);
581
- const params = patched.params.map((p) => {
582
- let next = legacy.has(p.source) ? { ...p, source: "", property: "" } : p;
583
- const claim = old.connections.filter((c) => c.destination === p.param && !c.excluded);
584
- if (claim.length === 1) {
585
- next = { ...next, source: claim[0].name, property: claim[0].name };
586
- } else if (claim.length > 1) {
587
- conflicts.push({ callId: patched.id, param: p.param, fields: claim.map((c) => c.name) });
588
- next = { ...next, source: "", property: "" };
589
- }
590
- if (has(old, "customerProperty") && grid && next.source === grid && p.param === CUSTOMER_PARAM) {
591
- next = { ...next, property: old.customerProperty ?? "" };
592
- }
593
- return next;
594
- });
595
- calls = calls.map((c) => c.id === submit.id ? { ...patched, params } : c);
596
- }
597
- }
598
- for (const conn of old.connections) {
599
- if (!has(conn, "readApi"))
600
- continue;
601
- if (!conn.readApi) {
602
- calls = calls.filter((c) => !(c.purpose === "read" && c.component === conn.name));
603
- continue;
604
- }
605
- const existing = calls.find((c) => c.purpose === "read" && c.component === conn.name);
606
- if (existing) {
607
- calls = calls.map((c) => c === existing ? reconcileCallParams({ ...c, api: conn.readApi, method: c.api === conn.readApi ? c.method : methodsFor(context, conn.readApi)[0] ?? "" }, context) : c);
608
- } else {
609
- calls = [
610
- ...calls,
611
- reconcileCallParams({
612
- id: newCallId(),
613
- purpose: "read",
614
- component: conn.name,
615
- api: conn.readApi,
616
- method: methodsFor(context, conn.readApi)[0] ?? "",
617
- timing: "lazy",
618
- freshness: "fresh",
619
- params: []
620
- }, context)
621
- ];
622
- }
623
- }
624
- const fields = base.fields.map((f) => {
625
- const c = old.connections.find((x) => x.name === f.name);
626
- return c ? { ...f, input: c.input, excluded: c.excluded } : f;
627
- });
628
- return {
629
- version: 2,
630
- customerRule: old.customerRule ?? "each",
631
- success: old.success ?? "close",
632
- templateFills: old.templateFills ?? true,
633
- fields,
634
- calls,
635
- ...conflicts.length ? { conflicts } : {}
636
- };
637
- }
638
- function callsFor(config, component) {
639
- if (!component)
640
- return config.calls;
641
- return config.calls.filter((c) => c.component === component || c.params.some((p) => p.source === component));
642
- }
643
- function changeCallTarget(call, api, method, context) {
644
- const methods = methodsFor(context, api);
645
- const m = method && methods.includes(method) ? method : methods[0] ?? "";
646
- const def = definitionFor(context, api, m);
647
- const saved = new Map(call.params.map((p) => [p.param, p]));
648
- const params = (def?.params ?? []).map((p) => ({
649
- param: p.name,
650
- required: p.required,
651
- source: saved.get(p.name)?.source ?? "",
652
- property: saved.get(p.name)?.property ?? ""
653
- }));
654
- const read = call.purpose === "read";
655
- return {
656
- ...call,
657
- api,
658
- method: m,
659
- params,
660
- ...read ? { timing: call.timing || "lazy", freshness: call.freshness || "fresh" } : {}
661
- };
662
- }
663
- function droppedBy(call, api, method, context) {
664
- const next = changeCallTarget(call, api, method, context);
665
- const kept = new Set(next.params.map((p) => p.param));
666
- return call.params.filter((p) => p.source && !kept.has(p.param)).length;
667
- }
668
- var populated = (call) => call.params.some((p) => !!p.source);
669
- function changePurpose(call, purpose) {
670
- if (purpose === call.purpose)
671
- return call;
672
- return purpose === "read" ? { ...call, purpose, timing: "lazy", freshness: "fresh" } : { ...call, purpose, timing: "", freshness: undefined, pollSeconds: undefined };
673
- }
674
- function newCall(purpose, component) {
675
- return {
676
- id: newCallId(),
677
- purpose,
678
- component,
679
- api: "",
680
- method: "",
681
- timing: purpose === "read" ? "lazy" : "",
682
- ...purpose === "read" ? { freshness: "fresh" } : {},
683
- params: []
684
- };
685
- }
686
- function moveCall(calls, id, delta) {
687
- const i = calls.findIndex((c) => c.id === id);
688
- const j = i + delta;
689
- if (i < 0 || j < 0 || j >= calls.length)
690
- return calls;
691
- const next = [...calls];
692
- [next[i], next[j]] = [next[j], next[i]];
693
- return next;
694
- }
695
- function setParamSource(p, source, gridId) {
696
- if (source === FREE_TEXT_SOURCE2)
697
- return { ...p, source, property: "" };
698
- if (gridId && source === gridId)
699
- return { ...p, source, property: "" };
700
- return { ...p, source, property: source };
701
- }
702
- var gridPaths = (context) => context.snapshot.grid ? gridPropertyPaths({ gridId: context.snapshot.grid.id, rows: context.snapshot.grid.rows }) : [];
703
- var componentLabel = (context, id) => contextComponents(context.snapshot).find((o) => o.value === id)?.label ?? (id || "No target");
704
- var endpointLabel = (call) => call.api ? `${call.method || "?"} ${call.api}` : "No API selected";
705
- function callIssues(call, index, context) {
706
- const out = [];
707
- const name = `Call ${index + 1} (${call.purpose}${call.api ? ` ${endpointLabel(call)}` : ""})`;
708
- const targets = new Set(contextComponents(context.snapshot).map((o) => o.value));
709
- const sources = new Set(sourceComponents(context.snapshot).map((o) => o.value));
710
- const paths = new Set(gridPaths(context));
711
- const gridId = context.snapshot.grid?.id;
712
- if (!call.component)
713
- out.push(`${name}: choose which component it belongs to.`);
714
- else if (!targets.has(call.component))
715
- out.push(`${name}: target "${call.component}" is not in this window.`);
716
- if (!call.api) {
717
- out.push(`${name}: choose an API.`);
718
- return out;
719
- }
720
- if (!apisOf(context).includes(call.api)) {
721
- out.push(`${name}: API "${call.api}" is not registered.`);
722
- return out;
723
- }
724
- if (!call.method || !methodsFor(context, call.api).includes(call.method)) {
725
- out.push(`${name}: method "${call.method || "none"}" is not registered for this API.`);
726
- return out;
727
- }
728
- const def = definitionFor(context, call.api, call.method);
729
- if (def.available === false)
730
- out.push(`${name}: unavailable — ${def.problem || "generated hook/contract missing"}.`);
731
- if (call.purpose === "read") {
732
- if (call.timing !== "lazy" && call.timing !== "prefetch")
733
- out.push(`${name}: choose when it loads.`);
734
- if (!call.freshness)
735
- out.push(`${name}: choose freshness.`);
736
- if (call.freshness === "poll" && !(Number.isInteger(call.pollSeconds) && (call.pollSeconds ?? 0) > 0)) {
737
- out.push(`${name}: poll interval must be a whole number of seconds above 0.`);
738
- }
739
- }
740
- for (const p of call.params) {
741
- const spec = def.params.find((d) => d.name === p.param);
742
- if (!spec) {
743
- out.push(`${name}: "${p.param}" is no longer in the API contract — remove it or change the API.`);
744
- continue;
745
- }
746
- if (!p.source) {
747
- if (spec.required)
748
- out.push(`${name}: required "${p.param}" has no source.`);
749
- continue;
750
- }
751
- if (p.source === FREE_TEXT_SOURCE2) {
752
- if (p.property === "") {
753
- if (spec.required)
754
- out.push(`${name}: required "${p.param}" has an empty fixed value.`);
755
- } else if (!p.param.includes("[].") && coerce(p.property, spec.type) === undefined) {
756
- out.push(`${name}: "${p.property}" is not a valid ${spec.type} for "${p.param}".`);
757
- }
758
- continue;
759
- }
760
- if (!sources.has(p.source)) {
761
- out.push(`${name}: "${p.param}" source "${p.source}" is not in this window.`);
762
- continue;
763
- }
764
- if (gridId && p.source === gridId && !paths.has(p.property)) {
765
- out.push(`${name}: "${p.param}" needs a valid grid property${p.property ? ` ("${p.property}" not found)` : ""}.`);
766
- }
767
- }
768
- return out;
769
- }
770
- var fieldUsed = (config, name) => config.calls.some((c) => c.component === name || c.params.some((p) => p.source === name));
771
- function issuesFor(config, context) {
772
- const issues = [];
773
- config.calls.forEach((c, i) => issues.push(...callIssues(c, i, context)));
774
- if (context.snapshot.buttons.includes("Submit") && !config.calls.some((c) => c.purpose === "write" && c.component === SUBMIT)) {
775
- issues.push("Submit has no write API mapping.");
776
- }
777
- for (const field of config.fields) {
778
- const component = context.snapshot.components.find((c) => c.name === field.name);
779
- if (field.excluded) {
780
- if (field.required)
781
- issues.push(`${field.label} is required and cannot be excluded.`);
782
- if (fieldUsed(config, field.name))
783
- issues.push(`${field.label} is excluded but still used by an API mapping.`);
784
- continue;
785
- }
786
- if ((component?.kind ?? component?.type) === "checkbox" && !fieldUsed(config, field.name)) {
787
- issues.push(`${field.label} has no supported action. Resolve it or explicitly exclude it.`);
788
- }
789
- if (component && (component.readable || component.kind === "dropdown" || component.type === "combobox") && component.optionsSource !== "static" && !config.calls.some((c) => c.purpose === "read" && c.component === field.name)) {
790
- issues.push(`${field.label} needs a load connection.`);
791
- }
792
- }
793
- for (const k of config.conflicts ?? []) {
794
- const i = config.calls.findIndex((c) => c.id === k.callId);
795
- const p = config.calls[i]?.params.find((x) => x.param === k.param);
796
- if (p && !p.source) {
797
- issues.push(`Call ${i + 1} (${endpointLabel(config.calls[i])}): "${k.param}" was claimed by ${k.fields.join(" and ")} in an older draft — choose its source.`);
798
- }
799
- }
800
- return [...new Set(issues)];
801
- }
802
- function actionContext(context, form, rows) {
803
- const grid = context.snapshot.grid;
804
- return {
805
- form,
806
- ...grid ? { grid: { gridId: grid.id, rows: rows ?? grid.rows } } : {},
807
- parents: (context.snapshot.contexts ?? []).map((p) => ({
808
- id: p.id,
809
- values: Object.fromEntries(p.components.map((c) => [c.name, c.value ?? ""]))
810
- }))
811
- };
812
- }
813
- var snapshotForm = (context) => Object.fromEntries(context.snapshot.components.map((c) => [c.name, c.value ?? ""]));
814
- function resolutionIssues(call, index, def, ctx) {
815
- if (!def)
816
- return [];
817
- const out = [];
818
- const name = `Call ${index + 1} (${call.purpose} ${endpointLabel(call)})`;
819
- for (const spec of def.params) {
820
- const m = call.params.find((p) => p.param === spec.name);
821
- const text = m ? resolveParam(m, ctx) : undefined;
822
- if (text === undefined || text === "") {
823
- if (spec.required && m?.source)
824
- out.push(`${name}: required "${spec.name}" resolves to no value.`);
825
- continue;
826
- }
827
- if (!spec.name.includes("[].") && coerce(text, spec.type) === undefined) {
828
- out.push(`${name}: "${text}" is not a valid ${spec.type} for "${spec.name}", so it would not be sent.`);
829
- }
830
- }
831
- return out;
832
- }
833
- function previewRequests(config, context, ctx, filter) {
834
- return config.calls.map((c, i) => ({ c, i })).filter(({ c }) => !filter || filter(c)).map(({ c, i }) => {
835
- const def = definitionFor(context, c.api, c.method);
836
- const parts = buildRequest(c.params, ctx, def?.params ?? []);
837
- return {
838
- order: i + 1,
839
- id: c.id,
840
- purpose: c.purpose,
841
- target: componentLabel(context, c.component),
842
- method: c.method ?? "",
843
- api: c.api,
844
- hook: def?.hook ?? "",
845
- available: !!def && def.available !== false,
846
- parts,
847
- issues: [...callIssues(c, i, context), ...resolutionIssues(c, i, def, ctx)]
848
- };
849
- });
850
- }
851
- var isNoteContract = (def) => !!def && ["subject", "description", "noteType"].every((n) => def.params.some((p) => p.name === n));
852
- function isSimulated(call, context) {
853
- const def = definitionFor(context, call.api, call.method);
854
- if (!def || def.available === false)
855
- return false;
856
- if (call.purpose === "write") {
857
- return call.component === SUBMIT && call.method === "POST" && isNoteContract(def) && def.params.some((p) => p.name === CUSTOMER_PARAM);
858
- }
859
- return !!context.options[call.component]?.length && !!context.mappings?.read.some((r) => normalizeComponent(r.component) === call.component && r.api === call.api && (r.method || call.method) === call.method);
860
- }
861
- function simulatorGaps(config, context) {
862
- return config.calls.filter((c) => c.api && !isSimulated(c, context)).map((c) => c.purpose === "write" ? `No simulator for ${endpointLabel(c)} on ${componentLabel(context, c.component)}; only its request preview can be shown, so this binding cannot be verified here.` : `${endpointLabel(c)} for ${componentLabel(context, c.component)} is not tested: no captured response exists for it, so this binding cannot be verified here.`);
863
- }
864
- function simulationErrors(config, input, customers) {
865
- const errors = [];
866
- const hasGridParam = config.calls.some((c) => c.params.some((p) => p.param === CUSTOMER_PARAM || p.source.toLowerCase().includes("grid")));
867
- if (hasGridParam) {
868
- if (!customers)
869
- errors.push("Select at least one customer.");
870
- if (config.customerRule === "single" && customers !== 1)
871
- errors.push("This binding requires exactly one customer.");
872
- }
873
- const required = (name) => config.fields.find((f) => f.name === name && !f.excluded && f.required);
874
- if (required("notesType") && !input.notesType)
875
- errors.push("Notes Type is required.");
876
- if (required("subject") && !String(input.subject ?? "").trim())
877
- errors.push("Subject is required.");
878
- if (required("notes") && !String(input.notes ?? "").trim())
879
- errors.push("Description is required.");
880
- for (const field of config.fields) {
881
- if (field.excluded || !field.required)
882
- continue;
883
- if (field.name === "notesType" || field.name === "subject" || field.name === "notes")
884
- continue;
885
- const val = input[field.name];
886
- if (val === undefined || val === null || String(val).trim() === "") {
887
- errors.push(`${field.label} is required.`);
888
- }
889
- }
890
- return errors;
891
- }
892
- var simulationForm = (input) => {
893
- const form = {
894
- notesType: String(input.notesType ?? ""),
895
- template: String(input.template ?? ""),
896
- subject: String(input.subject ?? ""),
897
- notes: String(input.notes ?? ""),
898
- addCallLog: String(input.addCallLog ?? "")
899
- };
900
- for (const [k, v] of Object.entries(input)) {
901
- form[k] = typeof v === "boolean" ? String(v) : String(v ?? "");
902
- }
903
- return form;
904
- };
905
- function simulateSave(config, context, input, rows, fail) {
906
- const ctx = actionContext(context, simulationForm(input), rows);
907
- const requests = previewRequests(config, context, ctx, (c) => c.purpose === "write" && c.component === SUBMIT);
908
- const errors = [...simulationErrors(config, input, rows.length), ...simulatorGaps(config, context)];
909
- if (!requests.length)
910
- errors.push("Submit has no write API mapping.");
911
- errors.push(...requests.flatMap((r) => r.issues));
912
- if (errors.length)
913
- return { notes: [], errors: [...new Set(errors)], requests };
914
- if (fail) {
915
- return {
916
- notes: [],
917
- errors: ["Simulated save failed. Your entered information is preserved; retry when ready."],
918
- requests
919
- };
920
- }
921
- const notes = [];
922
- for (const r of requests) {
923
- const d = r.parts.data;
924
- const def = definitionFor(context, r.api, r.method);
925
- if (isNoteContract(def)) {
926
- if (typeof d.subject !== "string" || !d.subject.trim() || typeof d.description !== "string" || !d.description.trim() || typeof d.noteType !== "number") {
927
- return {
928
- notes: [],
929
- errors: [`${endpointLabel(r)} would not send a valid note (subject, description, noteType). Review its request mappings.`],
930
- requests
931
- };
932
- }
933
- const ids = d[CUSTOMER_PARAM];
934
- if (!Array.isArray(ids) || !ids.length || !ids.every((x) => typeof x === "number" && Number.isFinite(x))) {
935
- return {
936
- notes: [],
937
- errors: [`${endpointLabel(r)} would not send valid customer IDs ("${CUSTOMER_PARAM}"). Review its customer identifier.`],
938
- requests
939
- };
940
- }
941
- const customers = ids;
942
- customers.forEach((customerId) => notes.push({
943
- id: `sample-note-${notes.length + 1}`,
944
- customerId,
945
- noteType: d.noteType,
946
- subject: d.subject,
947
- description: d.description
948
- }));
949
- } else {
950
- notes.push({
951
- id: `sample-record-${notes.length + 1}`,
952
- ...d
953
- });
954
- }
955
- }
956
- return { notes, errors: [], requests };
957
- }
958
- function canApply(config, context, verified) {
959
- return issuesFor(config, context).length === 0 && simulatorGaps(config, context).length === 0 && verified === fingerprint(config);
960
- }
961
- function fieldSummary(config, context, name) {
962
- const reads = config.calls.filter((c) => c.purpose === "read" && c.component === name).map((c) => `${endpointLabel(c)} · ${c.timing === "prefetch" ? "On Window Load" : "Lazy on Click"}`);
963
- const field = config.fields.find((f) => f.name === name);
964
- const sends = config.calls.flatMap((c) => c.params.filter((p) => p.source === name).map((p) => `${endpointLabel(c)} → ${p.param}`));
965
- const input = field?.input === "template" && config.templateFills ? "User input or template" : "User input";
966
- return {
967
- from: field?.excluded ? "Excluded by choice" : [...reads, ...reads.length ? [] : [input]].join("; "),
968
- to: field?.excluded ? "Hidden in Studio preview" : sends.join("; ") || (name === "template" && config.templateFills ? "Fills Subject and Description · not saved" : "Not sent by any API")
969
- };
970
- }
971
- var isButton = isButtonId;
972
- // src/core/transport.ts
973
- var contextKey = (key) => `api-binding:context:${key}`;
974
- var latestContexts = new Map;
975
- var hostListening = false;
976
- function storeBindingContext(context) {
977
- try {
978
- const key = context.snapshot.key;
979
- const json = JSON.stringify(context);
980
- localStorage.setItem(contextKey(key), json);
981
- storeSnapshot(context.snapshot);
982
- } catch {}
983
- }
984
- function readBindingContext(key) {
985
- try {
986
- const raw = localStorage.getItem(contextKey(key)) || localStorage.getItem(`api-binding-v2:context:${key}`);
987
- return raw ? JSON.parse(raw) : null;
988
- } catch {
989
- return null;
990
- }
991
- }
992
- function validBindingContext(value, key) {
993
- if (!value || typeof value !== "object")
994
- return false;
995
- const context = value;
996
- return context.snapshot?.key === key && Array.isArray(context.snapshot.components) && !!context.options;
997
- }
998
- function openBindingStudio(context, options) {
999
- const key = context.snapshot.key;
1000
- storeBindingContext(context);
1001
- latestContexts.set(key, context);
1002
- if (!hostListening) {
1003
- hostListening = true;
1004
- window.addEventListener("message", (event) => {
1005
- if (event.origin !== window.location.origin)
1006
- return;
1007
- const data = event.data;
1008
- if ((data?.type === BINDING_READY || data?.type === "api-binding-v2-ready") && data.key) {
1009
- const current = latestContexts.get(data.key);
1010
- if (current) {
1011
- const msg = { type: BINDING_CONTEXT, context: current };
1012
- event.source?.postMessage(msg, event.origin);
1013
- }
1014
- }
1015
- });
1016
- }
1017
- const tabName = `api-binding:${key}`;
1018
- const routeBase = options?.routeBase || "/api-binding";
1019
- const url = `${routeBase}/${encodeURIComponent(key)}`;
1020
- const tab = window.open("", tabName, options?.features);
1021
- if (!tab) {
1022
- throw new Error("Your browser blocked the API Binding tab. Please allow popups for this site and try again.");
1023
- }
1024
- try {
1025
- if (tab.location.href === "about:blank") {
1026
- tab.location.href = url;
1027
- }
1028
- } catch {
1029
- tab.location.href = url;
1030
- }
1031
- const msg = { type: BINDING_CONTEXT, context };
1032
- try {
1033
- tab.postMessage(msg, window.location.origin);
1034
- } catch {}
1035
- tab.focus();
1036
- try {
1037
- const channel = new BroadcastChannel(BINDING_CHANNEL);
1038
- channel.postMessage({ key });
1039
- channel.close();
1040
- } catch {}
1041
- return tab;
1042
- }
1043
- function notifyApplied(key, config) {
1044
- const payload = {
1045
- version: CONFIG_VERSION,
1046
- config,
1047
- appliedAt: new Date().toISOString()
1048
- };
1049
- const rawPayload = JSON.stringify(payload);
1050
- try {
1051
- localStorage.setItem(appliedKey(key), rawPayload);
1052
- localStorage.setItem(`api-binding:applied:${key}`, rawPayload);
1053
- } catch {}
1054
- const msg = {
1055
- type: BINDING_APPLIED,
1056
- key,
1057
- config
1058
- };
1059
- try {
1060
- window.opener?.postMessage(msg, window.location.origin);
1061
- window.opener?.postMessage({ type: "api-binding-v2-applied", key, config }, window.location.origin);
1062
- } catch {}
1063
- try {
1064
- const channel = new BroadcastChannel(BINDING_CHANNEL);
1065
- channel.postMessage(msg);
1066
- channel.close();
1067
- } catch {}
1068
- }
1069
- function notifySelectionUpdate(key, grid) {
1070
- const msg = {
1071
- type: BINDING_SELECTION_UPDATE,
1072
- key,
1073
- grid
1074
- };
1075
- try {
1076
- const channel = new BroadcastChannel(BINDING_CHANNEL);
1077
- channel.postMessage(msg);
1078
- channel.close();
1079
- } catch {}
1080
- }
1081
- var validContext = validBindingContext;
1082
- // src/core/catalog.ts
1083
- function friendlyLabel(operationId) {
1084
- if (!operationId)
1085
- return;
1086
- return operationId.split(".").filter((p) => !/^v\d+$/i.test(p)).map((p) => p.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase())).join(" ");
1087
- }
1088
- function buildCatalogFromRegistry({
1089
- bindings,
1090
- hookResolver = () => {
1091
- return;
1092
- },
1093
- paramsResolver = () => [],
1094
- availabilityChecker = () => ({ available: true })
1095
- }) {
1096
- const definitions = [];
1097
- for (const [api, methodsObj] of Object.entries(bindings)) {
1098
- for (const [method, meta] of Object.entries(methodsObj)) {
1099
- const hook = hookResolver(api, method) ?? "";
1100
- const params = paramsResolver(api, method);
1101
- const status = availabilityChecker(api, method);
1102
- definitions.push({
1103
- api,
1104
- method: method.toUpperCase(),
1105
- hook,
1106
- params,
1107
- label: friendlyLabel(meta?.operationId),
1108
- available: status.available,
1109
- ...status.problem ? { problem: status.problem } : {}
1110
- });
1111
- }
1112
- }
1113
- return definitions;
1114
- }
1115
- // src/hooks/useAppliedBinding.ts
1116
- import * as React from "react";
1117
- function useAppliedBinding(pageKey) {
1118
- const [applied, setApplied] = React.useState(() => {
1119
- try {
1120
- const raw = localStorage.getItem(appliedKey(pageKey));
1121
- return raw ? JSON.parse(raw) : null;
1122
- } catch {
1123
- return null;
1124
- }
1125
- });
1126
- React.useEffect(() => {
1127
- function check() {
1128
- try {
1129
- const raw = localStorage.getItem(appliedKey(pageKey));
1130
- if (raw)
1131
- setApplied(JSON.parse(raw));
1132
- } catch {}
1133
- }
1134
- const onMessage = (event) => {
1135
- if (event.origin === window.location.origin) {
1136
- const data = event.data;
1137
- if (data?.type === BINDING_APPLIED && data.key === pageKey) {
1138
- check();
1139
- }
1140
- }
1141
- };
1142
- const onStorage = (event) => {
1143
- if (event.key === appliedKey(pageKey)) {
1144
- check();
1145
- }
1146
- };
1147
- window.addEventListener("message", onMessage);
1148
- window.addEventListener("storage", onStorage);
1149
- let channel;
1150
- try {
1151
- channel = new BroadcastChannel(BINDING_CHANNEL);
1152
- channel.onmessage = (event) => {
1153
- if (event.data?.type === BINDING_APPLIED && event.data?.key === pageKey) {
1154
- check();
1155
- }
1156
- };
1157
- } catch {}
1158
- return () => {
1159
- window.removeEventListener("message", onMessage);
1160
- window.removeEventListener("storage", onStorage);
1161
- channel?.close();
1162
- };
1163
- }, [pageKey]);
1164
- return {
1165
- appliedConfig: applied?.config ?? null,
1166
- isApplied: !!applied,
1167
- appliedAt: applied?.appliedAt ?? null
1168
- };
1169
- }
1170
- // src/components/BindApiButton.tsx
1171
- import * as React3 from "react";
1172
-
1173
- // src/components/ui/Icons.tsx
1174
- import * as React2 from "react";
1175
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
1176
- var base = (path) => React2.forwardRef(({ size = 16, className = "", ...props }, ref) => /* @__PURE__ */ jsx("svg", {
1177
- ref,
1178
- width: size,
1179
- height: size,
1180
- viewBox: "0 0 24 24",
1181
- fill: "none",
1182
- stroke: "currentColor",
1183
- strokeWidth: "2",
1184
- strokeLinecap: "round",
1185
- strokeLinejoin: "round",
1186
- className,
1187
- ...props,
1188
- children: path
1189
- }));
1190
- var LinkIcon = base(/* @__PURE__ */ jsxs(Fragment, {
1191
- children: [
1192
- /* @__PURE__ */ jsx("path", {
1193
- d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"
1194
- }),
1195
- /* @__PURE__ */ jsx("path", {
1196
- d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"
1197
- })
1198
- ]
1199
- }));
1200
- var PencilIcon = base(/* @__PURE__ */ jsxs(Fragment, {
1201
- children: [
1202
- /* @__PURE__ */ jsx("path", {
1203
- d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"
1204
- }),
1205
- /* @__PURE__ */ jsx("path", {
1206
- d: "m15 5 4 4"
1207
- })
1208
- ]
1209
- }));
1210
- var SettingsIcon = base(/* @__PURE__ */ jsxs(Fragment, {
1211
- children: [
1212
- /* @__PURE__ */ jsx("path", {
1213
- d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"
1214
- }),
1215
- /* @__PURE__ */ jsx("circle", {
1216
- cx: "12",
1217
- cy: "12",
1218
- r: "3"
1219
- })
1220
- ]
1221
- }));
1222
- var PlusIcon = base(/* @__PURE__ */ jsxs(Fragment, {
1223
- children: [
1224
- /* @__PURE__ */ jsx("path", {
1225
- d: "M5 12h14"
1226
- }),
1227
- /* @__PURE__ */ jsx("path", {
1228
- d: "M12 5v14"
1229
- })
1230
- ]
1231
- }));
1232
- var Trash2Icon = base(/* @__PURE__ */ jsxs(Fragment, {
1233
- children: [
1234
- /* @__PURE__ */ jsx("path", {
1235
- d: "M3 6h18"
1236
- }),
1237
- /* @__PURE__ */ jsx("path", {
1238
- d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"
1239
- }),
1240
- /* @__PURE__ */ jsx("path", {
1241
- d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"
1242
- }),
1243
- /* @__PURE__ */ jsx("line", {
1244
- x1: "10",
1245
- x2: "10",
1246
- y1: "11",
1247
- y2: "17"
1248
- }),
1249
- /* @__PURE__ */ jsx("line", {
1250
- x1: "14",
1251
- x2: "14",
1252
- y1: "11",
1253
- y2: "17"
1254
- })
1255
- ]
1256
- }));
1257
- var ArrowUpIcon = base(/* @__PURE__ */ jsxs(Fragment, {
1258
- children: [
1259
- /* @__PURE__ */ jsx("path", {
1260
- d: "m5 12 7-7 7 7"
1261
- }),
1262
- /* @__PURE__ */ jsx("path", {
1263
- d: "M12 19V5"
1264
- })
1265
- ]
1266
- }));
1267
- var ArrowDownIcon = base(/* @__PURE__ */ jsxs(Fragment, {
1268
- children: [
1269
- /* @__PURE__ */ jsx("path", {
1270
- d: "M12 5v14"
1271
- }),
1272
- /* @__PURE__ */ jsx("path", {
1273
- d: "m19 12-7 7-7-7"
1274
- })
1275
- ]
1276
- }));
1277
- var PlayIcon = base(/* @__PURE__ */ jsx("polygon", {
1278
- points: "6 3 20 12 6 21 6 3"
1279
- }));
1280
- var CheckIcon = base(/* @__PURE__ */ jsx("polyline", {
1281
- points: "20 6 9 17 4 12"
1282
- }));
1283
- var XIcon = base(/* @__PURE__ */ jsxs(Fragment, {
1284
- children: [
1285
- /* @__PURE__ */ jsx("path", {
1286
- d: "M18 6 6 18"
1287
- }),
1288
- /* @__PURE__ */ jsx("path", {
1289
- d: "m6 6 12 12"
1290
- })
1291
- ]
1292
- }));
1293
- var SearchIcon = base(/* @__PURE__ */ jsxs(Fragment, {
1294
- children: [
1295
- /* @__PURE__ */ jsx("circle", {
1296
- cx: "11",
1297
- cy: "11",
1298
- r: "8"
1299
- }),
1300
- /* @__PURE__ */ jsx("path", {
1301
- d: "m21 21-4.3-4.3"
1302
- })
1303
- ]
1304
- }));
1305
- var AlertTriangleIcon = base(/* @__PURE__ */ jsxs(Fragment, {
1306
- children: [
1307
- /* @__PURE__ */ jsx("path", {
1308
- d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"
1309
- }),
1310
- /* @__PURE__ */ jsx("line", {
1311
- x1: "12",
1312
- x2: "12",
1313
- y1: "9",
1314
- y2: "13"
1315
- }),
1316
- /* @__PURE__ */ jsx("line", {
1317
- x1: "12",
1318
- x2: "12.01",
1319
- y1: "17",
1320
- y2: "17"
1321
- })
1322
- ]
1323
- }));
1324
- var AlertCircleIcon = base(/* @__PURE__ */ jsxs(Fragment, {
1325
- children: [
1326
- /* @__PURE__ */ jsx("circle", {
1327
- cx: "12",
1328
- cy: "12",
1329
- r: "10"
1330
- }),
1331
- /* @__PURE__ */ jsx("line", {
1332
- x1: "12",
1333
- x2: "12",
1334
- y1: "8",
1335
- y2: "12"
1336
- }),
1337
- /* @__PURE__ */ jsx("line", {
1338
- x1: "12",
1339
- x2: "12.01",
1340
- y1: "16",
1341
- y2: "16"
1342
- })
1343
- ]
1344
- }));
1345
- var iconMap = {
1346
- Link: LinkIcon,
1347
- Pencil: PencilIcon,
1348
- Settings: SettingsIcon,
1349
- Plus: PlusIcon,
1350
- Trash2: Trash2Icon,
1351
- ArrowUp: ArrowUpIcon,
1352
- ArrowDown: ArrowDownIcon,
1353
- Play: PlayIcon,
1354
- Check: CheckIcon,
1355
- X: XIcon,
1356
- Search: SearchIcon,
1357
- AlertTriangle: AlertTriangleIcon,
1358
- AlertCircle: AlertCircleIcon
1359
- };
1360
- function renderIcon(icon, props) {
1361
- if (!icon)
1362
- return null;
1363
- if (typeof icon === "string") {
1364
- const Component = iconMap[icon];
1365
- return Component ? /* @__PURE__ */ jsx(Component, {
1366
- ...props
1367
- }) : null;
1368
- }
1369
- return icon;
1370
- }
1371
-
1372
- // src/components/ui/Button.tsx
1373
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1374
- function Button({
1375
- variant = "secondary",
1376
- size = "md",
1377
- icon,
1378
- tooltip,
1379
- children,
1380
- className = "",
1381
- type = "button",
1382
- ...rest
1383
- }) {
1384
- const iconOnly = Boolean(icon) && (children === undefined || children === null || children === false);
1385
- return /* @__PURE__ */ jsxs2("button", {
1386
- type,
1387
- className: `ds-button ds-button--${size} ds-button--${variant} ${iconOnly ? "ds-button--icon-only" : ""} ${className}`,
1388
- title: typeof tooltip === "string" ? tooltip : undefined,
1389
- ...rest,
1390
- children: [
1391
- icon && /* @__PURE__ */ jsx2("span", {
1392
- className: "ds-button__icon",
1393
- children: renderIcon(icon)
1394
- }),
1395
- children && /* @__PURE__ */ jsx2("span", {
1396
- children
1397
- })
1398
- ]
1399
- });
1400
- }
1401
- function ButtonBar({ justify, children, className = "" }) {
1402
- return /* @__PURE__ */ jsx2("div", {
1403
- className: `ds-button-bar ${justify === "between" ? "ds-button-bar--justify-between" : ""} ${className}`,
1404
- children
1405
- });
1406
- }
1407
- function ButtonGroup({
1408
- value,
1409
- onChange,
1410
- options = [],
1411
- "aria-label": ariaLabel,
1412
- className = ""
1413
- }) {
1414
- return /* @__PURE__ */ jsx2("div", {
1415
- className: `ds-button-group ${className}`,
1416
- role: "group",
1417
- "aria-label": ariaLabel,
1418
- children: options.map((opt) => /* @__PURE__ */ jsx2("button", {
1419
- type: "button",
1420
- className: `ds-button-group__button ${opt.value === value ? "ds-button-group__button--selected active" : ""}`,
1421
- onClick: () => onChange(opt.value),
1422
- children: opt.label
1423
- }, opt.value))
1424
- });
1425
- }
1426
-
1427
- // src/components/BindApiButton.tsx
1428
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1429
- function BindApiButton({
1430
- appId = "app",
1431
- windowId,
1432
- source,
1433
- parent,
1434
- grid,
1435
- parents,
1436
- kind,
1437
- trigger,
1438
- options = {},
1439
- definitions = [],
1440
- mappingFile = null,
1441
- routeBase = "/api-binding",
1442
- containerRef,
1443
- containerSelector,
1444
- rootElement,
1445
- formId,
1446
- components: explicitComponents,
1447
- buttons: explicitButtons,
1448
- className = "",
1449
- children = "Bind API"
1450
- }) {
1451
- const ref = React3.useRef(null);
1452
- const [error, setError] = React3.useState("");
1453
- function handleClick(e) {
1454
- e.preventDefault();
1455
- e.stopPropagation();
1456
- try {
1457
- const ids = windowIds({
1458
- appId,
1459
- windowId,
1460
- gridId: grid?.gridId,
1461
- parentWindowIds: parents?.map((p) => p.id)
1462
- });
1463
- const root = findEnclosingContextElement(ref.current, {
1464
- containerRef,
1465
- containerSelector,
1466
- rootElement,
1467
- formId
1468
- });
1469
- const resolvedKind = kind ?? (root?.closest('[role="dialog"], dialog, .ds-dialog') ? "dialog" : root?.closest("[data-drawer], aside, .drawer") ? "drawer" : "screen");
1470
- const snapshot = buildSnapshot({
1471
- ids,
1472
- source,
1473
- parent,
1474
- root,
1475
- grid,
1476
- kind: resolvedKind,
1477
- trigger,
1478
- contexts: parents,
1479
- components: explicitComponents,
1480
- buttons: explicitButtons
1481
- });
1482
- openBindingStudio({
1483
- snapshot,
1484
- mappings: mappingFile,
1485
- options,
1486
- definitions
1487
- }, { routeBase });
1488
- setError("");
1489
- } catch (err) {
1490
- const msg = err instanceof Error ? err.message : "Could not open API Binding Studio.";
1491
- setError(msg);
1492
- console.error("[BindApiButton] Error:", err);
1493
- }
1494
- }
1495
- return /* @__PURE__ */ jsxs3("span", {
1496
- ref,
1497
- className,
1498
- children: [
1499
- /* @__PURE__ */ jsx3(Button, {
1500
- size: "sm",
1501
- icon: "Link",
1502
- onClick: handleClick,
1503
- tooltip: error || undefined,
1504
- children
1505
- }),
1506
- error && /* @__PURE__ */ jsx3("span", {
1507
- role: "alert",
1508
- className: "binding-error",
1509
- style: { marginLeft: "0.5rem" },
1510
- children: error
1511
- })
1512
- ]
1513
- });
1514
- }
1515
- // src/components/ApiBindingWorkspace.tsx
1516
- import * as React9 from "react";
1517
-
1518
- // src/components/WorkspaceContext.tsx
1519
- import * as React6 from "react";
1520
-
1521
- // src/components/ui/Badge.tsx
1522
- import { jsx as jsx4 } from "react/jsx-runtime";
1523
- var TONES = {
1524
- neutral: {
1525
- bg: "var(--ds-badge-neutral-background, var(--ds-color-surface, #f8fafc))",
1526
- fg: "var(--ds-badge-neutral-foreground, var(--ds-color-muted-foreground, #64748b))",
1527
- border: "var(--ds-badge-neutral-border, var(--ds-color-border, #e2e8f0))"
1528
- },
1529
- warning: {
1530
- bg: "var(--ds-badge-warning-background, #fff7ed)",
1531
- fg: "var(--ds-badge-warning-foreground, #9a3412)",
1532
- border: "var(--ds-badge-warning-border, #fed7aa)"
1533
- },
1534
- success: {
1535
- bg: "var(--ds-badge-success-background, #f0fdf4)",
1536
- fg: "var(--ds-badge-success-foreground, #166534)",
1537
- border: "var(--ds-badge-success-border, #bbf7d0)"
1538
- },
1539
- danger: {
1540
- bg: "var(--ds-badge-danger-background, #fef2f2)",
1541
- fg: "var(--ds-badge-danger-foreground, #991b1b)",
1542
- border: "var(--ds-badge-danger-border, #fecaca)"
1543
- },
1544
- info: {
1545
- bg: "var(--ds-badge-info-background, #eff6ff)",
1546
- fg: "var(--ds-badge-info-foreground, #1e40af)",
1547
- border: "var(--ds-badge-info-border, #bfdbfe)"
1548
- },
1549
- brand: {
1550
- bg: "var(--ds-badge-brand-background, #fff7ed)",
1551
- fg: "var(--ds-badge-brand-foreground, #c2410c)",
1552
- border: "var(--ds-badge-brand-border, #fdba74)"
1553
- }
1554
- };
1555
- function Badge({ variant = "neutral", children, className = "", style, ...rest }) {
1556
- const tone = TONES[variant] ?? TONES.neutral;
1557
- return /* @__PURE__ */ jsx4("span", {
1558
- className: `ds-badge ds-badge--${variant} ${className}`,
1559
- style: {
1560
- display: "inline-flex",
1561
- alignItems: "center",
1562
- borderWidth: "1px",
1563
- borderStyle: "solid",
1564
- borderColor: tone.border,
1565
- backgroundColor: tone.bg,
1566
- color: tone.fg,
1567
- borderRadius: "var(--ds-badge-radius, 4px)",
1568
- height: "var(--ds-badge-height-sm, 20px)",
1569
- padding: "0 var(--ds-badge-padding-x-sm, 8px)",
1570
- fontSize: "var(--ds-badge-font-size-sm, 12px)",
1571
- fontWeight: 500,
1572
- lineHeight: 1,
1573
- whiteSpace: "nowrap",
1574
- ...style
1575
- },
1576
- ...rest,
1577
- children
1578
- });
1579
- }
1580
-
1581
- // src/components/ui/AppGrid.tsx
1582
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1583
- function createActionColumn(spec) {
1584
- return {
1585
- type: "action",
1586
- headerName: spec.headerName,
1587
- labelField: spec.labelField,
1588
- icon: spec.icon,
1589
- onClick: spec.onClick
1590
- };
1591
- }
1592
- function AppGrid({
1593
- rows = [],
1594
- columns = [],
1595
- getRowKey = (_, i) => i,
1596
- className = ""
1597
- }) {
1598
- return /* @__PURE__ */ jsx5("div", {
1599
- className: `ds-grid ${className}`,
1600
- style: {
1601
- border: "1px solid var(--ds-color-border, #e2e8f0)",
1602
- borderRadius: "var(--ds-radius-md, 6px)",
1603
- overflow: "hidden",
1604
- backgroundColor: "var(--ds-color-surface, #ffffff)",
1605
- width: "100%"
1606
- },
1607
- children: /* @__PURE__ */ jsx5("div", {
1608
- style: { width: "100%", overflowX: "auto" },
1609
- children: /* @__PURE__ */ jsxs4("table", {
1610
- className: "ds-grid__table",
1611
- style: {
1612
- width: "100%",
1613
- borderCollapse: "collapse",
1614
- fontSize: "13px",
1615
- textAlign: "left"
1616
- },
1617
- children: [
1618
- /* @__PURE__ */ jsx5("thead", {
1619
- children: /* @__PURE__ */ jsx5("tr", {
1620
- className: "ds-grid__header-row",
1621
- style: {
1622
- backgroundColor: "var(--ds-grid-header-background, #f8fafc)",
1623
- borderBottom: "1px solid var(--ds-color-border, #e2e8f0)"
1624
- },
1625
- children: columns.map((col, idx) => /* @__PURE__ */ jsx5("th", {
1626
- className: `ds-grid__header-cell ${col.fill ? "ds-grid__header-cell--fill" : ""}`,
1627
- style: {
1628
- padding: "8px 12px",
1629
- fontSize: "12px",
1630
- fontWeight: 600,
1631
- color: "var(--ds-grid-header-foreground, #475569)",
1632
- borderRight: idx < columns.length - 1 ? "1px solid var(--ds-color-border, #e2e8f0)" : "none",
1633
- width: col.fill ? undefined : col.type === "action" ? "120px" : undefined
1634
- },
1635
- children: /* @__PURE__ */ jsx5("div", {
1636
- className: "ds-grid__header-cell-label",
1637
- children: col.headerName
1638
- })
1639
- }, `${String(col.field ?? idx)}`))
1640
- })
1641
- }),
1642
- /* @__PURE__ */ jsx5("tbody", {
1643
- children: rows.map((row, rowIdx) => /* @__PURE__ */ jsx5("tr", {
1644
- className: "ds-grid__row",
1645
- style: {
1646
- borderBottom: rowIdx < rows.length - 1 ? "1px solid var(--ds-color-border, #e2e8f0)" : "none",
1647
- backgroundColor: rowIdx % 2 === 1 ? "var(--ds-grid-row-alt, #fafbfc)" : "#ffffff"
1648
- },
1649
- children: columns.map((col, colIdx) => {
1650
- let content = null;
1651
- if (col.type === "action") {
1652
- const label = col.labelField ? String(row[col.labelField] ?? "") : "Edit";
1653
- content = /* @__PURE__ */ jsx5(Button, {
1654
- size: "sm",
1655
- variant: "secondary",
1656
- icon: col.icon,
1657
- onClick: () => col.onClick?.(row),
1658
- children: label
1659
- });
1660
- } else if (col.type === "badge") {
1661
- const val = String(row[col.field] ?? "");
1662
- const variant = col.badgeVariants?.[val] ?? "neutral";
1663
- content = /* @__PURE__ */ jsx5(Badge, {
1664
- variant,
1665
- children: val
1666
- });
1667
- } else if (col.render) {
1668
- content = col.render(row);
1669
- } else {
1670
- const val = row[col.field];
1671
- content = val !== undefined && val !== null ? String(val) : "—";
1672
- }
1673
- return /* @__PURE__ */ jsx5("td", {
1674
- className: `ds-grid__cell ${col.fill ? "ds-grid__cell--fill" : ""} ${col.type === "number" ? "ds-grid__cell--numeric" : ""}`,
1675
- style: {
1676
- padding: "8px 12px",
1677
- color: "var(--ds-color-foreground, #0f172a)",
1678
- borderRight: colIdx < columns.length - 1 ? "1px solid var(--ds-color-border, #e2e8f0)" : "none"
1679
- },
1680
- children: content
1681
- }, `${String(col.field ?? colIdx)}`);
1682
- })
1683
- }, String(getRowKey(row, rowIdx))))
1684
- })
1685
- ]
1686
- })
1687
- })
1688
- });
1689
- }
1690
-
1691
- // src/components/ui/Banner.tsx
1692
- import { jsx as jsx6 } from "react/jsx-runtime";
1693
- function Banner({ tone = "caution", children, className = "" }) {
1694
- return /* @__PURE__ */ jsx6("div", {
1695
- className: `ds-banner ds-banner--${tone} ${className}`,
1696
- role: "status",
1697
- children: /* @__PURE__ */ jsx6("div", {
1698
- className: "ds-banner__content",
1699
- children
1700
- })
1701
- });
1702
- }
1703
-
1704
- // src/components/ui/Card.tsx
1705
- import * as React4 from "react";
1706
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1707
- function DetailCard({
1708
- title,
1709
- subtitle,
1710
- actions,
1711
- fields = [],
1712
- dense = true,
1713
- className = ""
1714
- }) {
1715
- const showHeader = Boolean(title) || Boolean(subtitle) || Boolean(actions);
1716
- return /* @__PURE__ */ jsxs5("section", {
1717
- className: `ds-detail-card ${dense ? "ds-detail-card--dense" : ""} ${className}`,
1718
- style: {
1719
- backgroundColor: "var(--ds-detail-card-background, var(--ds-color-surface, #ffffff))",
1720
- border: "1px solid var(--ds-detail-card-border, var(--ds-color-border, #e2e8f0))",
1721
- borderRadius: "var(--ds-detail-card-radius, var(--ds-radius-md, 6px))",
1722
- boxShadow: "var(--ds-detail-card-shadow, none)",
1723
- display: "flex",
1724
- flexDirection: "column",
1725
- minWidth: 0
1726
- },
1727
- children: [
1728
- showHeader && /* @__PURE__ */ jsxs5("div", {
1729
- className: "ds-detail-card__header",
1730
- style: {
1731
- display: "flex",
1732
- alignItems: "center",
1733
- justifyContent: "space-between",
1734
- padding: "12px 16px",
1735
- borderBottom: "1px solid var(--ds-detail-card-border, var(--ds-color-border, #e2e8f0))"
1736
- },
1737
- children: [
1738
- /* @__PURE__ */ jsxs5("div", {
1739
- className: "ds-detail-card__heading",
1740
- style: { minWidth: 0 },
1741
- children: [
1742
- title && /* @__PURE__ */ jsx7("h3", {
1743
- className: "ds-detail-card__title",
1744
- style: {
1745
- margin: 0,
1746
- fontSize: "var(--ds-detail-card-title-font-size, 14px)",
1747
- fontWeight: 600,
1748
- color: "var(--ds-color-foreground, #0f172a)"
1749
- },
1750
- children: title
1751
- }),
1752
- subtitle && /* @__PURE__ */ jsx7("p", {
1753
- className: "ds-detail-card__subtitle",
1754
- style: {
1755
- margin: 0,
1756
- fontSize: "12px",
1757
- color: "var(--ds-color-muted-foreground, #64748b)"
1758
- },
1759
- children: subtitle
1760
- })
1761
- ]
1762
- }),
1763
- actions && /* @__PURE__ */ jsx7("div", {
1764
- className: "ds-detail-card__actions",
1765
- children: actions
1766
- })
1767
- ]
1768
- }),
1769
- /* @__PURE__ */ jsx7("dl", {
1770
- className: "ds-detail-card__body",
1771
- style: {
1772
- margin: 0,
1773
- padding: "12px 16px",
1774
- display: "grid",
1775
- gridTemplateColumns: "max-content 1fr",
1776
- columnGap: "24px",
1777
- rowGap: "8px",
1778
- alignItems: "baseline"
1779
- },
1780
- children: fields.map((f, i) => /* @__PURE__ */ jsxs5(React4.Fragment, {
1781
- children: [
1782
- /* @__PURE__ */ jsx7("dt", {
1783
- className: "ds-detail-card__label",
1784
- style: {
1785
- fontWeight: 600,
1786
- color: "var(--ds-color-foreground, #0f172a)",
1787
- fontSize: "var(--ds-font-size-sm, 13px)",
1788
- lineHeight: 1.4
1789
- },
1790
- children: f.label
1791
- }),
1792
- /* @__PURE__ */ jsx7("dd", {
1793
- className: "ds-detail-card__value",
1794
- style: {
1795
- margin: 0,
1796
- color: "var(--ds-color-foreground, #0f172a)",
1797
- fontSize: "var(--ds-font-size-sm, 13px)",
1798
- lineHeight: 1.4
1799
- },
1800
- children: f.value !== undefined && f.value !== null && f.value !== "" ? f.value : "—"
1801
- })
1802
- ]
1803
- }, `${f.label}-${i}`))
1804
- })
1805
- ]
1806
- });
1807
- }
1808
-
1809
- // src/components/ui/Dialog.tsx
1810
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1811
- function Dialog({
1812
- open = true,
1813
- title,
1814
- description,
1815
- submitLabel = "Submit",
1816
- cancelLabel = "Cancel",
1817
- onCancel,
1818
- onSubmit,
1819
- children,
1820
- size = "large",
1821
- formId,
1822
- className = ""
1823
- }) {
1824
- if (!open)
1825
- return null;
1826
- return /* @__PURE__ */ jsx8("div", {
1827
- className: "ds-dialog__backdrop",
1828
- onClick: onCancel,
1829
- children: /* @__PURE__ */ jsxs6("div", {
1830
- className: `ds-dialog ${size === "large" ? "ds-dialog--lg" : ""} ${className}`,
1831
- role: "dialog",
1832
- "aria-modal": "true",
1833
- onClick: (e) => e.stopPropagation(),
1834
- children: [
1835
- /* @__PURE__ */ jsxs6("div", {
1836
- className: "ds-dialog__header",
1837
- children: [
1838
- /* @__PURE__ */ jsxs6("div", {
1839
- className: "ds-dialog__heading",
1840
- children: [
1841
- /* @__PURE__ */ jsx8("h2", {
1842
- className: "ds-dialog__title",
1843
- children: title
1844
- }),
1845
- description && /* @__PURE__ */ jsx8("p", {
1846
- className: "ds-dialog__description",
1847
- children: description
1848
- })
1849
- ]
1850
- }),
1851
- onCancel && /* @__PURE__ */ jsx8(Button, {
1852
- variant: "ghost",
1853
- size: "sm",
1854
- icon: "X",
1855
- "aria-label": "Close",
1856
- onClick: onCancel,
1857
- className: "ds-dialog__close"
1858
- })
1859
- ]
1860
- }),
1861
- /* @__PURE__ */ jsxs6("form", {
1862
- id: formId,
1863
- onSubmit: (e) => {
1864
- e.preventDefault();
1865
- onSubmit?.();
1866
- },
1867
- className: "ds-dialog__form",
1868
- children: [
1869
- /* @__PURE__ */ jsx8("div", {
1870
- className: "ds-dialog__body",
1871
- children
1872
- }),
1873
- /* @__PURE__ */ jsx8("div", {
1874
- className: "ds-dialog__footer",
1875
- children: /* @__PURE__ */ jsxs6(ButtonBar, {
1876
- justify: "between",
1877
- children: [
1878
- onCancel && /* @__PURE__ */ jsx8(Button, {
1879
- variant: "secondary",
1880
- onClick: onCancel,
1881
- children: cancelLabel
1882
- }),
1883
- onSubmit && /* @__PURE__ */ jsx8(Button, {
1884
- variant: "primary",
1885
- type: "submit",
1886
- children: submitLabel
1887
- })
1888
- ]
1889
- })
1890
- })
1891
- ]
1892
- })
1893
- ]
1894
- })
1895
- });
1896
- }
1897
- function actionFormDialogTemplateV1(spec) {
1898
- return spec;
1899
- }
1900
- function alertDialogTemplateV1(spec) {
1901
- return {
1902
- open: spec.open,
1903
- title: spec.title,
1904
- description: spec.message,
1905
- submitLabel: spec.primaryAction?.label ?? spec.confirmLabel ?? "Confirm",
1906
- cancelLabel: spec.cancelLabel ?? "Cancel",
1907
- onSubmit: spec.primaryAction?.onClick ?? spec.onConfirm,
1908
- onCancel: spec.onCancel,
1909
- children: null
1910
- };
1911
- }
1912
-
1913
- // src/components/ui/Input.tsx
1914
- import * as React5 from "react";
1915
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1916
- function Input({ label, required, className = "", id, ...rest }) {
1917
- const generatedId = React5.useId();
1918
- const inputId = id ?? generatedId;
1919
- const inputEl = /* @__PURE__ */ jsx9("input", {
1920
- id: inputId,
1921
- className: `ds-field__control ds-input ${className}`,
1922
- required,
1923
- style: {
1924
- width: "100%",
1925
- height: "32px",
1926
- padding: "4px 8px",
1927
- fontSize: "13px",
1928
- fontFamily: "inherit",
1929
- border: "1px solid var(--ds-color-border, #cbd5e1)",
1930
- borderRadius: "4px",
1931
- backgroundColor: "var(--ds-color-surface, #ffffff)",
1932
- color: "var(--ds-color-foreground, #0f172a)",
1933
- outline: "none",
1934
- boxSizing: "border-box"
1935
- },
1936
- ...rest
1937
- });
1938
- if (!label)
1939
- return inputEl;
1940
- return /* @__PURE__ */ jsxs7("div", {
1941
- className: "ds-field-wrapper binding-stack-tight",
1942
- style: { width: "100%" },
1943
- children: [
1944
- /* @__PURE__ */ jsxs7("label", {
1945
- htmlFor: inputId,
1946
- className: "ds-field__label",
1947
- style: {
1948
- fontSize: "12px",
1949
- fontWeight: 500,
1950
- color: "var(--ds-color-foreground, #0f172a)",
1951
- display: "flex",
1952
- alignItems: "center",
1953
- gap: "2px"
1954
- },
1955
- children: [
1956
- label,
1957
- required && /* @__PURE__ */ jsx9("span", {
1958
- style: { color: "var(--ds-color-destructive, #ef4444)" },
1959
- children: "*"
1960
- })
1961
- ]
1962
- }),
1963
- /* @__PURE__ */ jsx9("div", {
1964
- className: "ds-field",
1965
- style: { width: "100%" },
1966
- children: inputEl
1967
- })
1968
- ]
1969
- });
1970
- }
1971
- function TextArea({ label, required, className = "", id, ...rest }) {
1972
- const generatedId = React5.useId();
1973
- const textareaId = id ?? generatedId;
1974
- const textEl = /* @__PURE__ */ jsx9("textarea", {
1975
- id: textareaId,
1976
- className: `ds-field__control ds-textarea ${className}`,
1977
- required,
1978
- style: {
1979
- width: "100%",
1980
- minHeight: "80px",
1981
- padding: "6px 8px",
1982
- fontSize: "13px",
1983
- fontFamily: "inherit",
1984
- border: "1px solid var(--ds-color-border, #cbd5e1)",
1985
- borderRadius: "4px",
1986
- backgroundColor: "var(--ds-color-surface, #ffffff)",
1987
- color: "var(--ds-color-foreground, #0f172a)",
1988
- outline: "none",
1989
- resize: "vertical",
1990
- boxSizing: "border-box"
1991
- },
1992
- ...rest
1993
- });
1994
- if (!label)
1995
- return textEl;
1996
- return /* @__PURE__ */ jsxs7("div", {
1997
- className: "ds-field-wrapper binding-stack-tight",
1998
- style: { width: "100%" },
1999
- children: [
2000
- /* @__PURE__ */ jsxs7("label", {
2001
- htmlFor: textareaId,
2002
- className: "ds-field__label",
2003
- style: {
2004
- fontSize: "12px",
2005
- fontWeight: 500,
2006
- color: "var(--ds-color-foreground, #0f172a)",
2007
- display: "flex",
2008
- alignItems: "center",
2009
- gap: "2px"
2010
- },
2011
- children: [
2012
- label,
2013
- required && /* @__PURE__ */ jsx9("span", {
2014
- style: { color: "var(--ds-color-destructive, #ef4444)" },
2015
- children: "*"
2016
- })
2017
- ]
2018
- }),
2019
- /* @__PURE__ */ jsx9("div", {
2020
- className: "ds-field",
2021
- style: { width: "100%" },
2022
- children: textEl
2023
- })
2024
- ]
2025
- });
2026
- }
2027
- function Select({
2028
- label,
2029
- required,
2030
- value,
2031
- options = [],
2032
- onChange,
2033
- placeholder,
2034
- disabled,
2035
- className = "",
2036
- id,
2037
- ...rest
2038
- }) {
2039
- const generatedId = React5.useId();
2040
- const selectId = id ?? generatedId;
2041
- const selectEl = /* @__PURE__ */ jsxs7("select", {
2042
- id: selectId,
2043
- className: `ds-field__control ds-select ${className}`,
2044
- disabled,
2045
- value: value ?? "",
2046
- onChange: (e) => onChange?.(e.target.value || null),
2047
- style: {
2048
- width: "100%",
2049
- height: "32px",
2050
- padding: "4px 8px",
2051
- fontSize: "13px",
2052
- fontFamily: "inherit",
2053
- border: "1px solid var(--ds-color-border, #cbd5e1)",
2054
- borderRadius: "4px",
2055
- backgroundColor: disabled ? "var(--ds-color-surface-subtle, #f8fafc)" : "var(--ds-color-surface, #ffffff)",
2056
- color: "var(--ds-color-foreground, #0f172a)",
2057
- outline: "none",
2058
- cursor: disabled ? "not-allowed" : "pointer",
2059
- boxSizing: "border-box"
2060
- },
2061
- ...rest,
2062
- children: [
2063
- placeholder && /* @__PURE__ */ jsx9("option", {
2064
- value: "",
2065
- children: placeholder
2066
- }),
2067
- options.map((opt) => /* @__PURE__ */ jsx9("option", {
2068
- value: opt.value,
2069
- children: opt.label || opt.value
2070
- }, opt.value))
2071
- ]
2072
- });
2073
- if (!label)
2074
- return selectEl;
2075
- return /* @__PURE__ */ jsxs7("div", {
2076
- className: "ds-field-wrapper binding-stack-tight",
2077
- style: { width: "100%" },
2078
- children: [
2079
- /* @__PURE__ */ jsxs7("label", {
2080
- htmlFor: selectId,
2081
- className: "ds-field__label",
2082
- style: {
2083
- fontSize: "12px",
2084
- fontWeight: 500,
2085
- color: "var(--ds-color-foreground, #0f172a)",
2086
- display: "flex",
2087
- alignItems: "center",
2088
- gap: "2px"
2089
- },
2090
- children: [
2091
- label,
2092
- required && /* @__PURE__ */ jsx9("span", {
2093
- style: { color: "var(--ds-color-destructive, #ef4444)" },
2094
- children: "*"
2095
- })
2096
- ]
2097
- }),
2098
- /* @__PURE__ */ jsx9("div", {
2099
- className: "ds-field",
2100
- style: { width: "100%" },
2101
- children: selectEl
2102
- })
2103
- ]
2104
- });
2105
- }
2106
- function Checkbox({ label, checked = false, onChange, disabled, className = "", id, ...rest }) {
2107
- const generatedId = React5.useId();
2108
- const checkboxId = id ?? generatedId;
2109
- return /* @__PURE__ */ jsxs7("label", {
2110
- htmlFor: checkboxId,
2111
- className: `ds-checkbox-wrapper ${className}`,
2112
- style: {
2113
- display: "inline-flex",
2114
- alignItems: "center",
2115
- gap: "8px",
2116
- cursor: disabled ? "not-allowed" : "pointer",
2117
- fontSize: "13px",
2118
- color: "var(--ds-color-foreground, #0f172a)"
2119
- },
2120
- children: [
2121
- /* @__PURE__ */ jsx9("input", {
2122
- type: "checkbox",
2123
- id: checkboxId,
2124
- className: "ds-checkbox",
2125
- checked,
2126
- disabled,
2127
- onChange: (e) => onChange?.(e.target.checked),
2128
- style: {
2129
- width: "16px",
2130
- height: "16px",
2131
- accentColor: "var(--ds-color-primary, #0284c7)",
2132
- cursor: disabled ? "not-allowed" : "pointer"
2133
- },
2134
- ...rest
2135
- }),
2136
- /* @__PURE__ */ jsx9("span", {
2137
- children: label
2138
- })
2139
- ]
2140
- });
2141
- }
2142
-
2143
- // src/components/WorkspaceContext.tsx
2144
- import { jsx as jsx10 } from "react/jsx-runtime";
2145
- var defaultComponents = {
2146
- AppGrid,
2147
- Button,
2148
- ButtonBar,
2149
- ButtonGroup,
2150
- Badge,
2151
- Banner,
2152
- DetailCard,
2153
- Select,
2154
- Input,
2155
- TextArea,
2156
- Checkbox,
2157
- Dialog,
2158
- createActionColumn
2159
- };
2160
- var WorkspaceComponentsContext = React6.createContext(defaultComponents);
2161
- function WorkspaceComponentsProvider({
2162
- components,
2163
- children
2164
- }) {
2165
- const merged = React6.useMemo(() => {
2166
- return {
2167
- AppGrid: components?.AppGrid ?? defaultComponents.AppGrid,
2168
- Button: components?.Button ?? defaultComponents.Button,
2169
- ButtonBar: components?.ButtonBar ?? defaultComponents.ButtonBar,
2170
- ButtonGroup: components?.ButtonGroup ?? defaultComponents.ButtonGroup,
2171
- Badge: components?.Badge ?? defaultComponents.Badge,
2172
- Banner: components?.Banner ?? defaultComponents.Banner,
2173
- DetailCard: components?.DetailCard ?? defaultComponents.DetailCard,
2174
- Select: components?.Select ?? defaultComponents.Select,
2175
- Input: components?.Input ?? defaultComponents.Input,
2176
- TextArea: components?.TextArea ?? defaultComponents.TextArea,
2177
- Checkbox: components?.Checkbox ?? defaultComponents.Checkbox,
2178
- Dialog: components?.Dialog ?? defaultComponents.Dialog,
2179
- createActionColumn: components?.createActionColumn ?? defaultComponents.createActionColumn
2180
- };
2181
- }, [components]);
2182
- return /* @__PURE__ */ jsx10(WorkspaceComponentsContext.Provider, {
2183
- value: merged,
2184
- children
2185
- });
2186
- }
2187
- function useWorkspaceComponents() {
2188
- return React6.useContext(WorkspaceComponentsContext);
2189
- }
2190
-
2191
- // src/components/ApiMappings.tsx
2192
- import * as React7 from "react";
2193
- import { jsx as jsx11, jsxs as jsxs8, Fragment as Fragment3 } from "react/jsx-runtime";
2194
- var TIMING_OPTIONS = [
2195
- { value: "lazy", label: "Lazy on Click" },
2196
- { value: "prefetch", label: "On Window Load" }
2197
- ];
2198
- var FRESHNESS_OPTIONS = [
2199
- { value: "fresh", label: "Fresh on every open" },
2200
- { value: "reuse", label: "Reuse cached response" },
2201
- { value: "poll", label: "Poll on interval" }
2202
- ];
2203
- function ApiMappings({
2204
- config,
2205
- context,
2206
- focus,
2207
- defaultTarget,
2208
- onChange
2209
- }) {
2210
- const { Badge, Banner, Button, ButtonBar, Dialog, Input, Select } = useWorkspaceComponents();
2211
- const [confirm, setConfirm] = React7.useState(null);
2212
- const targets = contextComponents(context.snapshot);
2213
- const sources = [...sourceComponents(context.snapshot), FREE_TEXT_OPTION];
2214
- const paths = gridPaths(context);
2215
- const gridId = context.snapshot.grid?.id;
2216
- const shown = config.calls.map((call, index) => ({ call, index })).filter(({ call }) => !focus || call.component === focus || call.params.some((p) => p.source === focus));
2217
- const update = (id, f) => onChange((old) => ({ ...old, calls: old.calls.map((c) => c.id === id ? f(c) : c) }));
2218
- const ask = (c, run) => c ? setConfirm(c) : run();
2219
- function changeApi(call, api, method) {
2220
- const run = () => update(call.id, (c) => changeCallTarget(c, api, method, context));
2221
- const lost = droppedBy(call, api, method, context);
2222
- ask(lost > 0 ? {
2223
- title: "Change API Mapping?",
2224
- label: "Change API",
2225
- message: `${lost} mapped request ${lost === 1 ? "property is" : "properties are"} not in ${method ?? ""} ${api} and will be discarded. Mappings with the same name are kept.`,
2226
- run
2227
- } : null, run);
2228
- }
2229
- function remove(call, index) {
2230
- const run = () => onChange((old) => ({ ...old, calls: old.calls.filter((c) => c.id !== call.id) }));
2231
- ask(populated(call) ? {
2232
- title: "Remove API Mapping?",
2233
- label: "Remove",
2234
- message: `Call ${index + 1} (${endpointLabel(call)}) has mapped request properties that will be discarded.`,
2235
- run
2236
- } : null, run);
2237
- }
2238
- function clearAll() {
2239
- const ids = new Set(shown.map((s) => s.call.id));
2240
- const run = () => onChange((old) => ({ ...old, calls: old.calls.filter((c) => !ids.has(c.id)) }));
2241
- ask(shown.some((s) => populated(s.call)) ? {
2242
- title: "Clear API Mappings?",
2243
- label: "Clear Mappings",
2244
- message: `${ids.size} API ${ids.size === 1 ? "mapping" : "mappings"} and their request properties will be discarded.`,
2245
- run
2246
- } : null, run);
2247
- }
2248
- const add = (purpose) => onChange((old) => ({ ...old, calls: [...old.calls, newCall(purpose, defaultTarget ?? focus ?? "")] }));
2249
- return /* @__PURE__ */ jsxs8("div", {
2250
- className: "binding-stack",
2251
- "aria-label": "API mappings",
2252
- children: [
2253
- /* @__PURE__ */ jsxs8("div", {
2254
- className: "binding-section-title",
2255
- children: [
2256
- /* @__PURE__ */ jsx11("h3", {
2257
- children: "API mappings"
2258
- }),
2259
- /* @__PURE__ */ jsxs8(ButtonBar, {
2260
- children: [
2261
- /* @__PURE__ */ jsx11(Button, {
2262
- icon: "Plus",
2263
- onClick: () => add("read"),
2264
- children: "Add Read Call"
2265
- }),
2266
- /* @__PURE__ */ jsx11(Button, {
2267
- icon: "Plus",
2268
- onClick: () => add("write"),
2269
- children: "Add Write Call"
2270
- }),
2271
- shown.length > 0 && /* @__PURE__ */ jsx11(Button, {
2272
- icon: "Trash2",
2273
- variant: "ghost",
2274
- onClick: clearAll,
2275
- children: "Clear Mappings"
2276
- })
2277
- ]
2278
- })
2279
- ]
2280
- }),
2281
- shown.length === 0 && /* @__PURE__ */ jsxs8("p", {
2282
- className: "binding-muted",
2283
- children: [
2284
- "No API mappings ",
2285
- focus ? "for this component yet" : "yet",
2286
- ". Add a read or write call."
2287
- ]
2288
- }),
2289
- shown.map(({ call, index }) => {
2290
- const def = definitionFor(context, call.api, call.method);
2291
- const issues = callIssues(call, index, context);
2292
- const related = focus && call.component !== focus;
2293
- return /* @__PURE__ */ jsxs8("section", {
2294
- className: "binding-call",
2295
- "aria-label": `Call ${index + 1}`,
2296
- children: [
2297
- /* @__PURE__ */ jsxs8("div", {
2298
- className: "binding-section-title",
2299
- children: [
2300
- /* @__PURE__ */ jsxs8("div", {
2301
- className: "binding-stack-tight",
2302
- children: [
2303
- /* @__PURE__ */ jsxs8("strong", {
2304
- children: [
2305
- index + 1,
2306
- ". ",
2307
- call.purpose === "read" ? "Read" : "Write",
2308
- " ·",
2309
- " ",
2310
- targets.find((t) => t.value === call.component)?.label ?? (call.component || "No target")
2311
- ]
2312
- }),
2313
- /* @__PURE__ */ jsxs8("code", {
2314
- className: "binding-endpoint",
2315
- children: [
2316
- endpointLabel(call),
2317
- def?.hook ? ` · ${def.hook}` : ""
2318
- ]
2319
- })
2320
- ]
2321
- }),
2322
- /* @__PURE__ */ jsxs8(ButtonBar, {
2323
- children: [
2324
- related && /* @__PURE__ */ jsx11(Badge, {
2325
- children: "Sends this field"
2326
- }),
2327
- def?.available === false && /* @__PURE__ */ jsx11(Badge, {
2328
- variant: "danger",
2329
- children: "Unavailable"
2330
- }),
2331
- /* @__PURE__ */ jsx11(Badge, {
2332
- variant: issues.length ? "warning" : "success",
2333
- children: issues.length ? `${issues.length} to resolve` : "Ready"
2334
- }),
2335
- /* @__PURE__ */ jsx11(Button, {
2336
- icon: "ArrowUp",
2337
- "aria-label": `Move Call ${index + 1} Up`,
2338
- tooltip: "Move Up",
2339
- disabled: index === 0,
2340
- onClick: () => onChange((old) => ({ ...old, calls: moveCall(old.calls, call.id, -1) }))
2341
- }),
2342
- /* @__PURE__ */ jsx11(Button, {
2343
- icon: "ArrowDown",
2344
- "aria-label": `Move Call ${index + 1} Down`,
2345
- tooltip: "Move Down",
2346
- disabled: index === config.calls.length - 1,
2347
- onClick: () => onChange((old) => ({ ...old, calls: moveCall(old.calls, call.id, 1) }))
2348
- }),
2349
- /* @__PURE__ */ jsx11(Button, {
2350
- icon: "Trash2",
2351
- "aria-label": `Remove Call ${index + 1}`,
2352
- tooltip: "Remove",
2353
- onClick: () => remove(call, index)
2354
- })
2355
- ]
2356
- })
2357
- ]
2358
- }),
2359
- /* @__PURE__ */ jsxs8("div", {
2360
- className: "binding-call-grid",
2361
- children: [
2362
- /* @__PURE__ */ jsx11(Select, {
2363
- label: "Purpose",
2364
- value: call.purpose,
2365
- options: [
2366
- { value: "read", label: "Read (load data)" },
2367
- { value: "write", label: "Write (send data)" }
2368
- ],
2369
- onChange: (v) => update(call.id, (c) => changePurpose(c, v === "write" ? "write" : "read"))
2370
- }),
2371
- /* @__PURE__ */ jsx11(Select, {
2372
- label: "Component",
2373
- value: call.component || null,
2374
- placeholder: "Choose a component",
2375
- options: [
2376
- ...targets,
2377
- ...call.component && !targets.some((t) => t.value === call.component) ? [{ value: call.component, label: `${call.component} (missing)` }] : []
2378
- ],
2379
- onChange: (v) => update(call.id, (c) => ({ ...c, component: String(v ?? "") }))
2380
- }),
2381
- /* @__PURE__ */ jsx11(Select, {
2382
- label: "API",
2383
- value: call.api || null,
2384
- placeholder: "Choose an API",
2385
- options: [
2386
- ...apisOf(context).map((api) => {
2387
- const defs = context.definitions.filter((d) => d.api === api);
2388
- return {
2389
- value: api,
2390
- label: api,
2391
- description: defs.map((d) => `${d.method}${d.label ? ` ${d.label}` : ""}`).join(" · "),
2392
- ...defs.every((d) => d.available === false) ? { badges: [{ label: "Unavailable", variant: "danger" }] } : {}
2393
- };
2394
- }),
2395
- ...call.api && !apisOf(context).includes(call.api) ? [{ value: call.api, label: `${call.api} (not registered)` }] : []
2396
- ],
2397
- onChange: (v) => changeApi(call, String(v ?? ""))
2398
- }),
2399
- /* @__PURE__ */ jsx11(Select, {
2400
- label: "Method",
2401
- value: call.method || null,
2402
- disabled: !call.api,
2403
- placeholder: "Choose a method",
2404
- options: methodsFor(context, call.api).map((m) => {
2405
- const d = definitionFor(context, call.api, m);
2406
- return {
2407
- value: m,
2408
- label: m,
2409
- description: d?.label,
2410
- ...d?.available === false ? { badges: [{ label: "Unavailable", variant: "danger" }] } : {}
2411
- };
2412
- }),
2413
- onChange: (v) => changeApi(call, call.api, String(v ?? ""))
2414
- }),
2415
- call.purpose === "read" && /* @__PURE__ */ jsxs8(Fragment3, {
2416
- children: [
2417
- /* @__PURE__ */ jsx11(Select, {
2418
- label: "Load Timing",
2419
- value: call.timing || null,
2420
- options: TIMING_OPTIONS,
2421
- onChange: (v) => update(call.id, (c) => ({ ...c, timing: v === "prefetch" ? "prefetch" : "lazy" }))
2422
- }),
2423
- /* @__PURE__ */ jsx11(Select, {
2424
- label: "Freshness",
2425
- value: call.freshness || null,
2426
- options: FRESHNESS_OPTIONS,
2427
- onChange: (v) => update(call.id, (c) => ({
2428
- ...c,
2429
- freshness: v ?? "fresh",
2430
- pollSeconds: v === "poll" ? c.pollSeconds : undefined
2431
- }))
2432
- }),
2433
- call.freshness === "poll" && /* @__PURE__ */ jsx11(Input, {
2434
- label: "Poll Every (s)",
2435
- type: "number",
2436
- min: 1,
2437
- value: call.pollSeconds ?? "",
2438
- onChange: (e) => {
2439
- const n = Number(e.target.value);
2440
- update(call.id, (c) => ({ ...c, pollSeconds: e.target.value === "" ? undefined : n }));
2441
- }
2442
- })
2443
- ]
2444
- })
2445
- ]
2446
- }),
2447
- def?.available === false && /* @__PURE__ */ jsxs8(Banner, {
2448
- tone: "caution",
2449
- children: [
2450
- "This API is registered but cannot run: ",
2451
- def.problem
2452
- ]
2453
- }),
2454
- call.api && def && call.params.length === 0 && /* @__PURE__ */ jsx11("p", {
2455
- className: "binding-muted",
2456
- children: "This API takes no request properties."
2457
- }),
2458
- call.params.length > 0 && /* @__PURE__ */ jsxs8("div", {
2459
- className: "binding-params",
2460
- role: "table",
2461
- "aria-label": `Request properties for call ${index + 1}`,
2462
- children: [
2463
- /* @__PURE__ */ jsxs8("div", {
2464
- className: "binding-param binding-muted",
2465
- role: "row",
2466
- children: [
2467
- /* @__PURE__ */ jsx11("span", {
2468
- role: "columnheader",
2469
- children: "Request property"
2470
- }),
2471
- /* @__PURE__ */ jsx11("span", {
2472
- role: "columnheader",
2473
- children: "Source"
2474
- }),
2475
- /* @__PURE__ */ jsx11("span", {
2476
- role: "columnheader",
2477
- children: "Property / value"
2478
- })
2479
- ]
2480
- }),
2481
- call.params.map((p) => {
2482
- const spec = def?.params.find((d) => d.name === p.param);
2483
- return /* @__PURE__ */ jsxs8("div", {
2484
- className: "binding-param",
2485
- role: "row",
2486
- children: [
2487
- /* @__PURE__ */ jsxs8("div", {
2488
- className: "binding-stack-tight",
2489
- role: "cell",
2490
- children: [
2491
- /* @__PURE__ */ jsxs8("span", {
2492
- children: [
2493
- p.param,
2494
- spec?.required ? " *" : ""
2495
- ]
2496
- }),
2497
- /* @__PURE__ */ jsx11("span", {
2498
- className: "binding-muted",
2499
- children: spec ? `${spec.type} · ${spec.in ?? "body"}${spec.required ? " · required" : ""}` : "Not in contract"
2500
- }),
2501
- !spec && /* @__PURE__ */ jsx11(Button, {
2502
- size: "sm",
2503
- variant: "ghost",
2504
- onClick: () => update(call.id, (c) => ({ ...c, params: c.params.filter((x) => x.param !== p.param) })),
2505
- children: "Remove Property"
2506
- })
2507
- ]
2508
- }),
2509
- /* @__PURE__ */ jsx11("div", {
2510
- role: "cell",
2511
- children: /* @__PURE__ */ jsx11(Select, {
2512
- "aria-label": `Source for ${p.param}`,
2513
- value: p.source || null,
2514
- placeholder: "Not sent",
2515
- options: [{ value: "", label: "Not sent" }, ...sources],
2516
- onChange: (v) => update(call.id, (c) => ({
2517
- ...c,
2518
- params: c.params.map((x) => x.param === p.param ? v ? setParamSource(x, String(v), gridId) : { ...x, source: "", property: "" } : x)
2519
- }))
2520
- })
2521
- }),
2522
- /* @__PURE__ */ jsx11("div", {
2523
- role: "cell",
2524
- children: p.source === FREE_TEXT_SOURCE ? /* @__PURE__ */ jsx11(Input, {
2525
- "aria-label": `Fixed value for ${p.param}`,
2526
- value: p.property,
2527
- placeholder: spec?.type === "boolean" ? "true or false" : "Fixed value",
2528
- onChange: (e) => {
2529
- const value = e.target.value;
2530
- update(call.id, (c) => ({
2531
- ...c,
2532
- params: c.params.map((x) => x.param === p.param ? { ...x, property: value } : x)
2533
- }));
2534
- }
2535
- }) : gridId && p.source === gridId ? /* @__PURE__ */ jsx11(Select, {
2536
- "aria-label": `Grid property for ${p.param}`,
2537
- value: p.property || null,
2538
- placeholder: "Choose a grid property",
2539
- options: paths.map((path) => ({
2540
- value: path,
2541
- label: path.slice(gridId.length + 1),
2542
- description: `First row: ${String(path.slice(gridId.length + 1).split(".").reduce((o, k) => o?.[k], context.snapshot.grid?.rows[0]) ?? "—")}`
2543
- })),
2544
- onChange: (v) => update(call.id, (c) => ({
2545
- ...c,
2546
- params: c.params.map((x) => x.param === p.param ? { ...x, property: String(v ?? "") } : x)
2547
- }))
2548
- }) : /* @__PURE__ */ jsx11("span", {
2549
- className: "binding-muted",
2550
- children: p.source ? p.property : "—"
2551
- })
2552
- })
2553
- ]
2554
- }, p.param);
2555
- })
2556
- ]
2557
- }),
2558
- issues.length > 0 && /* @__PURE__ */ jsx11("div", {
2559
- role: "status",
2560
- children: issues.map((i) => /* @__PURE__ */ jsx11("p", {
2561
- className: "binding-error",
2562
- children: i
2563
- }, i))
2564
- })
2565
- ]
2566
- }, call.id);
2567
- }),
2568
- confirm && /* @__PURE__ */ jsx11(Dialog, {
2569
- ...alertDialogTemplateV1({
2570
- open: true,
2571
- kind: "destructive",
2572
- title: confirm.title,
2573
- message: confirm.message,
2574
- onCancel: () => setConfirm(null),
2575
- primaryAction: {
2576
- label: confirm.label,
2577
- onClick: () => {
2578
- confirm.run();
2579
- setConfirm(null);
2580
- }
2581
- }
2582
- })
2583
- })
2584
- ]
2585
- });
2586
- }
2587
-
2588
- // src/components/RequestPreview.tsx
2589
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
2590
- var json = (o) => Object.keys(o).length ? JSON.stringify(o, null, 2) : "—";
2591
- function RequestPreview({ title, requests }) {
2592
- const { Badge } = useWorkspaceComponents();
2593
- return /* @__PURE__ */ jsxs9("div", {
2594
- className: "binding-stack",
2595
- "aria-label": title,
2596
- children: [
2597
- /* @__PURE__ */ jsx12("h3", {
2598
- children: title
2599
- }),
2600
- requests.length === 0 && /* @__PURE__ */ jsx12("p", {
2601
- className: "binding-muted",
2602
- children: "No calls to preview."
2603
- }),
2604
- requests.map((r) => /* @__PURE__ */ jsxs9("div", {
2605
- className: "binding-call",
2606
- children: [
2607
- /* @__PURE__ */ jsxs9("div", {
2608
- className: "binding-section-title",
2609
- children: [
2610
- /* @__PURE__ */ jsxs9("strong", {
2611
- children: [
2612
- r.order,
2613
- ". ",
2614
- r.purpose === "read" ? "Read" : "Write",
2615
- " · ",
2616
- r.target
2617
- ]
2618
- }),
2619
- /* @__PURE__ */ jsx12(Badge, {
2620
- variant: !r.available ? "danger" : r.issues.length ? "warning" : "neutral",
2621
- children: !r.available ? "Unavailable" : r.issues.length ? "Incomplete" : "Simulated only"
2622
- })
2623
- ]
2624
- }),
2625
- /* @__PURE__ */ jsxs9("code", {
2626
- className: "binding-endpoint",
2627
- children: [
2628
- r.method,
2629
- " ",
2630
- r.api || "No API",
2631
- r.hook ? ` · ${r.hook}` : ""
2632
- ]
2633
- }),
2634
- /* @__PURE__ */ jsxs9("div", {
2635
- className: "binding-preview-parts",
2636
- children: [
2637
- /* @__PURE__ */ jsxs9("div", {
2638
- children: [
2639
- /* @__PURE__ */ jsx12("span", {
2640
- className: "binding-muted",
2641
- children: "Body"
2642
- }),
2643
- /* @__PURE__ */ jsx12("pre", {
2644
- className: "binding-pre",
2645
- children: json(r.parts.data)
2646
- })
2647
- ]
2648
- }),
2649
- /* @__PURE__ */ jsxs9("div", {
2650
- children: [
2651
- /* @__PURE__ */ jsx12("span", {
2652
- className: "binding-muted",
2653
- children: "Query"
2654
- }),
2655
- /* @__PURE__ */ jsx12("pre", {
2656
- className: "binding-pre",
2657
- children: json(r.parts.params)
2658
- })
2659
- ]
2660
- }),
2661
- /* @__PURE__ */ jsxs9("div", {
2662
- children: [
2663
- /* @__PURE__ */ jsx12("span", {
2664
- className: "binding-muted",
2665
- children: "Path"
2666
- }),
2667
- /* @__PURE__ */ jsx12("pre", {
2668
- className: "binding-pre",
2669
- children: json(r.parts.path)
2670
- })
2671
- ]
2672
- })
2673
- ]
2674
- })
2675
- ]
2676
- }, r.id))
2677
- ]
2678
- });
2679
- }
2680
-
2681
- // src/components/SimulationDialog.tsx
2682
- import * as React8 from "react";
2683
- import { jsx as jsx13, jsxs as jsxs10, Fragment as Fragment4 } from "react/jsx-runtime";
2684
- function SimulationDialog({
2685
- config,
2686
- context,
2687
- onClose,
2688
- onVerified
2689
- }) {
2690
- const {
2691
- Banner,
2692
- Button,
2693
- ButtonBar,
2694
- ButtonGroup,
2695
- DetailCard,
2696
- Dialog,
2697
- Checkbox,
2698
- Input,
2699
- Select,
2700
- TextArea
2701
- } = useWorkspaceComponents();
2702
- const [input, setInput] = React8.useState(() => {
2703
- const initial = {
2704
- notesType: "",
2705
- template: "",
2706
- subject: "",
2707
- notes: "",
2708
- addCallLog: false
2709
- };
2710
- for (const comp of context.snapshot.components) {
2711
- if (comp.kind === "checkbox") {
2712
- initial[comp.name] = comp.value === "true";
2713
- } else {
2714
- initial[comp.name] = comp.value ?? "";
2715
- }
2716
- }
2717
- return initial;
2718
- });
2719
- const [scope, setScope] = React8.useState("one");
2720
- const [failure, setFailure] = React8.useState(false);
2721
- const [errors, setErrors] = React8.useState([]);
2722
- const [saved, setSaved] = React8.useState([]);
2723
- const [retrieved, setRetrieved] = React8.useState([]);
2724
- const [message, setMessage] = React8.useState("");
2725
- const callLog = config.fields.find((c) => c.name === "addCallLog");
2726
- const source = context.snapshot.grid?.rows ?? [];
2727
- const example = source[0] ?? {};
2728
- const rows = scope === "none" ? [] : scope === "one" ? [source[0] ?? example] : source.length > 1 ? source.slice(0, 2) : [example, { ...example, customerName: "Example customer" }];
2729
- const nameOf = (r) => String(r.customerName ?? r.customerNumber ?? r.id ?? "Example customer");
2730
- const gaps = simulatorGaps(config, context);
2731
- const requests = previewRequests(config, context, actionContext(context, simulationForm(input), rows), (c) => c.purpose === "write" && c.component === SUBMIT);
2732
- function setField(key, value) {
2733
- setInput((prev) => ({ ...prev, [key]: value }));
2734
- setErrors([]);
2735
- }
2736
- function chooseTemplate(value) {
2737
- const template = context.options.template?.find((o) => o.value === value);
2738
- const fromTemplate = (name) => config.templateFills && config.fields.find((c) => c.name === name)?.input === "template";
2739
- setInput((prev) => ({
2740
- ...prev,
2741
- template: value ?? "",
2742
- ...template && fromTemplate("subject") ? { subject: template.subject ?? "" } : {},
2743
- ...template && fromTemplate("notes") ? { notes: template.description ?? "" } : {}
2744
- }));
2745
- }
2746
- function submit() {
2747
- const result = simulateSave(config, context, input, rows, failure);
2748
- setErrors(result.errors);
2749
- if (result.errors.length) {
2750
- setMessage("");
2751
- return;
2752
- }
2753
- setSaved(result.notes);
2754
- setRetrieved([]);
2755
- setMessage(config.success === "close" ? "Simulated popup closed after save. Retrieve the saved note to finish the test." : "Saved. The simulated popup stays open. Retrieve the note to finish the test.");
2756
- }
2757
- const isNotesContract = context.snapshot.source === "Add Notes" || config.fields.some((f) => f.name === "notesType");
2758
- return /* @__PURE__ */ jsx13(Dialog, {
2759
- ...actionFormDialogTemplateV1({
2760
- open: true,
2761
- title: `Try ${context.snapshot.source || "Add Notes"}`,
2762
- description: "Simulation — sample data only.",
2763
- size: "large",
2764
- formId: "binding-simulation",
2765
- submitLabel: "Submit",
2766
- cancelLabel: "Cancel",
2767
- onCancel: onClose,
2768
- onSubmit: submit,
2769
- children: /* @__PURE__ */ jsxs10("div", {
2770
- className: "binding-simulation",
2771
- onInvalidCapture: (event) => {
2772
- event.preventDefault();
2773
- setErrors([...simulationErrors(config, input, rows.length), ...gaps]);
2774
- },
2775
- children: [
2776
- /* @__PURE__ */ jsxs10("div", {
2777
- className: "binding-stack",
2778
- children: [
2779
- /* @__PURE__ */ jsxs10("p", {
2780
- children: [
2781
- "Selected customers: ",
2782
- rows.length ? rows.map(nameOf).join(", ") : "None"
2783
- ]
2784
- }),
2785
- /* @__PURE__ */ jsx13(ButtonGroup, {
2786
- "aria-label": "Selected customers",
2787
- value: scope,
2788
- onChange: setScope,
2789
- options: [
2790
- { value: "one", label: "One Customer" },
2791
- { value: "multiple", label: "Multiple Customers" },
2792
- { value: "none", label: "No Customers" }
2793
- ]
2794
- }),
2795
- /* @__PURE__ */ jsx13(Checkbox, {
2796
- label: "Simulate a failed save",
2797
- checked: failure,
2798
- onChange: setFailure
2799
- })
2800
- ]
2801
- }),
2802
- errors.length > 0 && /* @__PURE__ */ jsx13(Banner, {
2803
- tone: "caution",
2804
- children: /* @__PURE__ */ jsx13("div", {
2805
- role: "alert",
2806
- children: errors.map((error) => /* @__PURE__ */ jsx13("p", {
2807
- children: error
2808
- }, error))
2809
- })
2810
- }),
2811
- isNotesContract ? /* @__PURE__ */ jsxs10(Fragment4, {
2812
- children: [
2813
- /* @__PURE__ */ jsx13(Select, {
2814
- label: "Notes Type",
2815
- required: true,
2816
- value: input.notesType || null,
2817
- onChange: (value) => setField("notesType", String(value ?? "")),
2818
- options: context.options.notesType ?? []
2819
- }),
2820
- /* @__PURE__ */ jsx13(Select, {
2821
- label: "Template",
2822
- value: input.template || null,
2823
- onChange: (value) => chooseTemplate(value),
2824
- options: context.options.template ?? []
2825
- }),
2826
- /* @__PURE__ */ jsx13(Input, {
2827
- label: "Subject",
2828
- required: true,
2829
- value: input.subject,
2830
- onChange: (event) => setField("subject", event.target.value)
2831
- }),
2832
- /* @__PURE__ */ jsx13(TextArea, {
2833
- label: "Description",
2834
- required: true,
2835
- value: input.notes,
2836
- onChange: (event) => setField("notes", event.target.value)
2837
- }),
2838
- callLog && !callLog.excluded && /* @__PURE__ */ jsx13(Checkbox, {
2839
- label: "Add Call Log",
2840
- checked: input.addCallLog,
2841
- onChange: (value) => setField("addCallLog", value)
2842
- }),
2843
- context.options.notesType?.length || context.options.template?.length ? /* @__PURE__ */ jsx13("p", {
2844
- className: "binding-muted",
2845
- children: "Notes Type and Template choices are samples captured from the open window, not responses fetched by this test."
2846
- }) : null
2847
- ]
2848
- }) : config.fields.filter((f) => !f.excluded).map((f) => {
2849
- const comp = context.snapshot.components.find((c) => c.name === f.name);
2850
- const fieldKind = comp?.kind ?? (comp?.type === "select" || comp?.type === "combobox" ? "dropdown" : comp?.type === "textarea" ? "text-area" : comp?.type === "checkbox" ? "checkbox" : "text");
2851
- const opts = context.options[f.name] ?? [];
2852
- if (fieldKind === "dropdown" || opts.length > 0) {
2853
- return /* @__PURE__ */ jsx13(Select, {
2854
- label: f.label,
2855
- required: f.required,
2856
- value: String(input[f.name] ?? "") || null,
2857
- onChange: (val) => setField(f.name, String(val ?? "")),
2858
- options: opts
2859
- }, f.name);
2860
- }
2861
- if (fieldKind === "text-area") {
2862
- return /* @__PURE__ */ jsx13(TextArea, {
2863
- label: f.label,
2864
- required: f.required,
2865
- value: String(input[f.name] ?? ""),
2866
- onChange: (event) => setField(f.name, event.target.value)
2867
- }, f.name);
2868
- }
2869
- if (fieldKind === "checkbox") {
2870
- return /* @__PURE__ */ jsx13(Checkbox, {
2871
- label: f.label,
2872
- checked: Boolean(input[f.name]),
2873
- onChange: (val) => setField(f.name, val)
2874
- }, f.name);
2875
- }
2876
- return /* @__PURE__ */ jsx13(Input, {
2877
- label: f.label,
2878
- required: f.required,
2879
- value: String(input[f.name] ?? ""),
2880
- onChange: (event) => setField(f.name, event.target.value)
2881
- }, f.name);
2882
- }),
2883
- gaps.length > 0 && /* @__PURE__ */ jsx13(Banner, {
2884
- tone: "caution",
2885
- children: /* @__PURE__ */ jsx13("div", {
2886
- children: gaps.map((g) => /* @__PURE__ */ jsx13("p", {
2887
- children: g
2888
- }, g))
2889
- })
2890
- }),
2891
- /* @__PURE__ */ jsx13(RequestPreview, {
2892
- title: "Request preview (sample values)",
2893
- requests
2894
- }),
2895
- message && /* @__PURE__ */ jsx13(Banner, {
2896
- tone: "resolved",
2897
- children: /* @__PURE__ */ jsx13("p", {
2898
- role: "status",
2899
- children: message
2900
- })
2901
- }),
2902
- saved.length > 0 && /* @__PURE__ */ jsx13(ButtonBar, {
2903
- children: /* @__PURE__ */ jsx13(Button, {
2904
- icon: "Search",
2905
- onClick: () => {
2906
- const readback = JSON.parse(JSON.stringify(saved));
2907
- setRetrieved(readback);
2908
- if (readback.length === saved.length && readback.every((note) => note.subject && note.description)) {
2909
- onVerified(fingerprint(config));
2910
- }
2911
- },
2912
- children: "Retrieve Saved Note"
2913
- })
2914
- }),
2915
- retrieved.map((note, i) => /* @__PURE__ */ jsx13(DetailCard, {
2916
- title: `Retrieved note ${i + 1} (${note.customerName || note.customerNumber || "Customer"})`,
2917
- dense: true,
2918
- fields: [
2919
- { label: "Notes Type", value: String(note.notesType ?? "") },
2920
- { label: "Subject", value: String(note.subject ?? "") },
2921
- { label: "Description", value: String(note.description ?? "") }
2922
- ]
2923
- }, i))
2924
- ]
2925
- })
2926
- })
2927
- });
2928
- }
2929
-
2930
- // src/components/ApiBindingWorkspace.tsx
2931
- import { jsx as jsx14, jsxs as jsxs11, Fragment as Fragment6 } from "react/jsx-runtime";
2932
- function loadDraft(key) {
2933
- try {
2934
- const raw = localStorage.getItem(draftKey(key)) || localStorage.getItem(`api-binding-v2:draft:${key}`) || localStorage.getItem(`api-binding:draft:${key}`);
2935
- return raw ? JSON.parse(raw) : null;
2936
- } catch {
2937
- return null;
2938
- }
2939
- }
2940
- function ApiBindingWorkspace({
2941
- pageKey,
2942
- initialContext,
2943
- components
2944
- }) {
2945
- return /* @__PURE__ */ jsx14(WorkspaceComponentsProvider, {
2946
- components,
2947
- children: /* @__PURE__ */ jsx14(ApiBindingWorkspaceInner, {
2948
- pageKey,
2949
- initialContext
2950
- })
2951
- });
2952
- }
2953
- function ApiBindingWorkspaceInner({
2954
- pageKey,
2955
- initialContext
2956
- }) {
2957
- const {
2958
- AppGrid,
2959
- Button,
2960
- ButtonBar,
2961
- Badge,
2962
- Banner,
2963
- Select,
2964
- Input,
2965
- TextArea,
2966
- Checkbox,
2967
- DetailCard,
2968
- createActionColumn
2969
- } = useWorkspaceComponents();
2970
- const [context, setContext] = React9.useState(initialContext ?? null);
2971
- const [config, setConfig] = React9.useState(null);
2972
- const [verified, setVerified] = React9.useState(null);
2973
- const [editing, setEditing] = React9.useState(null);
2974
- const [simulation, setSimulation] = React9.useState(false);
2975
- const [applied, setApplied] = React9.useState(false);
2976
- const [showCustomers, setShowCustomers] = React9.useState(false);
2977
- const [storageError, setStorageError] = React9.useState("");
2978
- const loaded = React9.useRef(false);
2979
- React9.useEffect(() => {
2980
- loaded.current = false;
2981
- function receive(raw) {
2982
- if (!validBindingContext(raw, pageKey))
2983
- return;
2984
- try {
2985
- storeBindingContext(raw);
2986
- } catch {
2987
- setStorageError("Browser storage is unavailable. Changes cannot be kept after refresh.");
2988
- }
2989
- const value = raw;
2990
- setContext(value);
2991
- if (!loaded.current) {
2992
- const draft = loadDraft(pageKey);
2993
- const initial = draft && migrateConfig(draft.config, value) || proposeConfig(value);
2994
- setConfig(initial);
2995
- setVerified(draft?.version === CONFIG_VERSION && draft.verified === fingerprint(initial) ? draft.verified : null);
2996
- try {
2997
- const stored = localStorage.getItem(appliedKey(pageKey)) || localStorage.getItem(`api-binding:applied:${pageKey}`) || localStorage.getItem(`api-binding-v2:applied:${pageKey}`);
2998
- const appliedConfig = stored ? migrateConfig(JSON.parse(stored).config, value) : null;
2999
- setApplied(!!appliedConfig && fingerprint(appliedConfig) === fingerprint(initial));
3000
- } catch {
3001
- setApplied(false);
3002
- }
3003
- loaded.current = true;
3004
- } else {
3005
- setConfig((previous) => previous ? reconcileConfig(previous, value) : proposeConfig(value));
3006
- }
3007
- }
3008
- const local = readBindingContext(pageKey);
3009
- if (local)
3010
- receive(local);
3011
- else if (initialContext && validBindingContext(initialContext, pageKey))
3012
- receive(initialContext);
3013
- const onMessage = (event) => {
3014
- if (event.origin === window.location.origin && (event.data?.type === BINDING_CONTEXT || event.data?.type === "api-binding-v2-context")) {
3015
- receive(event.data.context);
3016
- }
3017
- };
3018
- const onStorage = (event) => {
3019
- if (event.key === contextKey(pageKey) || event.key === `api-binding:context:${pageKey}` || event.key === `api-binding-v2:context:${pageKey}`) {
3020
- receive(readBindingContext(pageKey));
3021
- }
3022
- };
3023
- window.addEventListener("message", onMessage);
3024
- window.addEventListener("storage", onStorage);
3025
- let channel;
3026
- try {
3027
- channel = new BroadcastChannel(BINDING_CHANNEL);
3028
- channel.onmessage = (event) => {
3029
- if (event.data?.key === pageKey) {
3030
- if (event.data?.type === BINDING_SELECTION_UPDATE && event.data?.grid) {
3031
- setContext((prev) => prev ? {
3032
- ...prev,
3033
- snapshot: {
3034
- ...prev.snapshot,
3035
- grid: {
3036
- id: event.data.grid.id,
3037
- label: prev.snapshot.grid?.label,
3038
- columns: prev.snapshot.grid?.columns ?? [],
3039
- rows: event.data.grid.rows
3040
- }
3041
- }
3042
- } : prev);
3043
- } else {
3044
- receive(readBindingContext(pageKey));
3045
- }
3046
- }
3047
- };
3048
- } catch {}
3049
- try {
3050
- window.opener?.postMessage({ type: BINDING_READY, key: pageKey }, window.location.origin);
3051
- window.opener?.postMessage({ type: "api-binding-v2-ready", key: pageKey }, window.location.origin);
3052
- } catch {}
3053
- return () => {
3054
- window.removeEventListener("message", onMessage);
3055
- window.removeEventListener("storage", onStorage);
3056
- channel?.close();
3057
- };
3058
- }, [pageKey, initialContext]);
3059
- React9.useEffect(() => {
3060
- if (!config)
3061
- return;
3062
- try {
3063
- const payload = JSON.stringify({ version: CONFIG_VERSION, config, verified });
3064
- localStorage.setItem(draftKey(pageKey), payload);
3065
- } catch {
3066
- setStorageError("Browser storage is unavailable. Your changes have not been saved.");
3067
- }
3068
- }, [config, verified, pageKey]);
3069
- function change(updater) {
3070
- setConfig((old) => {
3071
- if (!old)
3072
- return old;
3073
- const next = updater(old);
3074
- if (fingerprint(next) !== fingerprint(old)) {
3075
- setVerified(null);
3076
- setApplied(false);
3077
- }
3078
- return next;
3079
- });
3080
- }
3081
- function updateField(name, patch) {
3082
- change((old) => ({
3083
- ...old,
3084
- fields: old.fields.map((c) => c.name === name ? { ...c, ...patch } : c)
3085
- }));
3086
- }
3087
- if (!context || !config) {
3088
- return /* @__PURE__ */ jsx14("main", {
3089
- className: "binding-workspace",
3090
- children: /* @__PURE__ */ jsxs11("div", {
3091
- className: "binding-empty",
3092
- children: [
3093
- /* @__PURE__ */ jsxs11("h1", {
3094
- children: [
3095
- "Bind ",
3096
- pageKey.includes("worklist-add-notes") ? "Add Notes" : "Add Notes"
3097
- ]
3098
- }),
3099
- /* @__PURE__ */ jsx14("p", {
3100
- children: "Open Add Notes and choose Bind API to bring its fields and selected customers here."
3101
- })
3102
- ]
3103
- })
3104
- });
3105
- }
3106
- const issues = issuesFor(config, context);
3107
- const gaps = simulatorGaps(config, context);
3108
- const grid = context.snapshot.grid;
3109
- const submitCalls = config.calls.filter((c) => c.purpose === "write" && c.component === SUBMIT);
3110
- const customerParams = submitCalls.filter((c) => isNoteContract(definitionFor(context, c.api, c.method))).flatMap((c) => c.params.filter((p) => p.param === CUSTOMER_PARAM && grid && p.source === grid.id).map((p) => ({ call: c, param: p })));
3111
- const cancelCalls = config.calls.filter((c) => c.component === CANCEL);
3112
- const customerOptions = gridPaths(context).map((path) => {
3113
- const key = path.slice((grid?.id.length ?? 0) + 1);
3114
- return {
3115
- value: path,
3116
- label: key,
3117
- description: `First selected customer: ${String(key.split(".").reduce((o, k) => o?.[k], grid?.rows[0]) ?? "—")}`
3118
- };
3119
- });
3120
- const selectedField = config.fields.find((c) => c.name === editing);
3121
- const fieldIssues = (name, label) => issues.filter((i) => i.includes(label) || config.calls.some((c, idx) => (c.component === name || c.params.some((p) => p.source === name)) && i.startsWith(`Call ${idx + 1} `)));
3122
- const rows = config.fields.map((field) => {
3123
- const s = fieldSummary(config, context, field.name);
3124
- return {
3125
- id: field.name,
3126
- label: field.label,
3127
- from: s.from,
3128
- to: s.to,
3129
- action: fieldIssues(field.name, field.label).length ? "Resolve" : "Change"
3130
- };
3131
- });
3132
- const columns = [
3133
- { field: "label", headerName: "Popup field" },
3134
- { field: "from", headerName: "Gets its value from", fill: true },
3135
- { field: "to", headerName: "Saves to / Used for", fill: true },
3136
- createActionColumn({
3137
- headerName: "Change or Resolve",
3138
- labelField: "action",
3139
- icon: "Pencil",
3140
- onClick: (row) => setEditing(row.id)
3141
- })
3142
- ];
3143
- const callRows = config.calls.map((c, i) => ({
3144
- id: c.id,
3145
- order: i + 1,
3146
- purpose: c.purpose === "read" ? "Read" : "Write",
3147
- target: componentLabel(context, c.component),
3148
- endpoint: endpointLabel(c),
3149
- mapped: `${c.params.filter((p) => p.source).length} of ${c.params.length}`,
3150
- status: issues.some((x) => x.startsWith(`Call ${i + 1} `)) ? "Needs attention" : "Ready"
3151
- }));
3152
- const callColumns = [
3153
- { field: "order", headerName: "Order", type: "number" },
3154
- { field: "purpose", headerName: "Purpose" },
3155
- { field: "target", headerName: "Component" },
3156
- { field: "endpoint", headerName: "Endpoint", fill: true },
3157
- { field: "mapped", headerName: "Mapped" },
3158
- {
3159
- field: "status",
3160
- headerName: "Status",
3161
- type: "badge",
3162
- badgeVariants: { Ready: "success", "Needs attention": "warning" }
3163
- }
3164
- ];
3165
- const customerNames = grid?.rows.map((row) => String(row.customerName ?? row.customerNumber ?? row.id ?? "Selected customer")) ?? [];
3166
- const field = (name) => config.fields.find((c) => c.name === name);
3167
- const callLog = field("addCallLog");
3168
- const inputValue = (name) => context.snapshot.components.find((c) => c.name === name)?.value ?? "";
3169
- const previewTemplate = context.options.template?.find((option) => option.value === inputValue("template")) ?? context.options.template?.[0];
3170
- const previewValue = (name) => inputValue(name) || (config.templateFills && field(name)?.input === "template" ? (name === "subject" ? previewTemplate?.subject : previewTemplate?.description) ?? "" : "");
3171
- const liveCtx = actionContext(context, snapshotForm(context));
3172
- const panelTitle = editing === "customers" ? "Customer context" : editing === "actions" ? "Submit behavior" : editing === "mappings" ? "Manage API mappings" : selectedField?.label;
3173
- const focus = editing === "actions" ? SUBMIT : selectedField ? selectedField.name : undefined;
3174
- return /* @__PURE__ */ jsxs11("main", {
3175
- className: "binding-workspace",
3176
- children: [
3177
- /* @__PURE__ */ jsxs11("header", {
3178
- className: "binding-header",
3179
- children: [
3180
- /* @__PURE__ */ jsxs11("div", {
3181
- className: "binding-stack",
3182
- children: [
3183
- /* @__PURE__ */ jsxs11("h1", {
3184
- children: [
3185
- "Bind ",
3186
- context.snapshot.source || "Add Notes"
3187
- ]
3188
- }),
3189
- /* @__PURE__ */ jsx14("p", {
3190
- className: "binding-muted",
3191
- children: "Review how this popup loads and saves information."
3192
- })
3193
- ]
3194
- }),
3195
- /* @__PURE__ */ jsx14(Badge, {
3196
- variant: issues.length ? "warning" : "success",
3197
- children: issues.length ? `${issues.length} connections to resolve` : "Connections ready"
3198
- })
3199
- ]
3200
- }),
3201
- /* @__PURE__ */ jsxs11("div", {
3202
- className: "binding-body",
3203
- children: [
3204
- /* @__PURE__ */ jsxs11("div", {
3205
- className: "binding-columns",
3206
- children: [
3207
- /* @__PURE__ */ jsxs11("div", {
3208
- className: "binding-main",
3209
- children: [
3210
- /* @__PURE__ */ jsx14("section", {
3211
- className: "binding-section",
3212
- "aria-label": "Customer context",
3213
- children: /* @__PURE__ */ jsx14(DetailCard, {
3214
- title: "Customer context",
3215
- dense: true,
3216
- actions: grid ? /* @__PURE__ */ jsx14(Button, {
3217
- icon: "Pencil",
3218
- onClick: () => setEditing("customers"),
3219
- children: "Edit"
3220
- }) : undefined,
3221
- fields: [
3222
- {
3223
- label: "Customer source",
3224
- value: grid ? `${grid.label || grid.id} · selected rows` : "No source grid connected"
3225
- },
3226
- {
3227
- label: "Save behavior",
3228
- value: config.customerRule === "each" ? "One note per selected customer" : "Require exactly one customer"
3229
- },
3230
- {
3231
- label: "Customer identifier",
3232
- value: customerParams.length ? customerParams.map((x) => `${x.param.property || "not chosen"} → ${x.param.param}`).join("; ") : "No Submit request property uses the selection"
3233
- },
3234
- {
3235
- label: "Current selection",
3236
- value: customerNames.length === 0 ? "No customers selected in the worklist" : customerNames.length === 1 ? customerNames[0] : /* @__PURE__ */ jsxs11("div", {
3237
- className: "binding-stack-tight",
3238
- children: [
3239
- /* @__PURE__ */ jsxs11("span", {
3240
- children: [
3241
- customerNames.length,
3242
- " customers selected"
3243
- ]
3244
- }),
3245
- /* @__PURE__ */ jsx14(Button, {
3246
- variant: "ghost",
3247
- size: "sm",
3248
- "aria-expanded": showCustomers,
3249
- onClick: () => setShowCustomers((v) => !v),
3250
- children: showCustomers ? "Hide Customers" : "Show Customers"
3251
- }),
3252
- showCustomers && /* @__PURE__ */ jsx14("ul", {
3253
- className: "binding-list",
3254
- children: customerNames.map((name, i) => /* @__PURE__ */ jsx14("li", {
3255
- children: name
3256
- }, `${name}-${i}`))
3257
- })
3258
- ]
3259
- })
3260
- }
3261
- ]
3262
- })
3263
- }),
3264
- /* @__PURE__ */ jsxs11("section", {
3265
- className: "binding-section",
3266
- "aria-labelledby": "fields-heading",
3267
- children: [
3268
- /* @__PURE__ */ jsx14("h2", {
3269
- id: "fields-heading",
3270
- children: "Field connections"
3271
- }),
3272
- /* @__PURE__ */ jsx14(AppGrid, {
3273
- rows,
3274
- getRowKey: (row) => row.id,
3275
- columns,
3276
- filtering: false,
3277
- height: "auto"
3278
- })
3279
- ]
3280
- }),
3281
- /* @__PURE__ */ jsxs11("section", {
3282
- className: "binding-section",
3283
- "aria-labelledby": "actions-heading",
3284
- children: [
3285
- /* @__PURE__ */ jsx14("h2", {
3286
- id: "actions-heading",
3287
- children: "Action behavior"
3288
- }),
3289
- /* @__PURE__ */ jsx14(DetailCard, {
3290
- title: "Submit",
3291
- dense: true,
3292
- actions: /* @__PURE__ */ jsx14(Button, {
3293
- icon: "Pencil",
3294
- onClick: () => setEditing("actions"),
3295
- children: "Edit"
3296
- }),
3297
- fields: [
3298
- { label: "Before saving", value: "Validate required fields" },
3299
- {
3300
- label: "Save operation",
3301
- value: submitCalls.length ? submitCalls.map((c) => `${endpointLabel(c)} (${config.customerRule === "each" ? "one note per selected customer" : "one customer"})`).join(" then ") : "Save connection missing"
3302
- },
3303
- { label: "On success", value: config.success === "close" ? "Close popup" : "Keep popup open" },
3304
- { label: "On failure", value: "Keep popup open, preserve entered values, allow retry" }
3305
- ]
3306
- }),
3307
- cancelCalls.length ? /* @__PURE__ */ jsx14(DetailCard, {
3308
- title: "Cancel",
3309
- subtitle: "Configured API mappings",
3310
- dense: true,
3311
- fields: [
3312
- {
3313
- label: "API calls",
3314
- value: cancelCalls.map((c) => `${c.purpose === "read" ? "Read" : "Write"} ${endpointLabel(c)}`).join(" then ")
3315
- },
3316
- { label: "Then", value: "Close popup" }
3317
- ]
3318
- }) : /* @__PURE__ */ jsx14(DetailCard, {
3319
- title: "Cancel",
3320
- subtitle: "Fixed behavior",
3321
- dense: true,
3322
- fields: [{ label: "Behavior", value: "Close without saving" }]
3323
- })
3324
- ]
3325
- }),
3326
- /* @__PURE__ */ jsxs11("section", {
3327
- className: "binding-section",
3328
- "aria-labelledby": "calls-heading",
3329
- children: [
3330
- /* @__PURE__ */ jsxs11("div", {
3331
- className: "binding-section-title",
3332
- children: [
3333
- /* @__PURE__ */ jsx14("h2", {
3334
- id: "calls-heading",
3335
- children: "API mappings"
3336
- }),
3337
- /* @__PURE__ */ jsx14(Button, {
3338
- icon: "Settings",
3339
- onClick: () => setEditing("mappings"),
3340
- children: "Manage API Mappings"
3341
- })
3342
- ]
3343
- }),
3344
- callRows.length ? /* @__PURE__ */ jsx14(AppGrid, {
3345
- rows: callRows,
3346
- getRowKey: (r) => r.id,
3347
- columns: callColumns,
3348
- filtering: false,
3349
- height: "auto"
3350
- }) : /* @__PURE__ */ jsx14("p", {
3351
- className: "binding-muted",
3352
- children: "No API mappings yet."
3353
- })
3354
- ]
3355
- }),
3356
- issues.length > 0 && /* @__PURE__ */ jsx14(Banner, {
3357
- tone: "caution",
3358
- children: /* @__PURE__ */ jsx14("div", {
3359
- children: issues.map((issue) => /* @__PURE__ */ jsx14("p", {
3360
- children: issue
3361
- }, issue))
3362
- })
3363
- }),
3364
- storageError && /* @__PURE__ */ jsx14(Banner, {
3365
- tone: "caution",
3366
- children: storageError
3367
- })
3368
- ]
3369
- }),
3370
- /* @__PURE__ */ jsxs11("aside", {
3371
- className: "binding-preview",
3372
- "aria-label": `Read-only ${context.snapshot.source || "Add Notes"} preview`,
3373
- children: [
3374
- /* @__PURE__ */ jsxs11("div", {
3375
- className: "binding-section-title",
3376
- children: [
3377
- /* @__PURE__ */ jsx14("h2", {
3378
- children: context.snapshot.source || "Add Notes"
3379
- }),
3380
- /* @__PURE__ */ jsx14(Badge, {
3381
- children: "Read-only preview"
3382
- })
3383
- ]
3384
- }),
3385
- /* @__PURE__ */ jsx14("p", {
3386
- className: "binding-muted",
3387
- children: config.customerRule === "each" ? "One note per selected customer" : "Exactly one customer"
3388
- }),
3389
- context.snapshot.source === "Add Notes" || config.fields.some((f) => f.name === "notesType") ? /* @__PURE__ */ jsxs11(Fragment6, {
3390
- children: [
3391
- /* @__PURE__ */ jsx14(Select, {
3392
- label: "Notes Type",
3393
- required: true,
3394
- disabled: true,
3395
- value: inputValue("notesType") || context.options.notesType?.[0]?.value || null,
3396
- options: context.options.notesType ?? []
3397
- }),
3398
- /* @__PURE__ */ jsx14(Select, {
3399
- label: "Template",
3400
- disabled: true,
3401
- value: config.templateFills ? previewTemplate?.value ?? null : null,
3402
- options: context.options.template ?? []
3403
- }),
3404
- /* @__PURE__ */ jsx14(Input, {
3405
- label: "Subject",
3406
- required: true,
3407
- readOnly: true,
3408
- value: previewValue("subject")
3409
- }),
3410
- /* @__PURE__ */ jsx14(TextArea, {
3411
- label: "Description",
3412
- required: true,
3413
- readOnly: true,
3414
- value: previewValue("notes")
3415
- }),
3416
- /* @__PURE__ */ jsx14("p", {
3417
- className: "binding-muted",
3418
- children: "Preview uses your entered values or sample template content."
3419
- }),
3420
- callLog && !callLog.excluded && /* @__PURE__ */ jsxs11(Fragment6, {
3421
- children: [
3422
- /* @__PURE__ */ jsx14(Checkbox, {
3423
- label: "Add Call Log",
3424
- disabled: true,
3425
- checked: false
3426
- }),
3427
- issues.some((i) => i.startsWith(callLog.label)) && /* @__PURE__ */ jsx14(Badge, {
3428
- variant: "warning",
3429
- children: "Connection unresolved"
3430
- })
3431
- ]
3432
- }),
3433
- /* @__PURE__ */ jsxs11(ButtonBar, {
3434
- justify: "between",
3435
- children: [
3436
- /* @__PURE__ */ jsx14(Button, {
3437
- disabled: true,
3438
- children: "Cancel"
3439
- }),
3440
- /* @__PURE__ */ jsx14(Button, {
3441
- variant: "primary",
3442
- disabled: true,
3443
- children: "Submit"
3444
- })
3445
- ]
3446
- })
3447
- ]
3448
- }) : /* @__PURE__ */ jsxs11(Fragment6, {
3449
- children: [
3450
- config.fields.filter((f) => !f.excluded).map((f) => {
3451
- const comp = context.snapshot.components.find((c) => c.name === f.name);
3452
- const fieldKind = comp?.kind ?? (comp?.type === "select" || comp?.type === "combobox" ? "dropdown" : comp?.type === "textarea" ? "text-area" : comp?.type === "checkbox" ? "checkbox" : "text");
3453
- const opts = context.options[f.name] ?? [];
3454
- if (fieldKind === "dropdown" || opts.length > 0) {
3455
- return /* @__PURE__ */ jsx14(Select, {
3456
- label: f.label,
3457
- required: f.required,
3458
- disabled: true,
3459
- value: inputValue(f.name) || opts[0]?.value || null,
3460
- options: opts
3461
- }, f.name);
3462
- }
3463
- if (fieldKind === "text-area") {
3464
- return /* @__PURE__ */ jsx14(TextArea, {
3465
- label: f.label,
3466
- required: f.required,
3467
- readOnly: true,
3468
- value: previewValue(f.name)
3469
- }, f.name);
3470
- }
3471
- if (fieldKind === "checkbox") {
3472
- return /* @__PURE__ */ jsxs11(React9.Fragment, {
3473
- children: [
3474
- /* @__PURE__ */ jsx14(Checkbox, {
3475
- label: f.label,
3476
- disabled: true,
3477
- checked: comp?.value === "true"
3478
- }),
3479
- issues.some((i) => i.startsWith(f.label)) && /* @__PURE__ */ jsx14(Badge, {
3480
- variant: "warning",
3481
- children: "Connection unresolved"
3482
- })
3483
- ]
3484
- }, f.name);
3485
- }
3486
- return /* @__PURE__ */ jsx14(Input, {
3487
- label: f.label,
3488
- required: f.required,
3489
- readOnly: true,
3490
- value: previewValue(f.name)
3491
- }, f.name);
3492
- }),
3493
- /* @__PURE__ */ jsx14("p", {
3494
- className: "binding-muted",
3495
- children: "Preview uses your entered values or sample data."
3496
- }),
3497
- /* @__PURE__ */ jsx14(ButtonBar, {
3498
- justify: "between",
3499
- children: (context.snapshot.buttons && context.snapshot.buttons.length > 0 ? context.snapshot.buttons : ["Cancel", "Submit"]).map((btnText, idx) => /* @__PURE__ */ jsx14(Button, {
3500
- variant: idx === (context.snapshot.buttons?.length ?? 2) - 1 ? "primary" : "secondary",
3501
- disabled: true,
3502
- children: btnText
3503
- }, btnText))
3504
- })
3505
- ]
3506
- }),
3507
- /* @__PURE__ */ jsxs11("p", {
3508
- className: "binding-muted",
3509
- children: [
3510
- "After save: ",
3511
- config.success === "close" ? "close popup" : "keep popup open",
3512
- "."
3513
- ]
3514
- })
3515
- ]
3516
- })
3517
- ]
3518
- }),
3519
- editing && /* @__PURE__ */ jsxs11("aside", {
3520
- className: `binding-panel${editing !== "customers" ? " binding-panel-wide" : ""}`,
3521
- "aria-label": "Edit connection",
3522
- children: [
3523
- /* @__PURE__ */ jsxs11("div", {
3524
- className: "binding-section-title",
3525
- children: [
3526
- /* @__PURE__ */ jsx14("h2", {
3527
- children: panelTitle
3528
- }),
3529
- /* @__PURE__ */ jsx14(Button, {
3530
- icon: "X",
3531
- tooltip: "Close Panel",
3532
- "aria-label": "Close Panel",
3533
- onClick: () => setEditing(null)
3534
- })
3535
- ]
3536
- }),
3537
- editing === "customers" && /* @__PURE__ */ jsxs11(Fragment6, {
3538
- children: [
3539
- /* @__PURE__ */ jsx14(Select, {
3540
- label: "Selection rule",
3541
- value: config.customerRule,
3542
- options: [
3543
- { value: "each", label: "One note per selected customer" },
3544
- { value: "single", label: "Require exactly one customer" }
3545
- ],
3546
- onChange: (value) => change((old) => ({ ...old, customerRule: value === "single" ? "single" : "each" }))
3547
- }),
3548
- customerParams.length > 0 && grid ? customerParams.map(({ call, param }) => /* @__PURE__ */ jsx14(Select, {
3549
- label: customerParams.length > 1 ? `Customer identifier · ${endpointLabel(call)} (call ${config.calls.indexOf(call) + 1})` : "Customer identifier",
3550
- value: param.property || null,
3551
- options: customerOptions,
3552
- onChange: (value) => change((old) => ({
3553
- ...old,
3554
- calls: old.calls.map((c) => c.id === call.id ? {
3555
- ...c,
3556
- params: c.params.map((p) => p.param === CUSTOMER_PARAM && p.source === grid.id ? { ...p, property: String(value ?? "") } : p)
3557
- } : c)
3558
- }))
3559
- }, call.id)) : /* @__PURE__ */ jsx14("p", {
3560
- className: "binding-muted",
3561
- children: "No Submit request property takes the selected customers. Map one to the grid in the Submit API mappings."
3562
- }),
3563
- /* @__PURE__ */ jsx14("p", {
3564
- children: config.customerRule === "single" ? "Submitting with no customers or multiple customers will be blocked." : "Submitting with multiple customers saves the same note separately for each customer."
3565
- })
3566
- ]
3567
- }),
3568
- editing === "actions" && /* @__PURE__ */ jsx14(Select, {
3569
- label: "After a successful save",
3570
- value: config.success,
3571
- options: [
3572
- { value: "close", label: "Close the popup" },
3573
- { value: "keep", label: "Keep the popup open" }
3574
- ],
3575
- onChange: (value) => change((old) => ({ ...old, success: value === "keep" ? "keep" : "close" }))
3576
- }),
3577
- selectedField && /* @__PURE__ */ jsxs11(Fragment6, {
3578
- children: [
3579
- selectedField.name === "template" && /* @__PURE__ */ jsx14(Checkbox, {
3580
- label: "Fill Subject and Description from the selected template",
3581
- checked: config.templateFills,
3582
- onChange: (value) => change((old) => ({ ...old, templateFills: value }))
3583
- }),
3584
- (selectedField.name === "subject" || selectedField.name === "notes") && /* @__PURE__ */ jsx14(Select, {
3585
- label: "Gets its value from",
3586
- value: selectedField.input,
3587
- options: [
3588
- { value: "template", label: "User input or template" },
3589
- { value: "user", label: "User input only" }
3590
- ],
3591
- onChange: (value) => updateField(selectedField.name, { input: value === "user" ? "user" : "template" })
3592
- }),
3593
- !selectedField.required && /* @__PURE__ */ jsx14(Select, {
3594
- label: "Include in Binding",
3595
- value: selectedField.excluded ? "exclude" : "include",
3596
- options: [
3597
- { value: "include", label: "Keep this field" },
3598
- { value: "exclude", label: "Exclude and hide it" }
3599
- ],
3600
- onChange: (value) => updateField(selectedField.name, { excluded: value === "exclude" })
3601
- }),
3602
- context.options[selectedField.name]?.length ? /* @__PURE__ */ jsxs11("p", {
3603
- className: "binding-muted",
3604
- children: [
3605
- "Sample choices:",
3606
- " ",
3607
- context.options[selectedField.name].map((option) => `${option.label} (${option.value})`).join(" · ")
3608
- ]
3609
- }) : null,
3610
- fieldIssues(selectedField.name, selectedField.label).filter((i) => i.startsWith(selectedField.label)).map((i) => /* @__PURE__ */ jsx14("p", {
3611
- className: "binding-error",
3612
- children: i
3613
- }, i))
3614
- ]
3615
- }),
3616
- editing !== "customers" && /* @__PURE__ */ jsxs11(Fragment6, {
3617
- children: [
3618
- /* @__PURE__ */ jsx14(ApiMappings, {
3619
- config,
3620
- context,
3621
- focus,
3622
- defaultTarget: editing === "actions" ? SUBMIT : focus,
3623
- onChange: change
3624
- }),
3625
- /* @__PURE__ */ jsx14(RequestPreview, {
3626
- title: "Request preview (current window values)",
3627
- requests: previewRequests(config, context, liveCtx, focus ? (c) => c.component === focus || c.params.some((p) => p.source === focus) : undefined)
3628
- })
3629
- ]
3630
- }),
3631
- /* @__PURE__ */ jsx14(ButtonBar, {
3632
- children: /* @__PURE__ */ jsx14(Button, {
3633
- icon: "Check",
3634
- onClick: () => setEditing(null),
3635
- children: "Done"
3636
- })
3637
- })
3638
- ]
3639
- })
3640
- ]
3641
- }),
3642
- /* @__PURE__ */ jsxs11("footer", {
3643
- className: "binding-footer",
3644
- children: [
3645
- /* @__PURE__ */ jsx14("p", {
3646
- className: "binding-muted",
3647
- role: "status",
3648
- children: applied ? "API binding applied in this browser only. Current Notes behavior is unchanged." : canApply(config, context, verified) ? "Simulated save and retrieval passed. Ready to apply." : gaps.length ? gaps[0] : verified ? "Resolve the remaining connections before applying." : "Try a save and retrieve the sample note before applying."
3649
- }),
3650
- /* @__PURE__ */ jsxs11(ButtonBar, {
3651
- children: [
3652
- /* @__PURE__ */ jsx14(Button, {
3653
- icon: "Play",
3654
- onClick: () => setSimulation(true),
3655
- children: "Try It"
3656
- }),
3657
- /* @__PURE__ */ jsx14(Button, {
3658
- variant: "primary",
3659
- icon: "Check",
3660
- disabled: !canApply(config, context, verified) || !!storageError,
3661
- onClick: () => {
3662
- try {
3663
- localStorage.setItem(appliedKey(pageKey), JSON.stringify({ version: CONFIG_VERSION, config, appliedAt: new Date().toISOString() }));
3664
- notifyApplied(pageKey, config);
3665
- setApplied(true);
3666
- } catch {
3667
- setStorageError("Could not apply: browser storage is unavailable.");
3668
- }
3669
- },
3670
- children: "Apply Binding"
3671
- })
3672
- ]
3673
- })
3674
- ]
3675
- }),
3676
- simulation && /* @__PURE__ */ jsx14(SimulationDialog, {
3677
- config,
3678
- context,
3679
- onClose: () => setSimulation(false),
3680
- onVerified: setVerified
3681
- }, fingerprint(config))
3682
- ]
3683
- });
3684
- }
3685
- var ApiBindingStudio = ApiBindingWorkspace;
3686
- export {
3687
- ApiBindingStudio,
3688
- ApiBindingWorkspace,
3689
- ApiMappings,
3690
- AppGrid,
3691
- BINDING_APPLIED,
3692
- BINDING_CHANNEL,
3693
- BINDING_CONTEXT,
3694
- BINDING_READY,
3695
- BINDING_SELECTION_UPDATE,
3696
- Badge,
3697
- Banner,
3698
- BindApiButton,
3699
- Button,
3700
- ButtonBar,
3701
- ButtonGroup,
3702
- CANCEL,
3703
- CONFIG_VERSION,
3704
- CUSTOMER_PARAM,
3705
- Checkbox,
3706
- DetailCard,
3707
- Dialog,
3708
- FREE_TEXT_OPTION,
3709
- FREE_TEXT_SOURCE,
3710
- exports_Icons as Icons,
3711
- Input,
3712
- RequestPreview,
3713
- SUBMIT,
3714
- Select,
3715
- SimulationDialog,
3716
- TextArea,
3717
- WorkspaceComponentsProvider,
3718
- actionContext,
3719
- actionFormDialogTemplateV1,
3720
- alertDialogTemplateV1,
3721
- apisOf,
3722
- appliedKey,
3723
- baseType,
3724
- buildCatalogFromRegistry,
3725
- buildRequest,
3726
- buildSnapshot,
3727
- callIssues,
3728
- callsFor,
3729
- canApply,
3730
- changeCallTarget,
3731
- changePurpose,
3732
- coerce,
3733
- coerceScalar,
3734
- componentId,
3735
- componentLabel,
3736
- contextComponents,
3737
- contextKey,
3738
- createActionColumn,
3739
- definitionFor,
3740
- discoverButtons,
3741
- discoverFormComponents,
3742
- draftKey,
3743
- droppedBy,
3744
- endpointLabel,
3745
- fieldId,
3746
- fieldSummary,
3747
- fingerprint,
3748
- friendlyLabel,
3749
- gridPaths,
3750
- gridPropertyPaths,
3751
- isButton,
3752
- isButtonId,
3753
- isNoteContract,
3754
- isSimulated,
3755
- issuesFor,
3756
- methodsFor,
3757
- migrateConfig,
3758
- moveCall,
3759
- newCall,
3760
- newCallId,
3761
- normalizeComponent,
3762
- notifyApplied,
3763
- notifySelectionUpdate,
3764
- openBindingStudio,
3765
- parseComponentId,
3766
- populated,
3767
- previewRequests,
3768
- proposeConfig,
3769
- readBindingContext,
3770
- readSnapshot,
3771
- reconcileCallParams,
3772
- reconcileConfig,
3773
- resolutionIssues,
3774
- resolveParam,
3775
- setParamSource,
3776
- simulateSave,
3777
- simulationErrors,
3778
- simulationForm,
3779
- simulatorGaps,
3780
- snapshotForm,
3781
- sourceComponents,
3782
- storageKey,
3783
- storeBindingContext,
3784
- storeSnapshot,
3785
- useAppliedBinding,
3786
- useWorkspaceComponents,
3787
- validBindingContext,
3788
- validContext,
3789
- windowIds
3790
- };
1
+ import {
2
+ catalogFromOpenApi2,
3
+ localStorageAdapter2,
4
+ memoryAdapter2,
5
+ toManifest2,
6
+ createEngine2,
7
+ validateRegistry2,
8
+ validateBindings2,
9
+ useBindingRuntime,
10
+ BindingProvider2
11
+ } from "./index-e9twc25y.js";
12
+ // src/framework/expr.ts
13
+ var PIPE = /^([A-Za-z_][\w-]*)\((.*)\)$/;
14
+ var cache = new Map;
15
+ function parseArg(raw) {
16
+ const t = raw.trim();
17
+ if (!t)
18
+ return;
19
+ try {
20
+ return JSON.parse(t);
21
+ } catch {
22
+ return t;
23
+ }
24
+ }
25
+ function segments(path) {
26
+ return path.replace(/\[(\*|\d+)\]/g, ".$1").split(".").filter(Boolean);
27
+ }
28
+ function parse(expr) {
29
+ const hit = cache.get(expr);
30
+ if (hit)
31
+ return hit;
32
+ const [main, ...rest] = expr.split("|");
33
+ const segs = segments(main.trim());
34
+ if (!segs.length)
35
+ throw new Error(`Empty expression: "${expr}"`);
36
+ const width = segs[0] === "$item" || segs[0].startsWith("$") && !["$context", "$steps"].includes(segs[0]) ? 1 : 2;
37
+ const pipes = rest.map((p) => {
38
+ const m = PIPE.exec(p.trim());
39
+ if (!m)
40
+ throw new Error(`Bad transform "${p.trim()}" in "${expr}"`);
41
+ return { name: m[1], args: m[2] ? m[2].split(",").map(parseArg) : [] };
42
+ });
43
+ const parsed = { head: segs.slice(0, width), path: segs.slice(width), pipes };
44
+ cache.set(expr, parsed);
45
+ return parsed;
46
+ }
47
+ function walk(value, path) {
48
+ let v = value;
49
+ for (let i = 0;i < path.length; i++) {
50
+ if (v == null)
51
+ return;
52
+ if (path[i] === "*") {
53
+ if (!Array.isArray(v))
54
+ return;
55
+ const rest = path.slice(i + 1);
56
+ return v.map((x) => walk(x, rest));
57
+ }
58
+ v = v[path[i]];
59
+ }
60
+ return v;
61
+ }
62
+ function evaluate(expr, scope) {
63
+ const { head, path, pipes } = parse(expr);
64
+ let root;
65
+ if (head[0] === "$item")
66
+ root = scope.item;
67
+ else if (head[0] === "$context")
68
+ root = scope.context?.[head[1]];
69
+ else if (head[0] === "$steps")
70
+ root = scope.steps?.[head[1]];
71
+ else if (head[0].startsWith("$"))
72
+ root = scope.providers?.[head[0].slice(1)];
73
+ else
74
+ root = scope.slot(head[0], head[1]);
75
+ let value = walk(root, path);
76
+ for (const p of pipes) {
77
+ const fn = scope.transforms?.[p.name];
78
+ if (!fn)
79
+ throw new Error(`Unknown transform "${p.name}" in "${expr}"`);
80
+ value = fn(value, ...p.args);
81
+ }
82
+ return value;
83
+ }
84
+ // src/framework/runner.ts
85
+ class BindingError extends Error {
86
+ detail;
87
+ constructor(message, detail) {
88
+ super(message);
89
+ this.detail = detail;
90
+ this.name = "BindingError";
91
+ }
92
+ }
93
+ function setPointer(target, keys, value) {
94
+ let node = target;
95
+ keys.slice(0, -1).forEach((k) => {
96
+ node = node[k] ??= {};
97
+ });
98
+ node[keys[keys.length - 1]] = value;
99
+ }
100
+ var decode = (k) => k.replace(/~1/g, "/").replace(/~0/g, "~");
101
+ function buildRequest(op, request, scope) {
102
+ const out = { method: op.method, path: op.path, query: {}, headers: {} };
103
+ let body;
104
+ for (const [pointer, expr] of Object.entries(request)) {
105
+ const value = evaluate(expr, scope);
106
+ if (value === undefined)
107
+ continue;
108
+ const [, where, ...rest] = pointer.split("/");
109
+ const keys = rest.map(decode);
110
+ if (where === "path")
111
+ out.path = out.path.replace(`{${keys[0]}}`, encodeURIComponent(String(value)));
112
+ else if (where === "query")
113
+ out.query[keys[0]] = String(value);
114
+ else if (where === "header")
115
+ out.headers[keys[0]] = String(value);
116
+ else if (where === "body") {
117
+ if (!keys.length)
118
+ out.body = value;
119
+ else
120
+ setPointer(body ??= {}, keys, value);
121
+ }
122
+ }
123
+ if (body)
124
+ out.body = { ...out.body, ...body };
125
+ const missing = op.parameters.filter((p) => p.required && !((p.pointer in request) && evaluate(request[p.pointer], scope) !== undefined));
126
+ if (missing.length)
127
+ throw new BindingError(`Missing required input: ${missing.map((p) => p.name).join(", ")}.`);
128
+ return out;
129
+ }
130
+ var requestKey = (r) => JSON.stringify([r.method, r.path, r.query, r.body ?? null]);
131
+ function select(response, selector) {
132
+ const path = selector.replace(/^\$\.?/, "").replace(/\[\*\]$/, "").split(".").filter(Boolean);
133
+ return path.reduce((v, k) => v == null ? undefined : v[k], response);
134
+ }
135
+ function mapItems(data, binding, scope) {
136
+ if (!binding.map || !Object.keys(binding.map).length)
137
+ return data;
138
+ const one = (item) => Object.fromEntries(Object.entries(binding.map).map(([k, e]) => [k, evaluate(e, { ...scope, item })]));
139
+ return Array.isArray(data) ? data.map(one) : data == null ? data : one(data);
140
+ }
141
+ async function runConsume(binding, op, scope, fetcher) {
142
+ const response = await fetcher(buildRequest(op, binding.request, scope), op);
143
+ return mapItems(select(response, binding.select), binding, scope);
144
+ }
145
+ async function runSteps(steps, operations, scope, fetcher) {
146
+ const outcomes = [];
147
+ const results = { ...scope.steps };
148
+ for (const step of steps) {
149
+ const op = operations.get(step.operation);
150
+ if (!op)
151
+ throw new BindingError(`Operation "${step.operation}" is not in the catalog.`);
152
+ const items = step.forEach ? evaluate(step.forEach, scope) : [scope.item];
153
+ const list = Array.isArray(items) ? items : [items];
154
+ const responses = [];
155
+ let failed = false;
156
+ for (const item of list) {
157
+ try {
158
+ responses.push(await fetcher(buildRequest(op, step.request, { ...scope, item, steps: results }), op));
159
+ } catch (error) {
160
+ failed = true;
161
+ outcomes.push({ step: step.id, ok: false, error });
162
+ if (step.onError === "stop")
163
+ return outcomes;
164
+ }
165
+ }
166
+ const response = step.forEach ? responses : responses[0];
167
+ results[step.id] = { response };
168
+ if (!failed)
169
+ outcomes.push({ step: step.id, ok: true, response });
170
+ }
171
+ return outcomes;
172
+ }
173
+ // src/runtime/BindingScreen.tsx
174
+ import * as React from "react";
175
+
176
+ // src/runtime/screenStore.ts
177
+ function createScreenStore() {
178
+ const values = new Map;
179
+ const listeners = new Set;
180
+ return {
181
+ get: (b, s) => values.get(`${b}.${s}`),
182
+ set(b, s, value) {
183
+ const k = `${b}.${s}`;
184
+ if (Object.is(values.get(k), value))
185
+ return;
186
+ values.set(k, value);
187
+ listeners.forEach((l) => l());
188
+ },
189
+ subscribe(l) {
190
+ listeners.add(l);
191
+ return () => {
192
+ listeners.delete(l);
193
+ };
194
+ }
195
+ };
196
+ }
197
+
198
+ // src/runtime/BindingScreen.tsx
199
+ import { jsx } from "react/jsx-runtime";
200
+ var ScreenContext = React.createContext(null);
201
+ var ShownContext = React.createContext(true);
202
+ function useScreen() {
203
+ const s = React.useContext(ScreenContext);
204
+ if (!s)
205
+ throw new Error("Use binding hooks inside <BindingScreen id=…>.");
206
+ return s;
207
+ }
208
+ function BindingActive({ value, children }) {
209
+ const parent = React.useContext(ShownContext);
210
+ return /* @__PURE__ */ jsx(ShownContext.Provider, {
211
+ value: parent && value,
212
+ children
213
+ });
214
+ }
215
+ var NO_CONTEXT = {};
216
+ function BindingScreen({ id, context = NO_CONTEXT, children }) {
217
+ const [store] = React.useState(createScreenStore);
218
+ const shown = React.useContext(ShownContext);
219
+ const { loadSaved, mountScreen } = useBindingRuntime();
220
+ React.useEffect(() => {
221
+ loadSaved(id);
222
+ }, [loadSaved, id]);
223
+ React.useEffect(() => mountScreen(id, shown), [mountScreen, id, shown]);
224
+ const scope = React.useMemo(() => ({ id, store, context }), [id, store, context]);
225
+ return /* @__PURE__ */ jsx(ScreenContext.Provider, {
226
+ value: scope,
227
+ children
228
+ });
229
+ }
230
+ // src/runtime/hooks.ts
231
+ import * as React2 from "react";
232
+ function scopeOf(runtime, screen, item) {
233
+ return { slot: screen.store.get, context: screen.context, providers: runtime.providers(), transforms: runtime.transforms, item };
234
+ }
235
+ function useProduce(bindId, slot, value) {
236
+ const { store } = useScreen();
237
+ React2.useEffect(() => {
238
+ store.set(bindId, slot, value);
239
+ }, [store, bindId, slot, value]);
240
+ }
241
+ var WAIT = "\x00wait:";
242
+ function useSlot(bindId, slot) {
243
+ const runtime = useBindingRuntime();
244
+ const screen = useScreen();
245
+ const binding = runtime.binding(screen.id, bindId, slot);
246
+ const [nonce, setNonce] = React2.useState(0);
247
+ const [state, setState] = React2.useState({ key: "", status: "loading" });
248
+ const snapshot = React2.useCallback(() => {
249
+ if (!binding)
250
+ return "";
251
+ try {
252
+ const scope = scopeOf(runtime, screen);
253
+ if (binding.type === "expression")
254
+ return JSON.stringify(evaluate(binding.value, scope) ?? null);
255
+ if (binding.type !== "operation")
256
+ return "";
257
+ const op = runtime.operations.get(binding.operation);
258
+ return op ? requestKey(buildRequest(op, binding.request, scope)) : WAIT + "operation missing";
259
+ } catch (e) {
260
+ return WAIT + (e instanceof Error ? e.message : String(e));
261
+ }
262
+ }, [binding, runtime, screen]);
263
+ const key = React2.useSyncExternalStore(screen.store.subscribe, snapshot, snapshot);
264
+ React2.useEffect(() => {
265
+ if (!binding || binding.type !== "operation" || !key || key.startsWith(WAIT))
266
+ return;
267
+ const op = runtime.operations.get(binding.operation);
268
+ const scope = scopeOf(runtime, screen);
269
+ const request = buildRequest(op, binding.request, scope);
270
+ const k = requestKey(request);
271
+ let live = true;
272
+ setState((s) => ({ key, status: "loading", data: s.data }));
273
+ let p = nonce ? undefined : runtime.inflight.get(k);
274
+ if (!p) {
275
+ p = runtime.fetcher(request, op).finally(() => runtime.inflight.delete(k));
276
+ runtime.inflight.set(k, p);
277
+ }
278
+ p.then((res) => {
279
+ if (live)
280
+ setState({ key, status: "ready", data: mapItems(select(res, binding.select), binding, scope) });
281
+ }, (error) => {
282
+ if (live)
283
+ setState({ key, status: "error", error });
284
+ });
285
+ return () => {
286
+ live = false;
287
+ };
288
+ }, [binding, key, nonce, runtime, screen]);
289
+ const reload = React2.useCallback(() => setNonce((n) => n + 1), []);
290
+ if (!binding || binding.type === "steps" || binding.type === "none")
291
+ return { status: "unbound", data: undefined, error: undefined, reload };
292
+ if (binding.type === "expression")
293
+ return { status: "ready", data: key ? JSON.parse(key) : undefined, error: undefined, reload };
294
+ if (key.startsWith(WAIT))
295
+ return { status: "waiting", data: undefined, error: new BindingError(key.slice(WAIT.length)), reload };
296
+ return { status: state.key === key ? state.status : "loading", data: state.data, error: state.error, reload };
297
+ }
298
+ function useCell(bindId, slot) {
299
+ const runtime = useBindingRuntime();
300
+ const screen = useScreen();
301
+ const binding = runtime.binding(screen.id, bindId, slot);
302
+ return React2.useCallback((item) => binding?.type === "expression" ? evaluate(binding.value, scopeOf(runtime, screen, item)) : undefined, [binding, runtime, screen]);
303
+ }
304
+ function useTrigger(bindId, slot) {
305
+ const runtime = useBindingRuntime();
306
+ const screen = useScreen();
307
+ const binding = runtime.binding(screen.id, bindId, slot);
308
+ const [running, setRunning] = React2.useState(false);
309
+ const run = React2.useCallback(async (item) => {
310
+ if (binding?.type !== "steps")
311
+ return [];
312
+ setRunning(true);
313
+ try {
314
+ return await runSteps(binding.steps, runtime.operations, scopeOf(runtime, screen, item), runtime.fetcher);
315
+ } finally {
316
+ setRunning(false);
317
+ }
318
+ }, [binding, runtime, screen]);
319
+ return { bound: binding?.type === "steps" || binding?.type === "none", running, run };
320
+ }
321
+ export {
322
+ BindingActive,
323
+ BindingError,
324
+ BindingProvider2 as BindingProvider,
325
+ BindingScreen,
326
+ buildRequest,
327
+ catalogFromOpenApi2 as catalogFromOpenApi,
328
+ createEngine2 as createEngine,
329
+ evaluate,
330
+ localStorageAdapter2 as localStorageAdapter,
331
+ memoryAdapter2 as memoryAdapter,
332
+ runConsume,
333
+ runSteps,
334
+ select,
335
+ toManifest2 as toManifest,
336
+ useCell,
337
+ useProduce,
338
+ useSlot,
339
+ useTrigger,
340
+ validateBindings2 as validateBindings,
341
+ validateRegistry2 as validateRegistry
342
+ };
343
+
344
+ //# debugId=0D89B09671A8D81B64756E2164756E21
345
+ //# sourceMappingURL=index.js.map