@timeax/digital-service-engine 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.cjs +2933 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +507 -0
- package/dist/core/index.d.ts +507 -0
- package/dist/core/index.js +2899 -0
- package/dist/core/index.js.map +1 -0
- package/dist/react/index.cjs +4015 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +1572 -0
- package/dist/react/index.d.ts +1572 -0
- package/dist/react/index.js +3984 -0
- package/dist/react/index.js.map +1 -0
- package/dist/schema/index.cjs +19 -0
- package/dist/schema/index.cjs.map +1 -0
- package/dist/schema/index.d.cts +671 -0
- package/dist/schema/index.d.ts +671 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/workspace/index.cjs +9755 -0
- package/dist/workspace/index.cjs.map +1 -0
- package/dist/workspace/index.d.cts +1995 -0
- package/dist/workspace/index.d.ts +1995 -0
- package/dist/workspace/index.js +9711 -0
- package/dist/workspace/index.js.map +1 -0
- package/package.json +97 -0
- package/schema/editor-snapshot.schema.json +1138 -0
- package/schema/policies.schema.json +148 -0
- package/schema/service-props.schema.json +772 -0
|
@@ -0,0 +1,4015 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react/index.ts
|
|
21
|
+
var react_exports = {};
|
|
22
|
+
__export(react_exports, {
|
|
23
|
+
CanvasAPI: () => CanvasAPI,
|
|
24
|
+
EventBus: () => EventBus,
|
|
25
|
+
FormProvider: () => FormProvider,
|
|
26
|
+
Provider: () => Provider,
|
|
27
|
+
Wrapper: () => Wrapper,
|
|
28
|
+
createInputRegistry: () => createInputRegistry,
|
|
29
|
+
resolveInputDescriptor: () => resolveInputDescriptor,
|
|
30
|
+
useFormApi: () => useFormApi,
|
|
31
|
+
useFormField: () => useFormField,
|
|
32
|
+
useFormSelections: () => useFormSelections,
|
|
33
|
+
useInputs: () => useInputs,
|
|
34
|
+
useOptionalFormApi: () => useOptionalFormApi
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(react_exports);
|
|
37
|
+
|
|
38
|
+
// src/react/canvas/events.ts
|
|
39
|
+
var EventBus = class {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
42
|
+
}
|
|
43
|
+
on(event, handler) {
|
|
44
|
+
var _a;
|
|
45
|
+
const set = (_a = this.listeners.get(event)) != null ? _a : /* @__PURE__ */ new Set();
|
|
46
|
+
set.add(handler);
|
|
47
|
+
this.listeners.set(event, set);
|
|
48
|
+
return () => {
|
|
49
|
+
set.delete(handler);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
once(event, handler) {
|
|
53
|
+
const off = this.on(event, (p) => {
|
|
54
|
+
off();
|
|
55
|
+
handler(p);
|
|
56
|
+
});
|
|
57
|
+
return off;
|
|
58
|
+
}
|
|
59
|
+
emit(event, payload) {
|
|
60
|
+
const set = this.listeners.get(event);
|
|
61
|
+
if (!set || set.size === 0) return;
|
|
62
|
+
for (const h of Array.from(set)) try {
|
|
63
|
+
h(payload);
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
clear() {
|
|
68
|
+
this.listeners.clear();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/utils/retry-queue.ts
|
|
73
|
+
var RetryQueue = class {
|
|
74
|
+
constructor(opts = {}) {
|
|
75
|
+
this.jobs = /* @__PURE__ */ new Map();
|
|
76
|
+
this.paused = false;
|
|
77
|
+
var _a, _b, _c, _d, _e, _f;
|
|
78
|
+
this.opts = {
|
|
79
|
+
enabled: (_a = opts.enabled) != null ? _a : true,
|
|
80
|
+
maxAttempts: (_b = opts.maxAttempts) != null ? _b : 5,
|
|
81
|
+
baseDelayMs: (_c = opts.baseDelayMs) != null ? _c : 800,
|
|
82
|
+
maxDelayMs: (_d = opts.maxDelayMs) != null ? _d : 2e4,
|
|
83
|
+
jitter: (_e = opts.jitter) != null ? _e : true,
|
|
84
|
+
immediateFirst: (_f = opts.immediateFirst) != null ? _f : false
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
pause() {
|
|
88
|
+
this.paused = true;
|
|
89
|
+
}
|
|
90
|
+
resume() {
|
|
91
|
+
this.paused = false;
|
|
92
|
+
this.flush();
|
|
93
|
+
}
|
|
94
|
+
/** Enqueue or no-op if a job with same id already exists */
|
|
95
|
+
enqueue(job) {
|
|
96
|
+
var _a;
|
|
97
|
+
if (!this.opts.enabled) return false;
|
|
98
|
+
if (this.jobs.has(job.id)) return false;
|
|
99
|
+
this.jobs.set(job.id, { job, attempt: 0 });
|
|
100
|
+
(_a = job.onStatus) == null ? void 0 : _a.call(job, "scheduled", { attempt: 0 });
|
|
101
|
+
this.kick(job.id);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
/** Force retry now (resets backoff); returns false if not found */
|
|
105
|
+
triggerNow(id) {
|
|
106
|
+
const rec = this.jobs.get(id);
|
|
107
|
+
if (!rec) return false;
|
|
108
|
+
if (rec.timer) clearTimeout(rec.timer);
|
|
109
|
+
rec.timer = void 0;
|
|
110
|
+
this.kick(id, true);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
cancel(id) {
|
|
114
|
+
var _a, _b;
|
|
115
|
+
const rec = this.jobs.get(id);
|
|
116
|
+
if (!rec) return false;
|
|
117
|
+
if (rec.timer) clearTimeout(rec.timer);
|
|
118
|
+
rec.cancelled = true;
|
|
119
|
+
(_b = (_a = rec.job).onStatus) == null ? void 0 : _b.call(_a, "cancelled", { attempt: rec.attempt });
|
|
120
|
+
this.jobs.delete(id);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
pendingIds() {
|
|
124
|
+
return Array.from(this.jobs.keys());
|
|
125
|
+
}
|
|
126
|
+
size() {
|
|
127
|
+
return this.jobs.size;
|
|
128
|
+
}
|
|
129
|
+
isQueued(id) {
|
|
130
|
+
return this.jobs.has(id);
|
|
131
|
+
}
|
|
132
|
+
drain() {
|
|
133
|
+
var _a, _b;
|
|
134
|
+
for (const [id, rec] of this.jobs.entries()) {
|
|
135
|
+
if (rec.timer) clearTimeout(rec.timer);
|
|
136
|
+
rec.cancelled = true;
|
|
137
|
+
(_b = (_a = rec.job).onStatus) == null ? void 0 : _b.call(_a, "cancelled", { attempt: rec.attempt });
|
|
138
|
+
this.jobs.delete(id);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
flush() {
|
|
142
|
+
for (const id of this.jobs.keys()) this.kick(id);
|
|
143
|
+
}
|
|
144
|
+
delayFor(attempt) {
|
|
145
|
+
const { baseDelayMs, maxDelayMs, jitter } = this.opts;
|
|
146
|
+
const exp = Math.min(maxDelayMs, baseDelayMs * Math.pow(2, Math.max(0, attempt - 1)));
|
|
147
|
+
if (!jitter) return exp;
|
|
148
|
+
const r = Math.random() * 0.4 + 0.8;
|
|
149
|
+
return Math.min(maxDelayMs, Math.floor(exp * r));
|
|
150
|
+
}
|
|
151
|
+
async kick(id, immediate = false) {
|
|
152
|
+
var _a, _b;
|
|
153
|
+
const rec = this.jobs.get(id);
|
|
154
|
+
if (!rec || rec.cancelled) return;
|
|
155
|
+
if (this.paused && !immediate) return;
|
|
156
|
+
const attempt = rec.attempt + 1;
|
|
157
|
+
const run = async () => {
|
|
158
|
+
var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
159
|
+
if (rec.cancelled) return;
|
|
160
|
+
(_b2 = (_a2 = rec.job).onStatus) == null ? void 0 : _b2.call(_a2, "retrying", { attempt });
|
|
161
|
+
try {
|
|
162
|
+
const ok = await rec.job.perform(attempt);
|
|
163
|
+
if (ok) {
|
|
164
|
+
(_d = (_c = rec.job).onStatus) == null ? void 0 : _d.call(_c, "succeeded", { attempt });
|
|
165
|
+
this.jobs.delete(id);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
(_f = (_e = rec.job).onStatus) == null ? void 0 : _f.call(_e, "failed", { attempt, error: err });
|
|
170
|
+
}
|
|
171
|
+
if (attempt >= this.opts.maxAttempts) {
|
|
172
|
+
(_h = (_g = rec.job).onStatus) == null ? void 0 : _h.call(_g, "failed", { attempt });
|
|
173
|
+
this.jobs.delete(id);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
rec.attempt = attempt;
|
|
177
|
+
const delay = this.delayFor(attempt);
|
|
178
|
+
(_j = (_i = rec.job).onStatus) == null ? void 0 : _j.call(_i, "scheduled", { attempt, nextDelayMs: delay });
|
|
179
|
+
rec.timer = setTimeout(() => this.kick(id), delay);
|
|
180
|
+
};
|
|
181
|
+
if (immediate) await run();
|
|
182
|
+
else {
|
|
183
|
+
const delay = this.opts.immediateFirst && attempt === 1 ? 0 : this.delayFor(attempt);
|
|
184
|
+
if (delay) {
|
|
185
|
+
(_b = (_a = rec.job).onStatus) == null ? void 0 : _b.call(_a, "scheduled", { attempt: 0, nextDelayMs: delay });
|
|
186
|
+
rec.timer = setTimeout(run, delay);
|
|
187
|
+
} else {
|
|
188
|
+
void run();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// src/react/canvas/comments.ts
|
|
195
|
+
var __seq = 0;
|
|
196
|
+
var newLocalId = (p = "loc") => `${p}_${Date.now().toString(36)}_${(++__seq).toString(36)}`;
|
|
197
|
+
var CommentsAPI = class {
|
|
198
|
+
constructor(bus, deps = {}) {
|
|
199
|
+
this.threads = /* @__PURE__ */ new Map();
|
|
200
|
+
this.bus = bus;
|
|
201
|
+
this.deps = deps;
|
|
202
|
+
this.retry = new RetryQueue(deps.retry);
|
|
203
|
+
}
|
|
204
|
+
scope() {
|
|
205
|
+
var _a, _b;
|
|
206
|
+
return (_b = (_a = this.deps).getScope) == null ? void 0 : _b.call(_a);
|
|
207
|
+
}
|
|
208
|
+
emitSync(op, threadId, messageId, status, meta) {
|
|
209
|
+
this.bus.emit("comment:sync", {
|
|
210
|
+
op,
|
|
211
|
+
threadId,
|
|
212
|
+
messageId,
|
|
213
|
+
status,
|
|
214
|
+
attempt: meta.attempt,
|
|
215
|
+
nextDelayMs: meta.nextDelayMs,
|
|
216
|
+
error: meta.error
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
/* ─── Persistence bridge ───────────────────────────── */
|
|
220
|
+
async loadAll() {
|
|
221
|
+
if (!this.deps.backend) return;
|
|
222
|
+
const scope = this.scope();
|
|
223
|
+
if (!scope) return;
|
|
224
|
+
const res = await this.deps.backend.listThreads(scope);
|
|
225
|
+
if (!res.ok) {
|
|
226
|
+
this.bus.emit("error", {
|
|
227
|
+
message: res.error.message,
|
|
228
|
+
code: res.error.code,
|
|
229
|
+
meta: res.error.meta
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
this.threads.clear();
|
|
234
|
+
for (const th of res.value) {
|
|
235
|
+
this.threads.set(th.id, { ...th, _sync: "synced" });
|
|
236
|
+
}
|
|
237
|
+
this.bus.emit("comment:thread:update", { thread: void 0 });
|
|
238
|
+
}
|
|
239
|
+
/* ─── Query ─────────────────────────────────────────── */
|
|
240
|
+
list() {
|
|
241
|
+
return Array.from(this.threads.values()).sort((a, b) => a.createdAt - b.createdAt).map((t) => t);
|
|
242
|
+
}
|
|
243
|
+
get(id) {
|
|
244
|
+
return this.threads.get(id);
|
|
245
|
+
}
|
|
246
|
+
/* ─── Mutations (optimistic if backend present) ─────── */
|
|
247
|
+
async create(anchor, initialBody, meta) {
|
|
248
|
+
var _a, _b;
|
|
249
|
+
const now = Date.now();
|
|
250
|
+
const localId = newLocalId("t");
|
|
251
|
+
const msgId = newLocalId("m");
|
|
252
|
+
const hasBackend = Boolean(this.deps.backend && this.scope());
|
|
253
|
+
const local = {
|
|
254
|
+
id: localId,
|
|
255
|
+
anchor,
|
|
256
|
+
resolved: false,
|
|
257
|
+
createdAt: now,
|
|
258
|
+
updatedAt: now,
|
|
259
|
+
messages: [{ id: msgId, body: initialBody, createdAt: now }],
|
|
260
|
+
meta,
|
|
261
|
+
_sync: hasBackend ? "pending" : "synced"
|
|
262
|
+
};
|
|
263
|
+
this.threads.set(localId, local);
|
|
264
|
+
this.bus.emit("comment:thread:create", { thread: local });
|
|
265
|
+
if (!this.deps.backend) return localId;
|
|
266
|
+
const performOnce = async () => {
|
|
267
|
+
const scope = this.scope();
|
|
268
|
+
if (!scope) return localId;
|
|
269
|
+
const res = await this.deps.backend.createThread(scope, {
|
|
270
|
+
anchor,
|
|
271
|
+
body: initialBody,
|
|
272
|
+
meta
|
|
273
|
+
});
|
|
274
|
+
if (!res.ok) throw res.error;
|
|
275
|
+
this.threads.delete(localId);
|
|
276
|
+
const serverTh = {
|
|
277
|
+
...res.value,
|
|
278
|
+
_sync: "synced"
|
|
279
|
+
};
|
|
280
|
+
this.threads.set(serverTh.id, serverTh);
|
|
281
|
+
this.bus.emit("comment:thread:update", { thread: serverTh });
|
|
282
|
+
return serverTh.id;
|
|
283
|
+
};
|
|
284
|
+
try {
|
|
285
|
+
const serverId = await performOnce();
|
|
286
|
+
return serverId;
|
|
287
|
+
} catch (err) {
|
|
288
|
+
const scope = this.scope();
|
|
289
|
+
const branchKey = (_a = scope == null ? void 0 : scope.branchId) != null ? _a : "no_branch";
|
|
290
|
+
const jobId = `comments:create_thread:${branchKey}:${localId}`;
|
|
291
|
+
this.retry.enqueue({
|
|
292
|
+
id: jobId,
|
|
293
|
+
perform: async () => {
|
|
294
|
+
try {
|
|
295
|
+
await performOnce();
|
|
296
|
+
return true;
|
|
297
|
+
} catch {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
onStatus: (status, meta2) => this.emitSync(
|
|
302
|
+
"create_thread",
|
|
303
|
+
localId,
|
|
304
|
+
void 0,
|
|
305
|
+
status,
|
|
306
|
+
meta2 != null ? meta2 : { attempt: 0 }
|
|
307
|
+
)
|
|
308
|
+
});
|
|
309
|
+
local._sync = "error";
|
|
310
|
+
this.bus.emit("error", {
|
|
311
|
+
message: (_b = err == null ? void 0 : err.message) != null ? _b : "Create failed",
|
|
312
|
+
code: err == null ? void 0 : err.code,
|
|
313
|
+
meta: err
|
|
314
|
+
});
|
|
315
|
+
this.bus.emit("comment:thread:update", { thread: local });
|
|
316
|
+
return localId;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async reply(threadId, body, meta) {
|
|
320
|
+
var _a, _b, _c;
|
|
321
|
+
const th = this.ensure(threadId);
|
|
322
|
+
const now = Date.now();
|
|
323
|
+
const localMid = newLocalId("m");
|
|
324
|
+
const hasBackend = Boolean(this.deps.backend && this.scope());
|
|
325
|
+
const localMsg = {
|
|
326
|
+
id: localMid,
|
|
327
|
+
body,
|
|
328
|
+
createdAt: now,
|
|
329
|
+
meta
|
|
330
|
+
};
|
|
331
|
+
th.messages.push(localMsg);
|
|
332
|
+
th.updatedAt = now;
|
|
333
|
+
(_a = th._sync) != null ? _a : th._sync = hasBackend ? "pending" : "synced";
|
|
334
|
+
this.bus.emit("comment:message:create", {
|
|
335
|
+
threadId,
|
|
336
|
+
message: localMsg
|
|
337
|
+
});
|
|
338
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
339
|
+
if (!this.deps.backend) return localMid;
|
|
340
|
+
const performOnce = async () => {
|
|
341
|
+
const scope = this.scope();
|
|
342
|
+
if (!scope) return localMid;
|
|
343
|
+
const res = await this.deps.backend.addMessage(scope, {
|
|
344
|
+
threadId: th.id,
|
|
345
|
+
body,
|
|
346
|
+
meta
|
|
347
|
+
});
|
|
348
|
+
if (!res.ok) throw res.error;
|
|
349
|
+
const serverMsg = res.value;
|
|
350
|
+
const idx = th.messages.findIndex((m) => m.id === localMid);
|
|
351
|
+
if (idx >= 0) th.messages[idx] = serverMsg;
|
|
352
|
+
th._sync = "synced";
|
|
353
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
354
|
+
return serverMsg.id;
|
|
355
|
+
};
|
|
356
|
+
try {
|
|
357
|
+
const serverMid = await performOnce();
|
|
358
|
+
return serverMid;
|
|
359
|
+
} catch (err) {
|
|
360
|
+
const scope = this.scope();
|
|
361
|
+
const branchKey = (_b = scope == null ? void 0 : scope.branchId) != null ? _b : "no_branch";
|
|
362
|
+
const jobId = `comments:add_message:${branchKey}:${threadId}:${localMid}`;
|
|
363
|
+
this.retry.enqueue({
|
|
364
|
+
id: jobId,
|
|
365
|
+
perform: async () => {
|
|
366
|
+
try {
|
|
367
|
+
await performOnce();
|
|
368
|
+
return true;
|
|
369
|
+
} catch {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
onStatus: (status, meta2) => this.emitSync(
|
|
374
|
+
"add_message",
|
|
375
|
+
threadId,
|
|
376
|
+
localMid,
|
|
377
|
+
status,
|
|
378
|
+
meta2 != null ? meta2 : { attempt: 0 }
|
|
379
|
+
)
|
|
380
|
+
});
|
|
381
|
+
th._sync = "error";
|
|
382
|
+
this.bus.emit("error", {
|
|
383
|
+
message: (_c = err == null ? void 0 : err.message) != null ? _c : "Reply failed",
|
|
384
|
+
code: err == null ? void 0 : err.code,
|
|
385
|
+
meta: err
|
|
386
|
+
});
|
|
387
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
388
|
+
return localMid;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async editMessage(threadId, messageId, body) {
|
|
392
|
+
var _a, _b, _c;
|
|
393
|
+
const th = this.ensure(threadId);
|
|
394
|
+
const orig = th.messages.find((m) => m.id === messageId);
|
|
395
|
+
if (!orig) return;
|
|
396
|
+
const previous = { ...orig };
|
|
397
|
+
orig.body = body;
|
|
398
|
+
orig.editedAt = Date.now();
|
|
399
|
+
th.updatedAt = orig.editedAt;
|
|
400
|
+
const hasBackend = Boolean(this.deps.backend && this.scope());
|
|
401
|
+
(_a = th._sync) != null ? _a : th._sync = hasBackend ? "pending" : "synced";
|
|
402
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
403
|
+
if (!this.deps.backend) return;
|
|
404
|
+
const performOnce = async () => {
|
|
405
|
+
const scope = this.scope();
|
|
406
|
+
if (!scope) return;
|
|
407
|
+
const res = await this.deps.backend.editMessage(scope, {
|
|
408
|
+
threadId: th.id,
|
|
409
|
+
messageId,
|
|
410
|
+
body
|
|
411
|
+
});
|
|
412
|
+
if (!res.ok) throw res.error;
|
|
413
|
+
const idx = th.messages.findIndex((m) => m.id === messageId);
|
|
414
|
+
if (idx >= 0) th.messages[idx] = res.value;
|
|
415
|
+
th._sync = "synced";
|
|
416
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
417
|
+
};
|
|
418
|
+
try {
|
|
419
|
+
await performOnce();
|
|
420
|
+
} catch (err) {
|
|
421
|
+
const scope = this.scope();
|
|
422
|
+
const branchKey = (_b = scope == null ? void 0 : scope.branchId) != null ? _b : "no_branch";
|
|
423
|
+
const jobId = `comments:edit_message:${branchKey}:${threadId}:${messageId}`;
|
|
424
|
+
this.retry.enqueue({
|
|
425
|
+
id: jobId,
|
|
426
|
+
perform: async () => {
|
|
427
|
+
try {
|
|
428
|
+
await performOnce();
|
|
429
|
+
return true;
|
|
430
|
+
} catch {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
onStatus: (status, meta2) => this.emitSync(
|
|
435
|
+
"edit_message",
|
|
436
|
+
threadId,
|
|
437
|
+
messageId,
|
|
438
|
+
status,
|
|
439
|
+
meta2 != null ? meta2 : { attempt: 0 }
|
|
440
|
+
)
|
|
441
|
+
});
|
|
442
|
+
const idx = th.messages.findIndex((m) => m.id === messageId);
|
|
443
|
+
if (idx >= 0) th.messages[idx] = previous;
|
|
444
|
+
th._sync = "error";
|
|
445
|
+
this.bus.emit("error", {
|
|
446
|
+
message: (_c = err == null ? void 0 : err.message) != null ? _c : "Edit failed",
|
|
447
|
+
code: err == null ? void 0 : err.code,
|
|
448
|
+
meta: err
|
|
449
|
+
});
|
|
450
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async deleteMessage(threadId, messageId) {
|
|
454
|
+
var _a, _b, _c;
|
|
455
|
+
const th = this.ensure(threadId);
|
|
456
|
+
const backup = [...th.messages];
|
|
457
|
+
th.messages = th.messages.filter((m) => m.id !== messageId);
|
|
458
|
+
th.updatedAt = Date.now();
|
|
459
|
+
const hasBackend = Boolean(this.deps.backend && this.scope());
|
|
460
|
+
(_a = th._sync) != null ? _a : th._sync = hasBackend ? "pending" : "synced";
|
|
461
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
462
|
+
if (!this.deps.backend) return;
|
|
463
|
+
const performOnce = async () => {
|
|
464
|
+
const scope = this.scope();
|
|
465
|
+
if (!scope) return;
|
|
466
|
+
const res = await this.deps.backend.deleteMessage(scope, {
|
|
467
|
+
threadId: th.id,
|
|
468
|
+
messageId
|
|
469
|
+
});
|
|
470
|
+
if (!res.ok) throw res.error;
|
|
471
|
+
th._sync = "synced";
|
|
472
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
473
|
+
};
|
|
474
|
+
try {
|
|
475
|
+
await performOnce();
|
|
476
|
+
} catch (err) {
|
|
477
|
+
const scope = this.scope();
|
|
478
|
+
const branchKey = (_b = scope == null ? void 0 : scope.branchId) != null ? _b : "no_branch";
|
|
479
|
+
const jobId = `comments:delete_message:${branchKey}:${threadId}:${messageId}`;
|
|
480
|
+
this.retry.enqueue({
|
|
481
|
+
id: jobId,
|
|
482
|
+
perform: async () => {
|
|
483
|
+
try {
|
|
484
|
+
await performOnce();
|
|
485
|
+
return true;
|
|
486
|
+
} catch {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
onStatus: (status, meta2) => this.emitSync(
|
|
491
|
+
"delete_message",
|
|
492
|
+
threadId,
|
|
493
|
+
messageId,
|
|
494
|
+
status,
|
|
495
|
+
meta2 != null ? meta2 : { attempt: 0 }
|
|
496
|
+
)
|
|
497
|
+
});
|
|
498
|
+
th.messages = backup;
|
|
499
|
+
th._sync = "error";
|
|
500
|
+
this.bus.emit("error", {
|
|
501
|
+
message: (_c = err == null ? void 0 : err.message) != null ? _c : "Delete failed",
|
|
502
|
+
code: err == null ? void 0 : err.code,
|
|
503
|
+
meta: err
|
|
504
|
+
});
|
|
505
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async move(threadId, anchor) {
|
|
509
|
+
var _a, _b, _c;
|
|
510
|
+
const th = this.ensure(threadId);
|
|
511
|
+
const prev = th.anchor;
|
|
512
|
+
th.anchor = anchor;
|
|
513
|
+
th.updatedAt = Date.now();
|
|
514
|
+
const hasBackend = Boolean(this.deps.backend && this.scope());
|
|
515
|
+
(_a = th._sync) != null ? _a : th._sync = hasBackend ? "pending" : "synced";
|
|
516
|
+
this.bus.emit("comment:move", { thread: th });
|
|
517
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
518
|
+
if (!this.deps.backend) return;
|
|
519
|
+
const performOnce = async () => {
|
|
520
|
+
const scope = this.scope();
|
|
521
|
+
if (!scope) return;
|
|
522
|
+
const res = await this.deps.backend.moveThread(scope, {
|
|
523
|
+
threadId: th.id,
|
|
524
|
+
anchor
|
|
525
|
+
});
|
|
526
|
+
if (!res.ok) throw res.error;
|
|
527
|
+
this.threads.set(th.id, { ...res.value, _sync: "synced" });
|
|
528
|
+
this.bus.emit("comment:thread:update", {
|
|
529
|
+
thread: this.threads.get(threadId)
|
|
530
|
+
});
|
|
531
|
+
};
|
|
532
|
+
try {
|
|
533
|
+
await performOnce();
|
|
534
|
+
} catch (err) {
|
|
535
|
+
const scope = this.scope();
|
|
536
|
+
const branchKey = (_b = scope == null ? void 0 : scope.branchId) != null ? _b : "no_branch";
|
|
537
|
+
const jobId = `comments:move_thread:${branchKey}:${threadId}`;
|
|
538
|
+
this.retry.enqueue({
|
|
539
|
+
id: jobId,
|
|
540
|
+
perform: async () => {
|
|
541
|
+
try {
|
|
542
|
+
await performOnce();
|
|
543
|
+
return true;
|
|
544
|
+
} catch {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
onStatus: (status, meta2) => this.emitSync(
|
|
549
|
+
"move_thread",
|
|
550
|
+
threadId,
|
|
551
|
+
void 0,
|
|
552
|
+
status,
|
|
553
|
+
meta2 != null ? meta2 : { attempt: 0 }
|
|
554
|
+
)
|
|
555
|
+
});
|
|
556
|
+
th.anchor = prev;
|
|
557
|
+
th._sync = "error";
|
|
558
|
+
this.bus.emit("error", {
|
|
559
|
+
message: (_c = err == null ? void 0 : err.message) != null ? _c : "Move failed",
|
|
560
|
+
code: err == null ? void 0 : err.code,
|
|
561
|
+
meta: err
|
|
562
|
+
});
|
|
563
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async resolve(threadId, value = true) {
|
|
567
|
+
var _a, _b, _c;
|
|
568
|
+
const th = this.ensure(threadId);
|
|
569
|
+
const prev = th.resolved;
|
|
570
|
+
th.resolved = value;
|
|
571
|
+
th.updatedAt = Date.now();
|
|
572
|
+
const hasBackend = Boolean(this.deps.backend && this.scope());
|
|
573
|
+
(_a = th._sync) != null ? _a : th._sync = hasBackend ? "pending" : "synced";
|
|
574
|
+
this.bus.emit("comment:resolve", { thread: th, resolved: value });
|
|
575
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
576
|
+
if (!this.deps.backend) return;
|
|
577
|
+
const performOnce = async () => {
|
|
578
|
+
const scope = this.scope();
|
|
579
|
+
if (!scope) return;
|
|
580
|
+
const res = await this.deps.backend.resolveThread(scope, {
|
|
581
|
+
threadId: th.id,
|
|
582
|
+
resolved: value
|
|
583
|
+
});
|
|
584
|
+
if (!res.ok) throw res.error;
|
|
585
|
+
this.threads.set(th.id, { ...res.value, _sync: "synced" });
|
|
586
|
+
this.bus.emit("comment:thread:update", {
|
|
587
|
+
thread: this.threads.get(threadId)
|
|
588
|
+
});
|
|
589
|
+
};
|
|
590
|
+
try {
|
|
591
|
+
await performOnce();
|
|
592
|
+
} catch (err) {
|
|
593
|
+
const scope = this.scope();
|
|
594
|
+
const branchKey = (_b = scope == null ? void 0 : scope.branchId) != null ? _b : "no_branch";
|
|
595
|
+
const jobId = `comments:resolve_thread:${branchKey}:${threadId}`;
|
|
596
|
+
this.retry.enqueue({
|
|
597
|
+
id: jobId,
|
|
598
|
+
perform: async () => {
|
|
599
|
+
try {
|
|
600
|
+
await performOnce();
|
|
601
|
+
return true;
|
|
602
|
+
} catch {
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
onStatus: (status, meta2) => this.emitSync(
|
|
607
|
+
"resolve_thread",
|
|
608
|
+
threadId,
|
|
609
|
+
void 0,
|
|
610
|
+
status,
|
|
611
|
+
meta2 != null ? meta2 : { attempt: 0 }
|
|
612
|
+
)
|
|
613
|
+
});
|
|
614
|
+
th.resolved = prev;
|
|
615
|
+
th._sync = "error";
|
|
616
|
+
this.bus.emit("error", {
|
|
617
|
+
message: (_c = err == null ? void 0 : err.message) != null ? _c : "Resolve failed",
|
|
618
|
+
code: err == null ? void 0 : err.code,
|
|
619
|
+
meta: err
|
|
620
|
+
});
|
|
621
|
+
this.bus.emit("comment:thread:update", { thread: th });
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
async deleteThread(threadId) {
|
|
625
|
+
var _a, _b;
|
|
626
|
+
const prev = this.threads.get(threadId);
|
|
627
|
+
if (!prev) return;
|
|
628
|
+
this.threads.delete(threadId);
|
|
629
|
+
this.bus.emit("comment:thread:delete", { threadId });
|
|
630
|
+
if (!this.deps.backend) return;
|
|
631
|
+
const performOnce = async () => {
|
|
632
|
+
const scope = this.scope();
|
|
633
|
+
if (!scope) return;
|
|
634
|
+
const res = await this.deps.backend.deleteThread(scope, { threadId });
|
|
635
|
+
if (!res.ok) throw res.error;
|
|
636
|
+
};
|
|
637
|
+
try {
|
|
638
|
+
await performOnce();
|
|
639
|
+
} catch (err) {
|
|
640
|
+
const scope = this.scope();
|
|
641
|
+
const branchKey = (_a = scope == null ? void 0 : scope.branchId) != null ? _a : "no_branch";
|
|
642
|
+
const jobId = `comments:delete_thread:${branchKey}:${threadId}`;
|
|
643
|
+
this.retry.enqueue({
|
|
644
|
+
id: jobId,
|
|
645
|
+
perform: async () => {
|
|
646
|
+
try {
|
|
647
|
+
await performOnce();
|
|
648
|
+
return true;
|
|
649
|
+
} catch {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
onStatus: (status, meta2) => this.emitSync(
|
|
654
|
+
"delete_thread",
|
|
655
|
+
threadId,
|
|
656
|
+
void 0,
|
|
657
|
+
status,
|
|
658
|
+
meta2 != null ? meta2 : { attempt: 0 }
|
|
659
|
+
)
|
|
660
|
+
});
|
|
661
|
+
this.threads.set(threadId, prev);
|
|
662
|
+
this.bus.emit("error", {
|
|
663
|
+
message: (_b = err == null ? void 0 : err.message) != null ? _b : "Delete thread failed",
|
|
664
|
+
code: err == null ? void 0 : err.code,
|
|
665
|
+
meta: err
|
|
666
|
+
});
|
|
667
|
+
this.bus.emit("comment:thread:update", { thread: prev });
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
// Optional helpers for UI controls
|
|
671
|
+
retryJob(jobId) {
|
|
672
|
+
return this.retry.triggerNow(jobId);
|
|
673
|
+
}
|
|
674
|
+
cancelJob(jobId) {
|
|
675
|
+
return this.retry.cancel(jobId);
|
|
676
|
+
}
|
|
677
|
+
pendingJobs() {
|
|
678
|
+
return this.retry.pendingIds();
|
|
679
|
+
}
|
|
680
|
+
/* ─── internal ────────────────────────────────────────── */
|
|
681
|
+
ensure(threadId) {
|
|
682
|
+
const th = this.threads.get(threadId);
|
|
683
|
+
if (!th) throw new Error(`Comment thread not found: ${threadId}`);
|
|
684
|
+
return th;
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// src/react/canvas/editor.ts
|
|
689
|
+
var import_lodash_es2 = require("lodash-es");
|
|
690
|
+
|
|
691
|
+
// src/core/normalise.ts
|
|
692
|
+
var import_lodash_es = require("lodash-es");
|
|
693
|
+
function normalise(input, opts = {}) {
|
|
694
|
+
var _a, _b;
|
|
695
|
+
const defRole = (_a = opts.defaultPricingRole) != null ? _a : "base";
|
|
696
|
+
const constraints = (_b = opts.constraints) != null ? _b : ["refill", "cancel", "dripfeed"];
|
|
697
|
+
const obj = toObject(input);
|
|
698
|
+
const rawFilters = Array.isArray(obj.filters) ? obj.filters : [];
|
|
699
|
+
const rawFields = Array.isArray(obj.fields) ? obj.fields : [];
|
|
700
|
+
const includes_for_buttons = toStringArrayMap(
|
|
701
|
+
obj.includes_for_buttons
|
|
702
|
+
);
|
|
703
|
+
const excludes_for_buttons = toStringArrayMap(
|
|
704
|
+
obj.excludes_for_buttons
|
|
705
|
+
);
|
|
706
|
+
let filters = rawFilters.map((t) => coerceTag(t, constraints));
|
|
707
|
+
const fields = rawFields.map((f) => coerceField(f, defRole));
|
|
708
|
+
if (!filters.some((t) => t.id === "t:root")) {
|
|
709
|
+
filters = [{ id: "t:root", label: "Root" }, ...filters];
|
|
710
|
+
}
|
|
711
|
+
const fallbacks = coerceFallbacks(obj.fallbacks);
|
|
712
|
+
const out = {
|
|
713
|
+
filters,
|
|
714
|
+
fields,
|
|
715
|
+
order_for_tags: obj.order_for_tags,
|
|
716
|
+
...isNonEmpty(includes_for_buttons) && { includes_for_buttons },
|
|
717
|
+
...isNonEmpty(excludes_for_buttons) && { excludes_for_buttons },
|
|
718
|
+
...fallbacks && (isNonEmpty(fallbacks.nodes) || isNonEmpty(fallbacks.global)) && {
|
|
719
|
+
fallbacks
|
|
720
|
+
},
|
|
721
|
+
schema_version: typeof obj.schema_version === "string" ? obj.schema_version : "1.0"
|
|
722
|
+
};
|
|
723
|
+
propagateConstraints(out, constraints);
|
|
724
|
+
return out;
|
|
725
|
+
}
|
|
726
|
+
function propagateConstraints(props, flagKeys) {
|
|
727
|
+
const tags = Array.isArray(props.filters) ? props.filters : [];
|
|
728
|
+
if (!tags.length) return;
|
|
729
|
+
const byId = new Map(tags.map((t) => [t.id, t]));
|
|
730
|
+
const children = /* @__PURE__ */ new Map();
|
|
731
|
+
for (const t of tags) {
|
|
732
|
+
const pid = t.bind_id;
|
|
733
|
+
if (!pid || !byId.has(pid)) continue;
|
|
734
|
+
if (!children.has(pid)) children.set(pid, []);
|
|
735
|
+
children.get(pid).push(t);
|
|
736
|
+
}
|
|
737
|
+
const roots = tags.filter((t) => !t.bind_id || !byId.has(t.bind_id));
|
|
738
|
+
const starts = roots.length ? roots : tags;
|
|
739
|
+
const visited = /* @__PURE__ */ new Set();
|
|
740
|
+
const visit = (tag, inherited) => {
|
|
741
|
+
var _a, _b;
|
|
742
|
+
if (visited.has(tag.id)) return;
|
|
743
|
+
visited.add(tag.id);
|
|
744
|
+
const local = (0, import_lodash_es.cloneDeep)((_a = tag.constraints) != null ? _a : {});
|
|
745
|
+
if (tag.constraints_overrides) {
|
|
746
|
+
for (const [k, over] of Object.entries(tag.constraints_overrides)) {
|
|
747
|
+
if (over) local[k] = over.from;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
const next = {};
|
|
751
|
+
const origin = {};
|
|
752
|
+
const overrides = {};
|
|
753
|
+
for (const k of flagKeys) {
|
|
754
|
+
const inh = inherited[k];
|
|
755
|
+
const prev = local[k];
|
|
756
|
+
if (inh) {
|
|
757
|
+
if (prev === void 0 || prev === inh.val) {
|
|
758
|
+
next[k] = inh.val;
|
|
759
|
+
origin[k] = inh.origin;
|
|
760
|
+
} else {
|
|
761
|
+
next[k] = inh.val;
|
|
762
|
+
origin[k] = inh.origin;
|
|
763
|
+
overrides[k] = {
|
|
764
|
+
from: prev,
|
|
765
|
+
to: inh.val,
|
|
766
|
+
origin: inh.origin
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
} else if (prev !== void 0) {
|
|
770
|
+
next[k] = prev;
|
|
771
|
+
origin[k] = tag.id;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
tag.constraints = Object.keys(next).length ? next : void 0;
|
|
775
|
+
tag.constraints_origin = Object.keys(origin).length ? origin : void 0;
|
|
776
|
+
tag.constraints_overrides = Object.keys(overrides).length ? overrides : void 0;
|
|
777
|
+
const passDown = { ...inherited };
|
|
778
|
+
for (const k of flagKeys) {
|
|
779
|
+
if (next[k] !== void 0 && origin[k] !== void 0) {
|
|
780
|
+
passDown[k] = { val: next[k], origin: origin[k] };
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
for (const c of (_b = children.get(tag.id)) != null ? _b : []) visit(c, passDown);
|
|
784
|
+
};
|
|
785
|
+
for (const r of starts) visit(r, {});
|
|
786
|
+
}
|
|
787
|
+
function coerceTag(src, flagKeys) {
|
|
788
|
+
if (!src || typeof src !== "object") src = {};
|
|
789
|
+
const id = str(src.id);
|
|
790
|
+
const label = str(src.label);
|
|
791
|
+
const bind_id = str(src.bind_id) || (id == "t:root" ? void 0 : "t:root");
|
|
792
|
+
const service_id = toNumberOrUndefined(src.service_id);
|
|
793
|
+
const includes = toStringArray(src.includes);
|
|
794
|
+
const excludes = toStringArray(src.excludes);
|
|
795
|
+
let constraints = void 0;
|
|
796
|
+
if (src.constraints && typeof src.constraints === "object") {
|
|
797
|
+
constraints = {};
|
|
798
|
+
for (const k of flagKeys) {
|
|
799
|
+
const v = src.constraints[k];
|
|
800
|
+
if (v !== void 0) {
|
|
801
|
+
constraints[k] = bool(v);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (Object.keys(constraints).length === 0) {
|
|
805
|
+
constraints = void 0;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const constraints_overrides = src.constraints_overrides && typeof src.constraints_overrides === "object" ? src.constraints_overrides : void 0;
|
|
809
|
+
const meta = src.meta && typeof src.meta === "object" ? src.meta : void 0;
|
|
810
|
+
const tag = {
|
|
811
|
+
id: "",
|
|
812
|
+
label: "",
|
|
813
|
+
...id && { id },
|
|
814
|
+
...label && { label },
|
|
815
|
+
...bind_id && { bind_id },
|
|
816
|
+
...service_id !== void 0 && { service_id },
|
|
817
|
+
...constraints && { constraints },
|
|
818
|
+
...constraints_overrides && { constraints_overrides },
|
|
819
|
+
...includes.length && { includes: dedupe(includes) },
|
|
820
|
+
...excludes.length && { excludes: dedupe(excludes) },
|
|
821
|
+
...meta && { meta }
|
|
822
|
+
};
|
|
823
|
+
return tag;
|
|
824
|
+
}
|
|
825
|
+
function coerceField(src, defRole) {
|
|
826
|
+
if (!src || typeof src !== "object") src = {};
|
|
827
|
+
const bind_id = normaliseBindId(src.bind_id);
|
|
828
|
+
const type = str(src.type) || "text";
|
|
829
|
+
const id = str(src.id);
|
|
830
|
+
const name = typeof src.name === "string" ? src.name : void 0;
|
|
831
|
+
const label = str(src.label) || "";
|
|
832
|
+
const required = !!src.required;
|
|
833
|
+
const ui = src.ui && typeof src.ui === "object" ? src.ui : void 0;
|
|
834
|
+
const defaults = src.defaults && typeof src.defaults === "object" ? src.defaults : void 0;
|
|
835
|
+
const pricing_role = src.pricing_role === "utility" || src.pricing_role === "base" ? src.pricing_role : defRole;
|
|
836
|
+
const srcHasOptions = Array.isArray(src.options) && src.options.length > 0;
|
|
837
|
+
const options = srcHasOptions ? src.options.map((o) => coerceOption(o, pricing_role)) : void 0;
|
|
838
|
+
const component = type === "custom" ? str(src.component) || void 0 : void 0;
|
|
839
|
+
const meta = src.meta && typeof src.meta === "object" ? { ...src.meta } : void 0;
|
|
840
|
+
const button = srcHasOptions ? true : src.button === true;
|
|
841
|
+
const field_service_id_raw = toNumberOrUndefined(src.service_id);
|
|
842
|
+
const field_service_id = button && pricing_role !== "utility" && field_service_id_raw !== void 0 ? field_service_id_raw : void 0;
|
|
843
|
+
const field = {
|
|
844
|
+
id,
|
|
845
|
+
type,
|
|
846
|
+
...bind_id !== void 0 && { bind_id },
|
|
847
|
+
...name && { name },
|
|
848
|
+
...options && options.length && { options },
|
|
849
|
+
...component && { component },
|
|
850
|
+
pricing_role,
|
|
851
|
+
label,
|
|
852
|
+
required,
|
|
853
|
+
...ui && { ui },
|
|
854
|
+
...defaults && { defaults },
|
|
855
|
+
...meta && { meta },
|
|
856
|
+
...button ? { button } : {},
|
|
857
|
+
...field_service_id !== void 0 && { service_id: field_service_id }
|
|
858
|
+
};
|
|
859
|
+
return field;
|
|
860
|
+
}
|
|
861
|
+
function coerceOption(src, inheritRole) {
|
|
862
|
+
if (!src || typeof src !== "object") src = {};
|
|
863
|
+
const id = str(src.id);
|
|
864
|
+
const label = str(src.label);
|
|
865
|
+
const service_id = toNumberOrUndefined(src.service_id);
|
|
866
|
+
const value = typeof src.value === "string" || typeof src.value === "number" ? src.value : void 0;
|
|
867
|
+
const pricing_role = src.pricing_role === "utility" || src.pricing_role === "base" ? src.pricing_role : inheritRole;
|
|
868
|
+
const meta = src.meta && typeof src.meta === "object" ? src.meta : void 0;
|
|
869
|
+
const option = {
|
|
870
|
+
id: "",
|
|
871
|
+
label: "",
|
|
872
|
+
...id && { id },
|
|
873
|
+
...label && { label },
|
|
874
|
+
...value !== void 0 && { value },
|
|
875
|
+
...service_id !== void 0 && { service_id },
|
|
876
|
+
pricing_role,
|
|
877
|
+
...meta && { meta }
|
|
878
|
+
};
|
|
879
|
+
return option;
|
|
880
|
+
}
|
|
881
|
+
function coerceFallbacks(src) {
|
|
882
|
+
if (!src || typeof src !== "object") return void 0;
|
|
883
|
+
const out = {};
|
|
884
|
+
const g = src.global;
|
|
885
|
+
const n = src.nodes;
|
|
886
|
+
if (g && typeof g === "object") {
|
|
887
|
+
const rg = {};
|
|
888
|
+
for (const [k, v] of Object.entries(g)) {
|
|
889
|
+
const key = String(k);
|
|
890
|
+
const arr = toServiceIdArray(v);
|
|
891
|
+
const clean = dedupe(arr.filter((x) => String(x) !== key));
|
|
892
|
+
if (clean.length) rg[key] = clean;
|
|
893
|
+
}
|
|
894
|
+
if (Object.keys(rg).length) out.global = rg;
|
|
895
|
+
}
|
|
896
|
+
if (n && typeof n === "object") {
|
|
897
|
+
const rn = {};
|
|
898
|
+
for (const [nodeId, v] of Object.entries(n)) {
|
|
899
|
+
const key = String(nodeId);
|
|
900
|
+
const arr = toServiceIdArray(v);
|
|
901
|
+
const clean = dedupe(arr.filter((x) => String(x) !== key));
|
|
902
|
+
if (clean.length) rn[key] = clean;
|
|
903
|
+
}
|
|
904
|
+
if (Object.keys(rn).length) out.nodes = rn;
|
|
905
|
+
}
|
|
906
|
+
return out.nodes || out.global ? out : void 0;
|
|
907
|
+
}
|
|
908
|
+
function toObject(input) {
|
|
909
|
+
if (input && typeof input === "object")
|
|
910
|
+
return input;
|
|
911
|
+
throw new TypeError("normalise(): expected an object payload");
|
|
912
|
+
}
|
|
913
|
+
function normaliseBindId(bind) {
|
|
914
|
+
if (typeof bind === "string" && bind.trim()) return bind.trim();
|
|
915
|
+
if (Array.isArray(bind)) {
|
|
916
|
+
const arr = dedupe(bind.map((b) => String(b).trim()).filter(Boolean));
|
|
917
|
+
if (arr.length === 0) return void 0;
|
|
918
|
+
if (arr.length === 1) return arr[0];
|
|
919
|
+
return arr;
|
|
920
|
+
}
|
|
921
|
+
return void 0;
|
|
922
|
+
}
|
|
923
|
+
function toStringArrayMap(src) {
|
|
924
|
+
if (!src || typeof src !== "object") return void 0;
|
|
925
|
+
const out = {};
|
|
926
|
+
for (const [k, v] of Object.entries(src)) {
|
|
927
|
+
if (!k) continue;
|
|
928
|
+
const arr = toStringArray(v);
|
|
929
|
+
if (arr.length) out[k] = dedupe(arr);
|
|
930
|
+
}
|
|
931
|
+
return Object.keys(out).length ? out : void 0;
|
|
932
|
+
}
|
|
933
|
+
function toStringArray(v) {
|
|
934
|
+
if (!Array.isArray(v)) return [];
|
|
935
|
+
return v.map((x) => String(x)).filter((s) => !!s && s.trim().length > 0);
|
|
936
|
+
}
|
|
937
|
+
function toNumberOrUndefined(v) {
|
|
938
|
+
if (v === null || v === void 0) return void 0;
|
|
939
|
+
const n = Number(v);
|
|
940
|
+
return Number.isFinite(n) ? n : void 0;
|
|
941
|
+
}
|
|
942
|
+
function str(v) {
|
|
943
|
+
if (typeof v === "string" && v.trim().length > 0) return v.trim();
|
|
944
|
+
return void 0;
|
|
945
|
+
}
|
|
946
|
+
function bool(v) {
|
|
947
|
+
if (v === void 0) return void 0;
|
|
948
|
+
return !!v;
|
|
949
|
+
}
|
|
950
|
+
function dedupe(arr) {
|
|
951
|
+
return Array.from(new Set(arr));
|
|
952
|
+
}
|
|
953
|
+
function isNonEmpty(obj) {
|
|
954
|
+
return !!obj && Object.keys(obj).length > 0;
|
|
955
|
+
}
|
|
956
|
+
function toServiceIdArray(v) {
|
|
957
|
+
if (!Array.isArray(v)) return [];
|
|
958
|
+
return v.map(
|
|
959
|
+
(x) => typeof x === "number" || typeof x === "string" ? x : String(x)
|
|
960
|
+
).filter(
|
|
961
|
+
(x) => x !== "" && x !== null && x !== void 0
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// src/utils/index.ts
|
|
966
|
+
function isMultiField(f) {
|
|
967
|
+
var _a;
|
|
968
|
+
const t = (f.type || "").toLowerCase();
|
|
969
|
+
const metaMulti = !!((_a = f.meta) == null ? void 0 : _a.multi);
|
|
970
|
+
return t === "multiselect" || t === "checkbox" || metaMulti;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// src/core/policy.ts
|
|
974
|
+
var ALLOWED_SCOPES = /* @__PURE__ */ new Set([
|
|
975
|
+
"global",
|
|
976
|
+
"visible_group"
|
|
977
|
+
]);
|
|
978
|
+
var ALLOWED_SUBJECTS = /* @__PURE__ */ new Set(["services"]);
|
|
979
|
+
var ALLOWED_OPS = /* @__PURE__ */ new Set([
|
|
980
|
+
"all_equal",
|
|
981
|
+
"unique",
|
|
982
|
+
"no_mix",
|
|
983
|
+
"all_true",
|
|
984
|
+
"any_true",
|
|
985
|
+
"max_count",
|
|
986
|
+
"min_count"
|
|
987
|
+
]);
|
|
988
|
+
var ALLOWED_ROLES = /* @__PURE__ */ new Set([
|
|
989
|
+
"base",
|
|
990
|
+
"utility",
|
|
991
|
+
"both"
|
|
992
|
+
]);
|
|
993
|
+
var ALLOWED_SEVERITIES = /* @__PURE__ */ new Set([
|
|
994
|
+
"error",
|
|
995
|
+
"warning"
|
|
996
|
+
]);
|
|
997
|
+
var ALLOWED_WHERE_OPS = /* @__PURE__ */ new Set(["eq", "neq", "in", "nin", "exists", "truthy", "falsy"]);
|
|
998
|
+
function normaliseWhere(src, d, i, id) {
|
|
999
|
+
if (src === void 0) return void 0;
|
|
1000
|
+
if (!Array.isArray(src)) {
|
|
1001
|
+
d.push({
|
|
1002
|
+
ruleIndex: i,
|
|
1003
|
+
ruleId: id,
|
|
1004
|
+
severity: "warning",
|
|
1005
|
+
message: "filter.where must be an array; ignored.",
|
|
1006
|
+
path: "filter.where"
|
|
1007
|
+
});
|
|
1008
|
+
return void 0;
|
|
1009
|
+
}
|
|
1010
|
+
const out = [];
|
|
1011
|
+
src.forEach((raw, j) => {
|
|
1012
|
+
const obj = raw && typeof raw === "object" ? raw : null;
|
|
1013
|
+
const path = typeof (obj == null ? void 0 : obj.path) === "string" && obj.path.trim() ? obj.path.trim() : void 0;
|
|
1014
|
+
if (!path) {
|
|
1015
|
+
d.push({
|
|
1016
|
+
ruleIndex: i,
|
|
1017
|
+
ruleId: id,
|
|
1018
|
+
severity: "warning",
|
|
1019
|
+
message: `filter.where[${j}].path must be a non-empty string; entry ignored.`,
|
|
1020
|
+
path: `filter.where[${j}].path`
|
|
1021
|
+
});
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
if (!path.startsWith("service.")) {
|
|
1025
|
+
d.push({
|
|
1026
|
+
ruleIndex: i,
|
|
1027
|
+
ruleId: id,
|
|
1028
|
+
severity: "warning",
|
|
1029
|
+
message: `filter.where[${j}].path should start with "service." for subject "services".`,
|
|
1030
|
+
path: `filter.where[${j}].path`
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
const opRaw = obj == null ? void 0 : obj.op;
|
|
1034
|
+
const op = opRaw === void 0 ? "eq" : typeof opRaw === "string" && ALLOWED_WHERE_OPS.has(opRaw) ? opRaw : "eq";
|
|
1035
|
+
if (opRaw !== void 0 && !(typeof opRaw === "string" && ALLOWED_WHERE_OPS.has(opRaw))) {
|
|
1036
|
+
d.push({
|
|
1037
|
+
ruleIndex: i,
|
|
1038
|
+
ruleId: id,
|
|
1039
|
+
severity: "warning",
|
|
1040
|
+
message: `Unknown filter.where[${j}].op; defaulted to "eq".`,
|
|
1041
|
+
path: `filter.where[${j}].op`
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
const value = obj == null ? void 0 : obj.value;
|
|
1045
|
+
if (op === "exists" || op === "truthy" || op === "falsy") {
|
|
1046
|
+
if (value !== void 0) {
|
|
1047
|
+
d.push({
|
|
1048
|
+
ruleIndex: i,
|
|
1049
|
+
ruleId: id,
|
|
1050
|
+
severity: "warning",
|
|
1051
|
+
message: `filter.where[${j}] op "${op}" does not use "value".`,
|
|
1052
|
+
path: `filter.where[${j}].value`
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
} else if (op === "in" || op === "nin") {
|
|
1056
|
+
if (!Array.isArray(value)) {
|
|
1057
|
+
d.push({
|
|
1058
|
+
ruleIndex: i,
|
|
1059
|
+
ruleId: id,
|
|
1060
|
+
severity: "warning",
|
|
1061
|
+
message: `filter.where[${j}] op "${op}" expects an array "value".`,
|
|
1062
|
+
path: `filter.where[${j}].value`
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
out.push({ path, op, value });
|
|
1067
|
+
});
|
|
1068
|
+
return out.length ? out : void 0;
|
|
1069
|
+
}
|
|
1070
|
+
function compilePolicies(raw) {
|
|
1071
|
+
const diagnostics = [];
|
|
1072
|
+
const policies = [];
|
|
1073
|
+
if (!Array.isArray(raw)) {
|
|
1074
|
+
diagnostics.push({
|
|
1075
|
+
ruleIndex: -1,
|
|
1076
|
+
severity: "error",
|
|
1077
|
+
message: "Policies root must be an array."
|
|
1078
|
+
});
|
|
1079
|
+
return { policies, diagnostics };
|
|
1080
|
+
}
|
|
1081
|
+
raw.forEach((entry, i) => {
|
|
1082
|
+
const d = [];
|
|
1083
|
+
const src = entry && typeof entry === "object" ? entry : {};
|
|
1084
|
+
let id = typeof src.id === "string" && src.id.trim() ? src.id.trim() : void 0;
|
|
1085
|
+
if (!id) {
|
|
1086
|
+
id = `policy_${i + 1}`;
|
|
1087
|
+
d.push({
|
|
1088
|
+
ruleIndex: i,
|
|
1089
|
+
ruleId: id,
|
|
1090
|
+
severity: "warning",
|
|
1091
|
+
message: 'Missing "id"; generated automatically.',
|
|
1092
|
+
path: "id"
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
const label = typeof src.label === "string" && src.label.trim() ? src.label.trim() : id;
|
|
1096
|
+
if (!(typeof src.label === "string" && src.label.trim())) {
|
|
1097
|
+
d.push({
|
|
1098
|
+
ruleIndex: i,
|
|
1099
|
+
ruleId: id,
|
|
1100
|
+
severity: "warning",
|
|
1101
|
+
message: 'Missing "label"; defaulted to rule id.',
|
|
1102
|
+
path: "label"
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
let scope = ALLOWED_SCOPES.has(src.scope) ? src.scope : src.scope === void 0 ? "visible_group" : "visible_group";
|
|
1106
|
+
if (src.scope !== void 0 && !ALLOWED_SCOPES.has(src.scope)) {
|
|
1107
|
+
d.push({
|
|
1108
|
+
ruleIndex: i,
|
|
1109
|
+
ruleId: id,
|
|
1110
|
+
severity: "warning",
|
|
1111
|
+
message: 'Unknown "scope"; defaulted to "visible_group".',
|
|
1112
|
+
path: "scope"
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
let subject = ALLOWED_SUBJECTS.has(src.subject) ? src.subject : "services";
|
|
1116
|
+
if (src.subject !== void 0 && !ALLOWED_SUBJECTS.has(src.subject)) {
|
|
1117
|
+
d.push({
|
|
1118
|
+
ruleIndex: i,
|
|
1119
|
+
ruleId: id,
|
|
1120
|
+
severity: "warning",
|
|
1121
|
+
message: 'Unknown "subject"; defaulted to "services".',
|
|
1122
|
+
path: "subject"
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
const op = src.op;
|
|
1126
|
+
if (!ALLOWED_OPS.has(op)) {
|
|
1127
|
+
d.push({
|
|
1128
|
+
ruleIndex: i,
|
|
1129
|
+
ruleId: id,
|
|
1130
|
+
severity: "error",
|
|
1131
|
+
message: `Invalid "op": ${String(op)}.`,
|
|
1132
|
+
path: "op"
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
let projection = typeof src.projection === "string" && src.projection.trim() ? src.projection.trim() : "service.id";
|
|
1136
|
+
if (subject === "services" && projection && !projection.startsWith("service.")) {
|
|
1137
|
+
d.push({
|
|
1138
|
+
ruleIndex: i,
|
|
1139
|
+
ruleId: id,
|
|
1140
|
+
severity: "warning",
|
|
1141
|
+
message: 'Projection should start with "service." for subject "services".',
|
|
1142
|
+
path: "projection"
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
const filterSrc = src.filter && typeof src.filter === "object" ? src.filter : void 0;
|
|
1146
|
+
const role = (filterSrc == null ? void 0 : filterSrc.role) && ALLOWED_ROLES.has(filterSrc.role) ? filterSrc.role : "both";
|
|
1147
|
+
if ((filterSrc == null ? void 0 : filterSrc.role) && !ALLOWED_ROLES.has(filterSrc.role)) {
|
|
1148
|
+
d.push({
|
|
1149
|
+
ruleIndex: i,
|
|
1150
|
+
ruleId: id,
|
|
1151
|
+
severity: "warning",
|
|
1152
|
+
message: 'Unknown filter.role; defaulted to "both".',
|
|
1153
|
+
path: "filter.role"
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
const filter = {
|
|
1157
|
+
role,
|
|
1158
|
+
tag_id: (filterSrc == null ? void 0 : filterSrc.tag_id) !== void 0 ? Array.isArray(filterSrc.tag_id) ? filterSrc.tag_id : [filterSrc.tag_id] : void 0,
|
|
1159
|
+
field_id: (filterSrc == null ? void 0 : filterSrc.field_id) !== void 0 ? Array.isArray(filterSrc.field_id) ? filterSrc.field_id : [filterSrc.field_id] : void 0,
|
|
1160
|
+
where: normaliseWhere(filterSrc == null ? void 0 : filterSrc.where, d, i, id)
|
|
1161
|
+
};
|
|
1162
|
+
const severity = ALLOWED_SEVERITIES.has(src.severity) ? src.severity : "error";
|
|
1163
|
+
if (src.severity !== void 0 && !ALLOWED_SEVERITIES.has(src.severity)) {
|
|
1164
|
+
d.push({
|
|
1165
|
+
ruleIndex: i,
|
|
1166
|
+
ruleId: id,
|
|
1167
|
+
severity: "warning",
|
|
1168
|
+
message: 'Unknown "severity"; defaulted to "error".',
|
|
1169
|
+
path: "severity"
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
const value = src.value;
|
|
1173
|
+
if (op === "max_count" || op === "min_count") {
|
|
1174
|
+
if (!(typeof value === "number" && Number.isFinite(value))) {
|
|
1175
|
+
d.push({
|
|
1176
|
+
ruleIndex: i,
|
|
1177
|
+
ruleId: id,
|
|
1178
|
+
severity: "error",
|
|
1179
|
+
message: `"${op}" requires numeric "value".`,
|
|
1180
|
+
path: "value"
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
} else if (op === "all_true" || op === "any_true") {
|
|
1184
|
+
if (value !== void 0) {
|
|
1185
|
+
d.push({
|
|
1186
|
+
ruleIndex: i,
|
|
1187
|
+
ruleId: id,
|
|
1188
|
+
severity: "warning",
|
|
1189
|
+
message: `"${op}" ignores "value"; it checks all/any true.`,
|
|
1190
|
+
path: "value"
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
} else {
|
|
1194
|
+
if (value !== void 0) {
|
|
1195
|
+
d.push({
|
|
1196
|
+
ruleIndex: i,
|
|
1197
|
+
ruleId: id,
|
|
1198
|
+
severity: "warning",
|
|
1199
|
+
message: `"${op}" does not use "value".`,
|
|
1200
|
+
path: "value"
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
const hasFatal = d.some((x) => x.severity === "error");
|
|
1205
|
+
if (!hasFatal) {
|
|
1206
|
+
const rule = {
|
|
1207
|
+
id,
|
|
1208
|
+
label,
|
|
1209
|
+
// ✅ now always present
|
|
1210
|
+
scope,
|
|
1211
|
+
subject,
|
|
1212
|
+
filter,
|
|
1213
|
+
projection,
|
|
1214
|
+
op,
|
|
1215
|
+
value,
|
|
1216
|
+
severity,
|
|
1217
|
+
message: typeof src.message === "string" ? src.message : void 0
|
|
1218
|
+
};
|
|
1219
|
+
policies.push(rule);
|
|
1220
|
+
}
|
|
1221
|
+
diagnostics.push(...d);
|
|
1222
|
+
});
|
|
1223
|
+
return { policies, diagnostics };
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// src/utils/util.ts
|
|
1227
|
+
function toFiniteNumber(v) {
|
|
1228
|
+
const n = Number(v);
|
|
1229
|
+
return Number.isFinite(n) ? n : NaN;
|
|
1230
|
+
}
|
|
1231
|
+
function constraintFitOk(svcMap, candidate, constraints) {
|
|
1232
|
+
const cap = svcMap[Number(candidate)];
|
|
1233
|
+
if (!cap) return false;
|
|
1234
|
+
if (constraints.dripfeed === true && !cap.dripfeed) return false;
|
|
1235
|
+
if (constraints.refill === true && !cap.refill) return false;
|
|
1236
|
+
return !(constraints.cancel === true && !cap.cancel);
|
|
1237
|
+
}
|
|
1238
|
+
function rateOk(svcMap, candidate, primary, policy) {
|
|
1239
|
+
var _a, _b, _c;
|
|
1240
|
+
const cand = svcMap[Number(candidate)];
|
|
1241
|
+
const prim = svcMap[Number(primary)];
|
|
1242
|
+
if (!cand || !prim) return false;
|
|
1243
|
+
const cRate = toFiniteNumber(cand.rate);
|
|
1244
|
+
const pRate = toFiniteNumber(prim.rate);
|
|
1245
|
+
if (!Number.isFinite(cRate) || !Number.isFinite(pRate)) return false;
|
|
1246
|
+
const rp = (_a = policy.ratePolicy) != null ? _a : { kind: "lte_primary" };
|
|
1247
|
+
switch (rp.kind) {
|
|
1248
|
+
case "lte_primary":
|
|
1249
|
+
return cRate <= pRate;
|
|
1250
|
+
case "within_pct": {
|
|
1251
|
+
const pct = Math.max(0, (_b = rp.pct) != null ? _b : 0);
|
|
1252
|
+
return cRate <= pRate * (1 + pct / 100);
|
|
1253
|
+
}
|
|
1254
|
+
case "at_least_pct_lower": {
|
|
1255
|
+
const pct = Math.max(0, (_c = rp.pct) != null ? _c : 0);
|
|
1256
|
+
return cRate <= pRate * (1 - pct / 100);
|
|
1257
|
+
}
|
|
1258
|
+
default:
|
|
1259
|
+
return false;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// src/react/canvas/editor.ts
|
|
1264
|
+
var MAX_LIMIT = 100;
|
|
1265
|
+
var isTagId = (id) => id.startsWith("t:");
|
|
1266
|
+
var isFieldId = (id) => id.startsWith("f:");
|
|
1267
|
+
var isOptionId = (id) => id.startsWith("o:");
|
|
1268
|
+
function ownerOfOption(props, optionId) {
|
|
1269
|
+
var _a, _b;
|
|
1270
|
+
for (const f of (_a = props.fields) != null ? _a : []) {
|
|
1271
|
+
const idx = ((_b = f.options) != null ? _b : []).findIndex((o) => o.id === optionId);
|
|
1272
|
+
if (idx >= 0) return { fieldId: f.id, index: idx };
|
|
1273
|
+
}
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1276
|
+
function ensureServiceExists(opts, id) {
|
|
1277
|
+
if (typeof opts.serviceExists === "function") {
|
|
1278
|
+
if (!opts.serviceExists(id))
|
|
1279
|
+
throw new Error(`service_not_found:${String(id)}`);
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
if (opts.serviceMap) {
|
|
1283
|
+
if (!Object.prototype.hasOwnProperty.call(opts.serviceMap, id)) {
|
|
1284
|
+
throw new Error(`service_not_found:${String(id)}`);
|
|
1285
|
+
}
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
throw new Error("service_checker_missing");
|
|
1289
|
+
}
|
|
1290
|
+
var Editor = class {
|
|
1291
|
+
constructor(builder, api, opts = {}) {
|
|
1292
|
+
this.history = [];
|
|
1293
|
+
this.index = -1;
|
|
1294
|
+
// points to current snapshot
|
|
1295
|
+
this.txnDepth = 0;
|
|
1296
|
+
var _a, _b;
|
|
1297
|
+
this.builder = builder;
|
|
1298
|
+
this.api = api;
|
|
1299
|
+
this.opts = {
|
|
1300
|
+
historyLimit: Math.max(
|
|
1301
|
+
1,
|
|
1302
|
+
Math.min((_a = opts.historyLimit) != null ? _a : MAX_LIMIT, 1e3)
|
|
1303
|
+
),
|
|
1304
|
+
validateAfterEach: (_b = opts.validateAfterEach) != null ? _b : false
|
|
1305
|
+
};
|
|
1306
|
+
this.pushHistory(this.makeSnapshot("init"));
|
|
1307
|
+
}
|
|
1308
|
+
/* ───────────────────────── Public API ───────────────────────── */
|
|
1309
|
+
getProps() {
|
|
1310
|
+
return this.builder.getProps();
|
|
1311
|
+
}
|
|
1312
|
+
transact(label, fn) {
|
|
1313
|
+
const wasTop = this.txnDepth === 0;
|
|
1314
|
+
let ok = false;
|
|
1315
|
+
if (wasTop) {
|
|
1316
|
+
this.txnLabel = label;
|
|
1317
|
+
this.stagedBefore = this.makeSnapshot(label + ":before");
|
|
1318
|
+
}
|
|
1319
|
+
this.txnDepth++;
|
|
1320
|
+
try {
|
|
1321
|
+
fn();
|
|
1322
|
+
ok = true;
|
|
1323
|
+
} finally {
|
|
1324
|
+
this.txnDepth--;
|
|
1325
|
+
if (wasTop) {
|
|
1326
|
+
if (ok) {
|
|
1327
|
+
this.commit(label);
|
|
1328
|
+
} else if (this.stagedBefore) {
|
|
1329
|
+
this.loadSnapshot(this.stagedBefore, "undo");
|
|
1330
|
+
}
|
|
1331
|
+
this.txnLabel = void 0;
|
|
1332
|
+
this.stagedBefore = void 0;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
exec(cmd) {
|
|
1337
|
+
var _a;
|
|
1338
|
+
try {
|
|
1339
|
+
const before = this.makeSnapshot(cmd.name + ":before");
|
|
1340
|
+
cmd.do();
|
|
1341
|
+
this.afterMutation(cmd.name, before);
|
|
1342
|
+
} catch (err) {
|
|
1343
|
+
this.emit("editor:error", {
|
|
1344
|
+
message: (_a = err == null ? void 0 : err.message) != null ? _a : String(err),
|
|
1345
|
+
code: "command"
|
|
1346
|
+
});
|
|
1347
|
+
throw err;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
undo() {
|
|
1351
|
+
if (this.index <= 0) return false;
|
|
1352
|
+
this.index--;
|
|
1353
|
+
this.loadSnapshot(this.history[this.index], "undo");
|
|
1354
|
+
this.emit("editor:undo", {
|
|
1355
|
+
stackSize: this.history.length,
|
|
1356
|
+
index: this.index
|
|
1357
|
+
});
|
|
1358
|
+
return true;
|
|
1359
|
+
}
|
|
1360
|
+
redo() {
|
|
1361
|
+
if (this.index >= this.history.length - 1) return false;
|
|
1362
|
+
this.index++;
|
|
1363
|
+
this.loadSnapshot(this.history[this.index], "redo");
|
|
1364
|
+
this.emit("editor:redo", {
|
|
1365
|
+
stackSize: this.history.length,
|
|
1366
|
+
index: this.index
|
|
1367
|
+
});
|
|
1368
|
+
return true;
|
|
1369
|
+
}
|
|
1370
|
+
clearService(id) {
|
|
1371
|
+
this.setService(id, { service_id: void 0 });
|
|
1372
|
+
}
|
|
1373
|
+
/* ───────────── Convenience editing ops (command-wrapped) ───────────── */
|
|
1374
|
+
duplicate(ref, opts = {}) {
|
|
1375
|
+
const snapBefore = this.makeSnapshot("duplicate:before");
|
|
1376
|
+
try {
|
|
1377
|
+
let newId = "";
|
|
1378
|
+
this.transact("duplicate", () => {
|
|
1379
|
+
if (ref.kind === "tag") {
|
|
1380
|
+
newId = this.duplicateTag(ref.id, opts);
|
|
1381
|
+
} else if (ref.kind === "field") {
|
|
1382
|
+
newId = this.duplicateField(ref.id, opts);
|
|
1383
|
+
} else {
|
|
1384
|
+
newId = this.duplicateOption(ref.fieldId, ref.id, opts);
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
return newId;
|
|
1388
|
+
} catch (err) {
|
|
1389
|
+
this.loadSnapshot(snapBefore, "undo");
|
|
1390
|
+
throw err;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Update the display label for a node and refresh the graph so node labels stay in sync.
|
|
1395
|
+
* Supports: tag ("t:*"), field ("f:*"), option ("o:*").
|
|
1396
|
+
* IDs are NOT changed; only the human-readable label.
|
|
1397
|
+
*/
|
|
1398
|
+
reLabel(id, nextLabel) {
|
|
1399
|
+
const label = String(nextLabel != null ? nextLabel : "").trim();
|
|
1400
|
+
this.exec({
|
|
1401
|
+
name: "reLabel",
|
|
1402
|
+
do: () => this.patchProps((p) => {
|
|
1403
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1404
|
+
if (isTagId(id)) {
|
|
1405
|
+
const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
|
|
1406
|
+
if (!t) return;
|
|
1407
|
+
if (((_b = t.label) != null ? _b : "") === label) return;
|
|
1408
|
+
t.label = label;
|
|
1409
|
+
this.api.refreshGraph();
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
if (isOptionId(id)) {
|
|
1413
|
+
const own = ownerOfOption(p, id);
|
|
1414
|
+
if (!own) return;
|
|
1415
|
+
const f = ((_c = p.fields) != null ? _c : []).find(
|
|
1416
|
+
(x) => x.id === own.fieldId
|
|
1417
|
+
);
|
|
1418
|
+
const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
|
|
1419
|
+
if (!o) return;
|
|
1420
|
+
if (((_e = o.label) != null ? _e : "") === label) return;
|
|
1421
|
+
o.label = label;
|
|
1422
|
+
this.api.refreshGraph();
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const fld = ((_f = p.fields) != null ? _f : []).find((x) => x.id === id);
|
|
1426
|
+
if (!fld) return;
|
|
1427
|
+
if (((_g = fld.label) != null ? _g : "") === label) return;
|
|
1428
|
+
fld.label = label;
|
|
1429
|
+
this.api.refreshGraph();
|
|
1430
|
+
}),
|
|
1431
|
+
undo: () => this.api.undo()
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Assign or change a field's `name`. Only allowed when the field (and its options) have NO service mapping.
|
|
1436
|
+
* - If `nextName` is empty/blank → removes the `name`.
|
|
1437
|
+
* - Emits an error if the field or any of its options carry a `service_id`.
|
|
1438
|
+
* - Emits an error if `nextName` collides with an existing field's name (case-sensitive).
|
|
1439
|
+
*/
|
|
1440
|
+
setFieldName(fieldId, nextName) {
|
|
1441
|
+
const raw = typeof nextName === "string" ? nextName : "";
|
|
1442
|
+
const name = raw.trim();
|
|
1443
|
+
this.exec({
|
|
1444
|
+
name: "setFieldName",
|
|
1445
|
+
do: () => this.patchProps((p) => {
|
|
1446
|
+
var _a;
|
|
1447
|
+
const fields = (_a = p.fields) != null ? _a : [];
|
|
1448
|
+
const f = fields.find((x) => x.id === fieldId);
|
|
1449
|
+
if (!f) {
|
|
1450
|
+
this.api.emit("error", {
|
|
1451
|
+
code: "field_not_found",
|
|
1452
|
+
message: `Field not found: ${fieldId}`,
|
|
1453
|
+
meta: { fieldId }
|
|
1454
|
+
});
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
const fieldHasService = typeof f.service_id === "number";
|
|
1458
|
+
const optionHasService = Array.isArray(f.options) ? f.options.some(
|
|
1459
|
+
(o) => typeof o.service_id === "number"
|
|
1460
|
+
) : false;
|
|
1461
|
+
if (fieldHasService || optionHasService) {
|
|
1462
|
+
this.api.emit("error", {
|
|
1463
|
+
code: "field_has_service_mapping",
|
|
1464
|
+
message: "Cannot set a name on a field that maps to a service (either the field or one of its options has a service_id).",
|
|
1465
|
+
meta: {
|
|
1466
|
+
fieldId,
|
|
1467
|
+
fieldHasService,
|
|
1468
|
+
optionHasService
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
if (name.length === 0) {
|
|
1474
|
+
if ("name" in f) delete f.name;
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
const collision = fields.find(
|
|
1478
|
+
(x) => {
|
|
1479
|
+
var _a2;
|
|
1480
|
+
return x.id !== fieldId && ((_a2 = x.name) != null ? _a2 : "") === name;
|
|
1481
|
+
}
|
|
1482
|
+
);
|
|
1483
|
+
if (collision) {
|
|
1484
|
+
this.api.emit("error", {
|
|
1485
|
+
code: "field_name_collision",
|
|
1486
|
+
message: `Another field already uses the name "${name}".`,
|
|
1487
|
+
meta: { fieldId, otherFieldId: collision.id }
|
|
1488
|
+
});
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
f.name = name;
|
|
1492
|
+
}),
|
|
1493
|
+
undo: () => this.api.undo()
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
getLastPolicyDiagnostics() {
|
|
1497
|
+
return this._lastPolicyDiagnostics;
|
|
1498
|
+
}
|
|
1499
|
+
/* ───────────────────── Internals: duplicate impls ───────────────────── */
|
|
1500
|
+
duplicateTag(tagId, opts) {
|
|
1501
|
+
var _a, _b, _c, _d;
|
|
1502
|
+
const props = this.builder.getProps();
|
|
1503
|
+
const tags = (_a = props.filters) != null ? _a : [];
|
|
1504
|
+
const src = tags.find((t) => t.id === tagId);
|
|
1505
|
+
if (!src) throw new Error(`Tag not found: ${tagId}`);
|
|
1506
|
+
const id = (_b = opts.id) != null ? _b : this.uniqueId(src.id);
|
|
1507
|
+
const label = ((_c = opts.labelStrategy) != null ? _c : nextCopyLabel)((_d = src.label) != null ? _d : id);
|
|
1508
|
+
if (!opts.withChildren) {
|
|
1509
|
+
this.patchProps((p) => {
|
|
1510
|
+
var _a2;
|
|
1511
|
+
const clone = { ...src, id, label };
|
|
1512
|
+
clone.bind_id = src.bind_id;
|
|
1513
|
+
clone.constraints_overrides = void 0;
|
|
1514
|
+
clone.constraints_origin = void 0;
|
|
1515
|
+
const arr = (_a2 = p.filters) != null ? _a2 : [];
|
|
1516
|
+
const idx = arr.findIndex((t) => t.id === tagId);
|
|
1517
|
+
arr.splice(idx + 1, 0, clone);
|
|
1518
|
+
p.filters = arr;
|
|
1519
|
+
});
|
|
1520
|
+
return id;
|
|
1521
|
+
}
|
|
1522
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
1523
|
+
const collect = (t, acc) => {
|
|
1524
|
+
acc.push(t);
|
|
1525
|
+
for (const child of tags.filter((x) => x.bind_id === t.id))
|
|
1526
|
+
collect(child, acc);
|
|
1527
|
+
};
|
|
1528
|
+
const subtree = [];
|
|
1529
|
+
collect(src, subtree);
|
|
1530
|
+
for (const n of subtree)
|
|
1531
|
+
idMap.set(n.id, n.id === src.id ? id : this.uniqueId(n.id));
|
|
1532
|
+
const clones = subtree.map((n) => {
|
|
1533
|
+
var _a2, _b2, _c2;
|
|
1534
|
+
const cloned = { ...n };
|
|
1535
|
+
cloned.id = idMap.get(n.id);
|
|
1536
|
+
cloned.label = n.id === src.id ? label : ((_a2 = opts.labelStrategy) != null ? _a2 : nextCopyLabel)((_b2 = n.label) != null ? _b2 : n.id);
|
|
1537
|
+
cloned.bind_id = n.bind_id ? (_c2 = idMap.get(n.bind_id)) != null ? _c2 : n.bind_id : void 0;
|
|
1538
|
+
cloned.constraints_origin = void 0;
|
|
1539
|
+
cloned.constraints_overrides = void 0;
|
|
1540
|
+
return cloned;
|
|
1541
|
+
});
|
|
1542
|
+
this.patchProps((p) => {
|
|
1543
|
+
var _a2;
|
|
1544
|
+
const arr = (_a2 = p.filters) != null ? _a2 : [];
|
|
1545
|
+
const rootIdx = arr.findIndex((t) => t.id === tagId);
|
|
1546
|
+
arr.splice(rootIdx + 1, 0, clones[0]);
|
|
1547
|
+
for (const c of clones.slice(1)) arr.push(c);
|
|
1548
|
+
p.filters = arr;
|
|
1549
|
+
});
|
|
1550
|
+
return id;
|
|
1551
|
+
}
|
|
1552
|
+
duplicateField(fieldId, opts) {
|
|
1553
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1554
|
+
const props = this.builder.getProps();
|
|
1555
|
+
const fields = (_a = props.fields) != null ? _a : [];
|
|
1556
|
+
const src = fields.find((f) => f.id === fieldId);
|
|
1557
|
+
if (!src) throw new Error(`Field not found: ${fieldId}`);
|
|
1558
|
+
const id = (_b = opts.id) != null ? _b : this.uniqueId(src.id);
|
|
1559
|
+
const label = ((_c = opts.labelStrategy) != null ? _c : nextCopyLabel)((_d = src.label) != null ? _d : id);
|
|
1560
|
+
const name = opts.nameStrategy ? opts.nameStrategy(src.name) : nextCopyName(src.name);
|
|
1561
|
+
const optId = (old) => {
|
|
1562
|
+
var _a2;
|
|
1563
|
+
return this.uniqueOptionId(
|
|
1564
|
+
id,
|
|
1565
|
+
((_a2 = opts.optionIdStrategy) != null ? _a2 : defaultOptionIdStrategy)(old)
|
|
1566
|
+
);
|
|
1567
|
+
};
|
|
1568
|
+
const clonedOptions = ((_e = src.options) != null ? _e : []).map((o) => {
|
|
1569
|
+
var _a2, _b2;
|
|
1570
|
+
return {
|
|
1571
|
+
...o,
|
|
1572
|
+
id: optId(o.id),
|
|
1573
|
+
label: ((_a2 = opts.labelStrategy) != null ? _a2 : nextCopyLabel)((_b2 = o.label) != null ? _b2 : o.id)
|
|
1574
|
+
};
|
|
1575
|
+
});
|
|
1576
|
+
const cloned = {
|
|
1577
|
+
...src,
|
|
1578
|
+
id,
|
|
1579
|
+
label,
|
|
1580
|
+
name,
|
|
1581
|
+
bind_id: ((_f = opts.copyBindings) != null ? _f : true) ? src.bind_id : void 0,
|
|
1582
|
+
options: clonedOptions
|
|
1583
|
+
};
|
|
1584
|
+
const optionIdMap = /* @__PURE__ */ new Map();
|
|
1585
|
+
((_g = src.options) != null ? _g : []).forEach((o, i) => {
|
|
1586
|
+
var _a2, _b2;
|
|
1587
|
+
const newOptId = (_b2 = (_a2 = clonedOptions[i]) == null ? void 0 : _a2.id) != null ? _b2 : o.id;
|
|
1588
|
+
optionIdMap.set(o.id, newOptId);
|
|
1589
|
+
});
|
|
1590
|
+
this.patchProps((p) => {
|
|
1591
|
+
var _a2, _b2, _c2, _d2, _e2, _f2, _g2;
|
|
1592
|
+
const arr = (_a2 = p.fields) != null ? _a2 : [];
|
|
1593
|
+
const idx = arr.findIndex((f) => f.id === fieldId);
|
|
1594
|
+
arr.splice(idx + 1, 0, cloned);
|
|
1595
|
+
p.fields = arr;
|
|
1596
|
+
if (opts.copyIncludesExcludes) {
|
|
1597
|
+
for (const t of (_b2 = p.filters) != null ? _b2 : []) {
|
|
1598
|
+
if ((_c2 = t.includes) == null ? void 0 : _c2.includes(fieldId)) {
|
|
1599
|
+
const s = new Set(t.includes);
|
|
1600
|
+
s.add(id);
|
|
1601
|
+
t.includes = Array.from(s);
|
|
1602
|
+
}
|
|
1603
|
+
if ((_d2 = t.excludes) == null ? void 0 : _d2.includes(fieldId)) {
|
|
1604
|
+
const s = new Set(t.excludes);
|
|
1605
|
+
s.add(id);
|
|
1606
|
+
t.excludes = Array.from(s);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
if (opts.copyOptionMaps) {
|
|
1611
|
+
const maps = ["includes_for_buttons", "excludes_for_buttons"];
|
|
1612
|
+
for (const mapKey of maps) {
|
|
1613
|
+
const srcMap = (_e2 = p[mapKey]) != null ? _e2 : {};
|
|
1614
|
+
const nextMap = { ...srcMap };
|
|
1615
|
+
for (const [key, targets] of Object.entries(
|
|
1616
|
+
srcMap
|
|
1617
|
+
)) {
|
|
1618
|
+
if (key === fieldId) {
|
|
1619
|
+
const newKey = id;
|
|
1620
|
+
const merged = /* @__PURE__ */ new Set([
|
|
1621
|
+
...(_f2 = nextMap[newKey]) != null ? _f2 : [],
|
|
1622
|
+
...targets
|
|
1623
|
+
]);
|
|
1624
|
+
nextMap[newKey] = Array.from(merged);
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
if (optionIdMap.has(key)) {
|
|
1628
|
+
const newKey = optionIdMap.get(key);
|
|
1629
|
+
const merged = /* @__PURE__ */ new Set([
|
|
1630
|
+
...(_g2 = nextMap[newKey]) != null ? _g2 : [],
|
|
1631
|
+
...targets
|
|
1632
|
+
]);
|
|
1633
|
+
nextMap[newKey] = Array.from(merged);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
p[mapKey] = nextMap;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
return id;
|
|
1641
|
+
}
|
|
1642
|
+
duplicateOption(fieldId, optionId, opts) {
|
|
1643
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1644
|
+
const props = this.builder.getProps();
|
|
1645
|
+
const fields = (_a = props.fields) != null ? _a : [];
|
|
1646
|
+
const f = fields.find((x) => x.id === fieldId);
|
|
1647
|
+
if (!f) throw new Error(`Field not found: ${fieldId}`);
|
|
1648
|
+
const optIdx = ((_b = f.options) != null ? _b : []).findIndex((o) => o.id === optionId);
|
|
1649
|
+
if (optIdx < 0)
|
|
1650
|
+
throw new Error(`Option not found: ${fieldId}::${optionId}`);
|
|
1651
|
+
const src = ((_c = f.options) != null ? _c : [])[optIdx];
|
|
1652
|
+
const newId = this.uniqueOptionId(
|
|
1653
|
+
fieldId,
|
|
1654
|
+
((_d = opts.optionIdStrategy) != null ? _d : defaultOptionIdStrategy)(src.id)
|
|
1655
|
+
);
|
|
1656
|
+
const newLabel = ((_e = opts.labelStrategy) != null ? _e : nextCopyLabel)(
|
|
1657
|
+
(_f = src.label) != null ? _f : src.id
|
|
1658
|
+
);
|
|
1659
|
+
this.patchProps((p) => {
|
|
1660
|
+
var _a2, _b2, _c2;
|
|
1661
|
+
const fld = ((_a2 = p.fields) != null ? _a2 : []).find((x) => x.id === fieldId);
|
|
1662
|
+
const arr = (_b2 = fld.options) != null ? _b2 : [];
|
|
1663
|
+
const clone = { ...src, id: newId, label: newLabel };
|
|
1664
|
+
arr.splice(optIdx + 1, 0, clone);
|
|
1665
|
+
fld.options = arr;
|
|
1666
|
+
if (opts.copyOptionMaps) {
|
|
1667
|
+
const oldKey = `${fieldId}::${optionId}`;
|
|
1668
|
+
const newKey = `${fieldId}::${newId}`;
|
|
1669
|
+
for (const mapKey of [
|
|
1670
|
+
"includes_for_buttons",
|
|
1671
|
+
"excludes_for_buttons"
|
|
1672
|
+
]) {
|
|
1673
|
+
const m = (_c2 = p[mapKey]) != null ? _c2 : {};
|
|
1674
|
+
if (m[oldKey]) {
|
|
1675
|
+
m[newKey] = Array.from(new Set(m[oldKey]));
|
|
1676
|
+
p[mapKey] = m;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
return newId;
|
|
1682
|
+
}
|
|
1683
|
+
/* ───────────────────── Helpers: uniqueness & naming ───────────────────── */
|
|
1684
|
+
uniqueId(base) {
|
|
1685
|
+
var _a, _b;
|
|
1686
|
+
const props = this.builder.getProps();
|
|
1687
|
+
const taken = /* @__PURE__ */ new Set([
|
|
1688
|
+
...((_a = props.filters) != null ? _a : []).map((t) => t.id),
|
|
1689
|
+
...((_b = props.fields) != null ? _b : []).map((f) => f.id)
|
|
1690
|
+
]);
|
|
1691
|
+
let candidate = nextCopyId(base);
|
|
1692
|
+
while (taken.has(candidate)) candidate = bumpSuffix(candidate);
|
|
1693
|
+
return candidate;
|
|
1694
|
+
}
|
|
1695
|
+
uniqueOptionId(fieldId, base) {
|
|
1696
|
+
var _a, _b;
|
|
1697
|
+
const props = this.builder.getProps();
|
|
1698
|
+
const fld = ((_a = props.fields) != null ? _a : []).find((f) => f.id === fieldId);
|
|
1699
|
+
const taken = new Set(((_b = fld == null ? void 0 : fld.options) != null ? _b : []).map((o) => o.id));
|
|
1700
|
+
let candidate = base;
|
|
1701
|
+
if (taken.has(candidate)) candidate = nextCopyId(candidate);
|
|
1702
|
+
while (taken.has(candidate)) candidate = bumpSuffix(candidate);
|
|
1703
|
+
return candidate;
|
|
1704
|
+
}
|
|
1705
|
+
//---------
|
|
1706
|
+
/**
|
|
1707
|
+
* Reorder a node:
|
|
1708
|
+
* - Tag: among its siblings (same bind_id) inside filters[]
|
|
1709
|
+
* - Field: inside order_for_tags[scopeTagId] (you must pass scopeTagId)
|
|
1710
|
+
* - Option: use placeOption() instead
|
|
1711
|
+
*/
|
|
1712
|
+
placeNode(id, opts) {
|
|
1713
|
+
if (isTagId(id)) {
|
|
1714
|
+
this.exec({
|
|
1715
|
+
name: "placeTag",
|
|
1716
|
+
do: () => this.patchProps((p) => {
|
|
1717
|
+
var _a, _b, _c;
|
|
1718
|
+
const all = (_a = p.filters) != null ? _a : [];
|
|
1719
|
+
const cur = all.find((t) => t.id === id);
|
|
1720
|
+
if (!cur) return;
|
|
1721
|
+
const groupKey = (_b = cur.bind_id) != null ? _b : "__root__";
|
|
1722
|
+
const siblings = all.filter(
|
|
1723
|
+
(t) => {
|
|
1724
|
+
var _a2;
|
|
1725
|
+
return ((_a2 = t.bind_id) != null ? _a2 : "__root__") === groupKey;
|
|
1726
|
+
}
|
|
1727
|
+
);
|
|
1728
|
+
const curIdx = siblings.findIndex((t) => t.id === id);
|
|
1729
|
+
if (curIdx < 0) return;
|
|
1730
|
+
const pulled = siblings.splice(curIdx, 1)[0];
|
|
1731
|
+
let dest = typeof opts.index === "number" ? opts.index : void 0;
|
|
1732
|
+
if (opts.beforeId)
|
|
1733
|
+
dest = Math.max(
|
|
1734
|
+
0,
|
|
1735
|
+
siblings.findIndex(
|
|
1736
|
+
(t) => t.id === opts.beforeId
|
|
1737
|
+
)
|
|
1738
|
+
);
|
|
1739
|
+
if (opts.afterId)
|
|
1740
|
+
dest = Math.min(
|
|
1741
|
+
siblings.length,
|
|
1742
|
+
siblings.findIndex(
|
|
1743
|
+
(t) => t.id === opts.afterId
|
|
1744
|
+
) + 1
|
|
1745
|
+
);
|
|
1746
|
+
if (dest === void 0 || Number.isNaN(dest))
|
|
1747
|
+
dest = siblings.length;
|
|
1748
|
+
const out = [];
|
|
1749
|
+
for (const t of all) {
|
|
1750
|
+
const sameGroup = ((_c = t.bind_id) != null ? _c : "__root__") === groupKey;
|
|
1751
|
+
if (!sameGroup) {
|
|
1752
|
+
out.push(t);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
siblings.splice(dest, 0, pulled);
|
|
1756
|
+
p.filters = [...out, ...siblings];
|
|
1757
|
+
}),
|
|
1758
|
+
undo: () => this.api.undo()
|
|
1759
|
+
});
|
|
1760
|
+
} else if (isFieldId(id)) {
|
|
1761
|
+
if (!opts.scopeTagId)
|
|
1762
|
+
throw new Error("placeNode(field): scopeTagId is required");
|
|
1763
|
+
const fieldId = id;
|
|
1764
|
+
const tagId = opts.scopeTagId;
|
|
1765
|
+
this.exec({
|
|
1766
|
+
name: "placeField",
|
|
1767
|
+
do: () => this.patchProps((p) => {
|
|
1768
|
+
var _a, _b;
|
|
1769
|
+
const map = (_a = p.order_for_tags) != null ? _a : p.order_for_tags = {};
|
|
1770
|
+
const arr = (_b = map[tagId]) != null ? _b : map[tagId] = [];
|
|
1771
|
+
const curIdx = arr.indexOf(fieldId);
|
|
1772
|
+
if (curIdx >= 0) arr.splice(curIdx, 1);
|
|
1773
|
+
let dest = typeof opts.index === "number" ? opts.index : void 0;
|
|
1774
|
+
if (opts.beforeId)
|
|
1775
|
+
dest = Math.max(0, arr.indexOf(opts.beforeId));
|
|
1776
|
+
if (opts.afterId)
|
|
1777
|
+
dest = Math.min(
|
|
1778
|
+
arr.length,
|
|
1779
|
+
arr.indexOf(opts.afterId) + 1
|
|
1780
|
+
);
|
|
1781
|
+
if (dest === void 0 || Number.isNaN(dest))
|
|
1782
|
+
dest = arr.length;
|
|
1783
|
+
arr.splice(dest, 0, fieldId);
|
|
1784
|
+
}),
|
|
1785
|
+
undo: () => this.api.undo()
|
|
1786
|
+
});
|
|
1787
|
+
} else if (isOptionId(id)) {
|
|
1788
|
+
this.placeOption(id, opts);
|
|
1789
|
+
} else {
|
|
1790
|
+
throw new Error("placeNode: unknown id prefix");
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
placeOption(optionId, opts) {
|
|
1794
|
+
if (!isOptionId(optionId))
|
|
1795
|
+
throw new Error('placeOption: optionId must start with "o:"');
|
|
1796
|
+
this.exec({
|
|
1797
|
+
name: "placeOption",
|
|
1798
|
+
do: () => this.patchProps((p) => {
|
|
1799
|
+
var _a;
|
|
1800
|
+
const owner = ownerOfOption(p, optionId);
|
|
1801
|
+
if (!owner) return;
|
|
1802
|
+
const f = ((_a = p.fields) != null ? _a : []).find(
|
|
1803
|
+
(x) => x.id === owner.fieldId
|
|
1804
|
+
);
|
|
1805
|
+
if (!(f == null ? void 0 : f.options)) return;
|
|
1806
|
+
const curIdx = f.options.findIndex(
|
|
1807
|
+
(o) => o.id === optionId
|
|
1808
|
+
);
|
|
1809
|
+
if (curIdx < 0) return;
|
|
1810
|
+
const pulled = f.options.splice(curIdx, 1)[0];
|
|
1811
|
+
let dest = typeof opts.index === "number" ? opts.index : void 0;
|
|
1812
|
+
if (opts.beforeId)
|
|
1813
|
+
dest = Math.max(
|
|
1814
|
+
0,
|
|
1815
|
+
f.options.findIndex((o) => o.id === opts.beforeId)
|
|
1816
|
+
);
|
|
1817
|
+
if (opts.afterId)
|
|
1818
|
+
dest = Math.min(
|
|
1819
|
+
f.options.length,
|
|
1820
|
+
f.options.findIndex((o) => o.id === opts.afterId) + 1
|
|
1821
|
+
);
|
|
1822
|
+
if (dest === void 0 || Number.isNaN(dest))
|
|
1823
|
+
dest = f.options.length;
|
|
1824
|
+
f.options.splice(dest, 0, pulled);
|
|
1825
|
+
}),
|
|
1826
|
+
undo: () => this.api.undo()
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
addOption(fieldId, input) {
|
|
1830
|
+
var _a;
|
|
1831
|
+
const id = (_a = input.id) != null ? _a : this.genId("o");
|
|
1832
|
+
this.exec({
|
|
1833
|
+
name: "addOption",
|
|
1834
|
+
do: () => this.patchProps((p) => {
|
|
1835
|
+
var _a2, _b;
|
|
1836
|
+
const f = ((_a2 = p.fields) != null ? _a2 : []).find((x) => x.id === fieldId);
|
|
1837
|
+
if (!f)
|
|
1838
|
+
throw new Error(
|
|
1839
|
+
`addOption: field '${fieldId}' not found`
|
|
1840
|
+
);
|
|
1841
|
+
const list = (_b = f.options) != null ? _b : f.options = [];
|
|
1842
|
+
if (list.some((o) => o.id === id))
|
|
1843
|
+
throw new Error(`Option id '${id}' already exists`);
|
|
1844
|
+
list.push({ ...input, id });
|
|
1845
|
+
}),
|
|
1846
|
+
undo: () => this.api.undo()
|
|
1847
|
+
});
|
|
1848
|
+
return id;
|
|
1849
|
+
}
|
|
1850
|
+
updateOption(optionId, patch) {
|
|
1851
|
+
if (!isOptionId(optionId))
|
|
1852
|
+
throw new Error('updateOption: optionId must start with "o:"');
|
|
1853
|
+
this.exec({
|
|
1854
|
+
name: "updateOption",
|
|
1855
|
+
do: () => this.patchProps((p) => {
|
|
1856
|
+
var _a;
|
|
1857
|
+
const owner = ownerOfOption(p, optionId);
|
|
1858
|
+
if (!owner) return;
|
|
1859
|
+
const f = ((_a = p.fields) != null ? _a : []).find(
|
|
1860
|
+
(x) => x.id === owner.fieldId
|
|
1861
|
+
);
|
|
1862
|
+
if (!(f == null ? void 0 : f.options)) return;
|
|
1863
|
+
const o = f.options.find((x) => x.id === optionId);
|
|
1864
|
+
if (o) Object.assign(o, patch);
|
|
1865
|
+
}),
|
|
1866
|
+
undo: () => this.api.undo()
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
removeOption(optionId) {
|
|
1870
|
+
if (!isOptionId(optionId))
|
|
1871
|
+
throw new Error('removeOption: optionId must start with "o:"');
|
|
1872
|
+
this.exec({
|
|
1873
|
+
name: "removeOption",
|
|
1874
|
+
do: () => this.patchProps((p) => {
|
|
1875
|
+
var _a;
|
|
1876
|
+
const owner = ownerOfOption(p, optionId);
|
|
1877
|
+
if (!owner) return;
|
|
1878
|
+
const f = ((_a = p.fields) != null ? _a : []).find(
|
|
1879
|
+
(x) => x.id === owner.fieldId
|
|
1880
|
+
);
|
|
1881
|
+
if (!(f == null ? void 0 : f.options)) return;
|
|
1882
|
+
f.options = f.options.filter((o) => o.id !== optionId);
|
|
1883
|
+
const maps = ["includes_for_options", "excludes_for_options"];
|
|
1884
|
+
for (const m of maps) {
|
|
1885
|
+
const map = p[m];
|
|
1886
|
+
if (!map) continue;
|
|
1887
|
+
if (map[optionId]) delete map[optionId];
|
|
1888
|
+
if (!Object.keys(map).length) delete p[m];
|
|
1889
|
+
}
|
|
1890
|
+
}),
|
|
1891
|
+
undo: () => this.api.undo()
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
editLabel(id, label) {
|
|
1895
|
+
const next = (label != null ? label : "").trim();
|
|
1896
|
+
if (!next) throw new Error("Label cannot be empty");
|
|
1897
|
+
this.exec({
|
|
1898
|
+
name: "editLabel",
|
|
1899
|
+
do: () => this.patchProps((p) => {
|
|
1900
|
+
var _a, _b, _c, _d;
|
|
1901
|
+
if (isTagId(id)) {
|
|
1902
|
+
const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
|
|
1903
|
+
if (t) t.label = next;
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
if (isFieldId(id)) {
|
|
1907
|
+
const f = ((_b = p.fields) != null ? _b : []).find((x) => x.id === id);
|
|
1908
|
+
if (f) f.label = next;
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
if (isOptionId(id)) {
|
|
1912
|
+
const own = ownerOfOption(p, id);
|
|
1913
|
+
if (!own) return;
|
|
1914
|
+
const f = ((_c = p.fields) != null ? _c : []).find(
|
|
1915
|
+
(x) => x.id === own.fieldId
|
|
1916
|
+
);
|
|
1917
|
+
const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
|
|
1918
|
+
if (o) o.label = next;
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
throw new Error("editLabel: unsupported id");
|
|
1922
|
+
}),
|
|
1923
|
+
undo: () => this.api.undo()
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
editName(fieldId, name) {
|
|
1927
|
+
this.exec({
|
|
1928
|
+
name: "editName",
|
|
1929
|
+
do: () => this.patchProps((p) => {
|
|
1930
|
+
var _a;
|
|
1931
|
+
const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === fieldId);
|
|
1932
|
+
if (!f) return;
|
|
1933
|
+
f.name = name;
|
|
1934
|
+
}),
|
|
1935
|
+
undo: () => this.api.undo()
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
setService(id, input) {
|
|
1939
|
+
this.exec({
|
|
1940
|
+
name: "setService",
|
|
1941
|
+
do: () => this.patchProps((p) => {
|
|
1942
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1943
|
+
const hasSidKey = Object.prototype.hasOwnProperty.call(
|
|
1944
|
+
input,
|
|
1945
|
+
"service_id"
|
|
1946
|
+
);
|
|
1947
|
+
const validId = hasSidKey && typeof input.service_id === "number" && Number.isFinite(input.service_id);
|
|
1948
|
+
const sid = validId ? Number(input.service_id) : void 0;
|
|
1949
|
+
const nextRole = input.pricing_role;
|
|
1950
|
+
if (isTagId(id)) {
|
|
1951
|
+
const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
|
|
1952
|
+
if (!t) return;
|
|
1953
|
+
if (hasSidKey) {
|
|
1954
|
+
if (sid === void 0) delete t.service_id;
|
|
1955
|
+
else t.service_id = sid;
|
|
1956
|
+
}
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
if (isOptionId(id)) {
|
|
1960
|
+
const own = ownerOfOption(p, id);
|
|
1961
|
+
if (!own) return;
|
|
1962
|
+
const f2 = ((_b = p.fields) != null ? _b : []).find(
|
|
1963
|
+
(x) => x.id === own.fieldId
|
|
1964
|
+
);
|
|
1965
|
+
const o = (_c = f2 == null ? void 0 : f2.options) == null ? void 0 : _c.find((x) => x.id === id);
|
|
1966
|
+
if (!o) return;
|
|
1967
|
+
const currentRole = (_d = o.pricing_role) != null ? _d : "base";
|
|
1968
|
+
const role = nextRole != null ? nextRole : currentRole;
|
|
1969
|
+
if (role === "utility") {
|
|
1970
|
+
if (hasSidKey && sid !== void 0) {
|
|
1971
|
+
this.api.emit("error", {
|
|
1972
|
+
message: "Utilities cannot have service_id (option).",
|
|
1973
|
+
code: "utility_service_conflict",
|
|
1974
|
+
meta: { id, service_id: sid }
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
o.pricing_role = "utility";
|
|
1978
|
+
if ("service_id" in o) delete o.service_id;
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
if (nextRole) o.pricing_role = "base";
|
|
1982
|
+
if (hasSidKey) {
|
|
1983
|
+
if (sid === void 0) delete o.service_id;
|
|
1984
|
+
else o.service_id = sid;
|
|
1985
|
+
}
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
const f = ((_e = p.fields) != null ? _e : []).find((x) => x.id === id);
|
|
1989
|
+
if (!f) {
|
|
1990
|
+
throw new Error(
|
|
1991
|
+
'setService only supports tag ("t:*"), option ("o:*"), or field ("f:*") ids'
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
const isOptionBased = Array.isArray(f.options) && f.options.length > 0;
|
|
1995
|
+
const isButton = !!f.button;
|
|
1996
|
+
if (nextRole) {
|
|
1997
|
+
f.pricing_role = nextRole;
|
|
1998
|
+
}
|
|
1999
|
+
const effectiveRole = (_f = f.pricing_role) != null ? _f : "base";
|
|
2000
|
+
if (isOptionBased) {
|
|
2001
|
+
if (hasSidKey) {
|
|
2002
|
+
this.api.emit("error", {
|
|
2003
|
+
message: "Cannot set service_id on an option-based field. Assign service_id on its options instead.",
|
|
2004
|
+
code: "field_option_based_service_forbidden",
|
|
2005
|
+
meta: { id, service_id: sid }
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
if ("service_id" in f)
|
|
2009
|
+
delete f.service_id;
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
if (!isButton) {
|
|
2013
|
+
if (hasSidKey) {
|
|
2014
|
+
this.api.emit("error", {
|
|
2015
|
+
message: "Only button fields (without options) can have a service_id.",
|
|
2016
|
+
code: "non_button_field_service_forbidden",
|
|
2017
|
+
meta: { id, service_id: sid }
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
if ("service_id" in f)
|
|
2021
|
+
delete f.service_id;
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
if (effectiveRole === "utility") {
|
|
2025
|
+
if (hasSidKey && sid !== void 0) {
|
|
2026
|
+
this.api.emit("error", {
|
|
2027
|
+
message: "Utilities cannot have service_id (field).",
|
|
2028
|
+
code: "utility_service_conflict",
|
|
2029
|
+
meta: { id, service_id: sid }
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
if ("service_id" in f)
|
|
2033
|
+
delete f.service_id;
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
if (hasSidKey) {
|
|
2037
|
+
if (sid === void 0) delete f.service_id;
|
|
2038
|
+
else f.service_id = sid;
|
|
2039
|
+
}
|
|
2040
|
+
}),
|
|
2041
|
+
undo: () => this.api.undo()
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
addTag(partial) {
|
|
2045
|
+
var _a;
|
|
2046
|
+
const id = (_a = partial.id) != null ? _a : this.genId("t");
|
|
2047
|
+
const payload = { ...partial, id };
|
|
2048
|
+
this.exec({
|
|
2049
|
+
name: "addTag",
|
|
2050
|
+
do: () => this.patchProps((p) => {
|
|
2051
|
+
var _a2;
|
|
2052
|
+
p.filters = [...(_a2 = p.filters) != null ? _a2 : [], payload];
|
|
2053
|
+
}),
|
|
2054
|
+
undo: () => this.patchProps((p) => {
|
|
2055
|
+
var _a2;
|
|
2056
|
+
p.filters = ((_a2 = p.filters) != null ? _a2 : []).filter((t) => t.id !== id);
|
|
2057
|
+
})
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
updateTag(id, patch) {
|
|
2061
|
+
let prev;
|
|
2062
|
+
this.exec({
|
|
2063
|
+
name: "updateTag",
|
|
2064
|
+
do: () => this.patchProps((p) => {
|
|
2065
|
+
var _a;
|
|
2066
|
+
p.filters = ((_a = p.filters) != null ? _a : []).map((t) => {
|
|
2067
|
+
if (t.id !== id) return t;
|
|
2068
|
+
prev = t;
|
|
2069
|
+
return { ...t, ...patch };
|
|
2070
|
+
});
|
|
2071
|
+
}),
|
|
2072
|
+
undo: () => this.patchProps((p) => {
|
|
2073
|
+
var _a;
|
|
2074
|
+
p.filters = ((_a = p.filters) != null ? _a : []).map(
|
|
2075
|
+
(t) => t.id === id && prev ? prev : t
|
|
2076
|
+
);
|
|
2077
|
+
})
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
removeTag(id) {
|
|
2081
|
+
let prevSlice;
|
|
2082
|
+
this.exec({
|
|
2083
|
+
name: "removeTag",
|
|
2084
|
+
do: () => this.patchProps((p) => {
|
|
2085
|
+
var _a, _b, _c, _d, _e;
|
|
2086
|
+
prevSlice = (0, import_lodash_es2.cloneDeep)(p);
|
|
2087
|
+
p.filters = ((_a = p.filters) != null ? _a : []).filter((t) => t.id !== id);
|
|
2088
|
+
for (const t of (_b = p.filters) != null ? _b : []) {
|
|
2089
|
+
if (t.bind_id === id) delete t.bind_id;
|
|
2090
|
+
t.includes = ((_c = t.includes) != null ? _c : []).filter((x) => x !== id);
|
|
2091
|
+
t.excludes = ((_d = t.excludes) != null ? _d : []).filter((x) => x !== id);
|
|
2092
|
+
}
|
|
2093
|
+
for (const f of (_e = p.fields) != null ? _e : []) {
|
|
2094
|
+
if (Array.isArray(f.bind_id))
|
|
2095
|
+
f.bind_id = f.bind_id.filter((x) => x !== id);
|
|
2096
|
+
else if (f.bind_id === id) delete f.bind_id;
|
|
2097
|
+
}
|
|
2098
|
+
}),
|
|
2099
|
+
undo: () => this.replaceProps(prevSlice)
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
addField(partial) {
|
|
2103
|
+
var _a;
|
|
2104
|
+
const id = (_a = partial.id) != null ? _a : this.genId("f");
|
|
2105
|
+
const payload = { ...partial, id };
|
|
2106
|
+
this.exec({
|
|
2107
|
+
name: "addField",
|
|
2108
|
+
do: () => this.patchProps((p) => {
|
|
2109
|
+
var _a2;
|
|
2110
|
+
p.fields = [...(_a2 = p.fields) != null ? _a2 : [], payload];
|
|
2111
|
+
}),
|
|
2112
|
+
undo: () => this.patchProps((p) => {
|
|
2113
|
+
var _a2;
|
|
2114
|
+
p.fields = ((_a2 = p.fields) != null ? _a2 : []).filter((f) => f.id !== id);
|
|
2115
|
+
})
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
updateField(id, patch) {
|
|
2119
|
+
let prev;
|
|
2120
|
+
this.exec({
|
|
2121
|
+
name: "updateField",
|
|
2122
|
+
do: () => this.patchProps((p) => {
|
|
2123
|
+
var _a;
|
|
2124
|
+
p.fields = ((_a = p.fields) != null ? _a : []).map((f) => {
|
|
2125
|
+
if (f.id !== id) return f;
|
|
2126
|
+
prev = f;
|
|
2127
|
+
return { ...f, ...patch };
|
|
2128
|
+
});
|
|
2129
|
+
}),
|
|
2130
|
+
undo: () => this.patchProps((p) => {
|
|
2131
|
+
var _a;
|
|
2132
|
+
p.fields = ((_a = p.fields) != null ? _a : []).map(
|
|
2133
|
+
(f) => f.id === id && prev ? prev : f
|
|
2134
|
+
);
|
|
2135
|
+
})
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
removeField(id) {
|
|
2139
|
+
let prevSlice;
|
|
2140
|
+
this.exec({
|
|
2141
|
+
name: "removeField",
|
|
2142
|
+
do: () => this.patchProps((p) => {
|
|
2143
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2144
|
+
prevSlice = (0, import_lodash_es2.cloneDeep)(p);
|
|
2145
|
+
p.fields = ((_a = p.fields) != null ? _a : []).filter((f) => f.id !== id);
|
|
2146
|
+
for (const mapKey of [
|
|
2147
|
+
"includes_for_buttons",
|
|
2148
|
+
"excludes_for_buttons"
|
|
2149
|
+
]) {
|
|
2150
|
+
const m = p[mapKey];
|
|
2151
|
+
if (!m) continue;
|
|
2152
|
+
for (const k of Object.keys(m)) {
|
|
2153
|
+
m[k] = ((_b = m[k]) != null ? _b : []).filter((fid) => fid !== id);
|
|
2154
|
+
if (!((_c = m[k]) == null ? void 0 : _c.length)) delete m[k];
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
for (const t of (_d = p.filters) != null ? _d : []) {
|
|
2158
|
+
t.includes = ((_e = t.includes) != null ? _e : []).filter((x) => x !== id);
|
|
2159
|
+
t.excludes = ((_f = t.excludes) != null ? _f : []).filter((x) => x !== id);
|
|
2160
|
+
}
|
|
2161
|
+
}),
|
|
2162
|
+
undo: () => this.replaceProps(prevSlice)
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
remove(id) {
|
|
2166
|
+
if (isTagId(id)) {
|
|
2167
|
+
this.exec({
|
|
2168
|
+
name: "removeTag",
|
|
2169
|
+
do: () => this.patchProps((p) => {
|
|
2170
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2171
|
+
p.filters = ((_a = p.filters) != null ? _a : []).filter(
|
|
2172
|
+
(t) => t.id !== id
|
|
2173
|
+
);
|
|
2174
|
+
for (const t of (_b = p.filters) != null ? _b : []) {
|
|
2175
|
+
if (t.bind_id === id) delete t.bind_id;
|
|
2176
|
+
t.includes = ((_c = t.includes) != null ? _c : []).filter(
|
|
2177
|
+
(x) => x !== id
|
|
2178
|
+
);
|
|
2179
|
+
t.excludes = ((_d = t.excludes) != null ? _d : []).filter(
|
|
2180
|
+
(x) => x !== id
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
2183
|
+
for (const f of (_e = p.fields) != null ? _e : []) {
|
|
2184
|
+
if (Array.isArray(f.bind_id))
|
|
2185
|
+
f.bind_id = f.bind_id.filter(
|
|
2186
|
+
(x) => x !== id
|
|
2187
|
+
);
|
|
2188
|
+
else if (f.bind_id === id) delete f.bind_id;
|
|
2189
|
+
}
|
|
2190
|
+
if ((_f = p.order_for_tags) == null ? void 0 : _f[id]) delete p.order_for_tags[id];
|
|
2191
|
+
for (const k of Object.keys((_g = p.order_for_tags) != null ? _g : {})) {
|
|
2192
|
+
p.order_for_tags[k] = ((_h = p.order_for_tags[k]) != null ? _h : []).filter(
|
|
2193
|
+
(fid) => {
|
|
2194
|
+
var _a2;
|
|
2195
|
+
return ((_a2 = p.fields) != null ? _a2 : []).some((f) => f.id === fid);
|
|
2196
|
+
}
|
|
2197
|
+
);
|
|
2198
|
+
if (!p.order_for_tags[k].length)
|
|
2199
|
+
delete p.order_for_tags[k];
|
|
2200
|
+
}
|
|
2201
|
+
}),
|
|
2202
|
+
undo: () => this.api.undo()
|
|
2203
|
+
});
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
if (isFieldId(id)) {
|
|
2207
|
+
this.exec({
|
|
2208
|
+
name: "removeField",
|
|
2209
|
+
do: () => this.patchProps((p) => {
|
|
2210
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2211
|
+
p.fields = ((_a = p.fields) != null ? _a : []).filter((f) => f.id !== id);
|
|
2212
|
+
for (const t of (_b = p.filters) != null ? _b : []) {
|
|
2213
|
+
t.includes = ((_c = t.includes) != null ? _c : []).filter(
|
|
2214
|
+
(x) => x !== id
|
|
2215
|
+
);
|
|
2216
|
+
t.excludes = ((_d = t.excludes) != null ? _d : []).filter(
|
|
2217
|
+
(x) => x !== id
|
|
2218
|
+
);
|
|
2219
|
+
}
|
|
2220
|
+
for (const k of Object.keys((_e = p.order_for_tags) != null ? _e : {})) {
|
|
2221
|
+
p.order_for_tags[k] = ((_f = p.order_for_tags[k]) != null ? _f : []).filter((fid) => fid !== id);
|
|
2222
|
+
if (!p.order_for_tags[k].length)
|
|
2223
|
+
delete p.order_for_tags[k];
|
|
2224
|
+
}
|
|
2225
|
+
const maps = ["includes_for_options", "excludes_for_options"];
|
|
2226
|
+
for (const m of maps) {
|
|
2227
|
+
const map = p[m];
|
|
2228
|
+
if (!map) continue;
|
|
2229
|
+
for (const key of Object.keys(map)) {
|
|
2230
|
+
map[key] = ((_g = map[key]) != null ? _g : []).filter(
|
|
2231
|
+
(fid) => fid !== id
|
|
2232
|
+
);
|
|
2233
|
+
if (!((_h = map[key]) == null ? void 0 : _h.length)) delete map[key];
|
|
2234
|
+
}
|
|
2235
|
+
if (!Object.keys(map).length) delete p[m];
|
|
2236
|
+
}
|
|
2237
|
+
}),
|
|
2238
|
+
undo: () => this.api.undo()
|
|
2239
|
+
});
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
if (isOptionId(id)) {
|
|
2243
|
+
this.removeOption(id);
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
throw new Error("remove: unknown id prefix");
|
|
2247
|
+
}
|
|
2248
|
+
getNode(id) {
|
|
2249
|
+
var _a, _b, _c, _d;
|
|
2250
|
+
const props = this.builder.getProps();
|
|
2251
|
+
if (isTagId(id)) {
|
|
2252
|
+
const t = ((_a = props.filters) != null ? _a : []).find((x) => x.id === id);
|
|
2253
|
+
return {
|
|
2254
|
+
kind: "tag",
|
|
2255
|
+
data: t,
|
|
2256
|
+
owners: { parentTagId: t == null ? void 0 : t.bind_id }
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
if (isFieldId(id)) {
|
|
2260
|
+
const f = ((_b = props.fields) != null ? _b : []).find((x) => x.id === id);
|
|
2261
|
+
const bind = Array.isArray(f == null ? void 0 : f.bind_id) ? f.bind_id : (f == null ? void 0 : f.bind_id) ? [f.bind_id] : [];
|
|
2262
|
+
return { kind: "field", data: f, owners: { bindTagIds: bind } };
|
|
2263
|
+
}
|
|
2264
|
+
if (isOptionId(id)) {
|
|
2265
|
+
const own = ownerOfOption(props, id);
|
|
2266
|
+
const f = own ? ((_c = props.fields) != null ? _c : []).find((x) => x.id === own.fieldId) : void 0;
|
|
2267
|
+
const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
|
|
2268
|
+
return {
|
|
2269
|
+
kind: "option",
|
|
2270
|
+
data: o,
|
|
2271
|
+
owners: { fieldId: own == null ? void 0 : own.fieldId }
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
return { kind: "option", data: void 0, owners: {} };
|
|
2275
|
+
}
|
|
2276
|
+
getFieldQuantityRule(id) {
|
|
2277
|
+
var _a, _b;
|
|
2278
|
+
const props = this.builder.getProps();
|
|
2279
|
+
const f = ((_a = props.fields) != null ? _a : []).find((x) => x.id === id);
|
|
2280
|
+
if (!f) return void 0;
|
|
2281
|
+
return normalizeQuantityRule((_b = f.meta) == null ? void 0 : _b.quantity);
|
|
2282
|
+
}
|
|
2283
|
+
setFieldQuantityRule(id, rule) {
|
|
2284
|
+
this.exec({
|
|
2285
|
+
name: "setFieldQuantityRule",
|
|
2286
|
+
do: () => this.patchProps((p) => {
|
|
2287
|
+
var _a, _b;
|
|
2288
|
+
const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === id);
|
|
2289
|
+
if (!f) return;
|
|
2290
|
+
const normalized = normalizeQuantityRule(rule);
|
|
2291
|
+
if (!normalized) {
|
|
2292
|
+
if (((_b = f.meta) == null ? void 0 : _b.quantity) !== void 0) {
|
|
2293
|
+
delete f.meta.quantity;
|
|
2294
|
+
if (f.meta && Object.keys(f.meta).length === 0) {
|
|
2295
|
+
delete f.meta;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
f.meta = {
|
|
2301
|
+
...f.meta,
|
|
2302
|
+
quantity: normalized
|
|
2303
|
+
};
|
|
2304
|
+
}),
|
|
2305
|
+
undo: () => this.api.undo()
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
clearFieldQuantityRule(id) {
|
|
2309
|
+
this.exec({
|
|
2310
|
+
name: "clearFieldQuantityRule",
|
|
2311
|
+
do: () => this.patchProps((p) => {
|
|
2312
|
+
var _a, _b;
|
|
2313
|
+
const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === id);
|
|
2314
|
+
if (!f || !((_b = f.meta) == null ? void 0 : _b.quantity)) return;
|
|
2315
|
+
delete f.meta.quantity;
|
|
2316
|
+
if (f.meta && Object.keys(f.meta).length === 0) {
|
|
2317
|
+
delete f.meta;
|
|
2318
|
+
}
|
|
2319
|
+
}),
|
|
2320
|
+
undo: () => this.api.undo()
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
/** Walk ancestors for a tag and detect if parent→child would create a cycle */
|
|
2324
|
+
wouldCreateTagCycle(p, parentId, childId) {
|
|
2325
|
+
var _a, _b;
|
|
2326
|
+
if (parentId === childId) return true;
|
|
2327
|
+
const tagById = new Map(((_a = p.filters) != null ? _a : []).map((t) => [t.id, t]));
|
|
2328
|
+
let cur = parentId;
|
|
2329
|
+
const guard = /* @__PURE__ */ new Set();
|
|
2330
|
+
while (cur) {
|
|
2331
|
+
if (cur === childId) return true;
|
|
2332
|
+
if (guard.has(cur)) break;
|
|
2333
|
+
guard.add(cur);
|
|
2334
|
+
cur = (_b = tagById.get(cur)) == null ? void 0 : _b.bind_id;
|
|
2335
|
+
}
|
|
2336
|
+
return false;
|
|
2337
|
+
}
|
|
2338
|
+
wouldCreateIncludeExcludeCycle(p, receiverId, targetId) {
|
|
2339
|
+
if (receiverId === targetId) return true;
|
|
2340
|
+
const getDirectRelations = (id) => {
|
|
2341
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
2342
|
+
if (isTagId(id)) {
|
|
2343
|
+
const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
|
|
2344
|
+
return [...(_b = t == null ? void 0 : t.includes) != null ? _b : [], ...(_c = t == null ? void 0 : t.excludes) != null ? _c : []];
|
|
2345
|
+
}
|
|
2346
|
+
const inc = (_e = (_d = p.includes_for_buttons) == null ? void 0 : _d[id]) != null ? _e : [];
|
|
2347
|
+
const exc = (_g = (_f = p.excludes_for_buttons) == null ? void 0 : _f[id]) != null ? _g : [];
|
|
2348
|
+
return [...inc, ...exc];
|
|
2349
|
+
};
|
|
2350
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2351
|
+
const stack = [targetId];
|
|
2352
|
+
while (stack.length > 0) {
|
|
2353
|
+
const curr = stack.pop();
|
|
2354
|
+
if (curr === receiverId) return true;
|
|
2355
|
+
if (visited.has(curr)) continue;
|
|
2356
|
+
visited.add(curr);
|
|
2357
|
+
stack.push(...getDirectRelations(curr));
|
|
2358
|
+
}
|
|
2359
|
+
return false;
|
|
2360
|
+
}
|
|
2361
|
+
include(receiverId, idOrIds) {
|
|
2362
|
+
this.exec({
|
|
2363
|
+
name: "include",
|
|
2364
|
+
do: () => this.patchProps((p) => {
|
|
2365
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2366
|
+
const receiver = this.getNode(receiverId);
|
|
2367
|
+
const ids = Array.isArray(idOrIds) ? idOrIds : [idOrIds];
|
|
2368
|
+
if (receiver.kind === "tag" || receiver.kind === "field" && ((_a = receiver.data) == null ? void 0 : _a.button) || receiver.kind === "option") {
|
|
2369
|
+
if (receiver.kind === "tag") {
|
|
2370
|
+
const t = ((_b = p.filters) != null ? _b : []).find(
|
|
2371
|
+
(x) => x.id === receiverId
|
|
2372
|
+
);
|
|
2373
|
+
if (t) {
|
|
2374
|
+
const accepted = [];
|
|
2375
|
+
const next = new Set((_c = t.includes) != null ? _c : []);
|
|
2376
|
+
for (const id of ids) {
|
|
2377
|
+
if (this.wouldCreateIncludeExcludeCycle(
|
|
2378
|
+
p,
|
|
2379
|
+
receiverId,
|
|
2380
|
+
id
|
|
2381
|
+
)) {
|
|
2382
|
+
this.emit("editor:error", {
|
|
2383
|
+
message: `Cycle detected: ${receiverId} including ${id} would create a cycle.`,
|
|
2384
|
+
code: "cycle_detected",
|
|
2385
|
+
meta: {
|
|
2386
|
+
receiverId,
|
|
2387
|
+
targetId: id,
|
|
2388
|
+
type: "include"
|
|
2389
|
+
}
|
|
2390
|
+
});
|
|
2391
|
+
continue;
|
|
2392
|
+
}
|
|
2393
|
+
next.add(id);
|
|
2394
|
+
accepted.push(id);
|
|
2395
|
+
}
|
|
2396
|
+
if (accepted.length > 0 || ((_e = (_d = t.includes) == null ? void 0 : _d.length) != null ? _e : 0) > 0) {
|
|
2397
|
+
t.includes = Array.from(next);
|
|
2398
|
+
}
|
|
2399
|
+
if (t.excludes) {
|
|
2400
|
+
t.excludes = t.excludes.filter(
|
|
2401
|
+
(x) => !accepted.includes(x)
|
|
2402
|
+
);
|
|
2403
|
+
if (t.excludes.length === 0) {
|
|
2404
|
+
delete t.excludes;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
} else {
|
|
2409
|
+
const accepted = [];
|
|
2410
|
+
const current = (_g = (_f = p.includes_for_buttons) == null ? void 0 : _f[receiverId]) != null ? _g : [];
|
|
2411
|
+
const next = new Set(current);
|
|
2412
|
+
for (const id of ids) {
|
|
2413
|
+
if (this.wouldCreateIncludeExcludeCycle(
|
|
2414
|
+
p,
|
|
2415
|
+
receiverId,
|
|
2416
|
+
id
|
|
2417
|
+
)) {
|
|
2418
|
+
this.emit("editor:error", {
|
|
2419
|
+
message: `Cycle detected: ${receiverId} including ${id} would create a cycle.`,
|
|
2420
|
+
code: "cycle_detected",
|
|
2421
|
+
meta: {
|
|
2422
|
+
receiverId,
|
|
2423
|
+
targetId: id,
|
|
2424
|
+
type: "include"
|
|
2425
|
+
}
|
|
2426
|
+
});
|
|
2427
|
+
continue;
|
|
2428
|
+
}
|
|
2429
|
+
next.add(id);
|
|
2430
|
+
accepted.push(id);
|
|
2431
|
+
}
|
|
2432
|
+
if (accepted.length > 0 || current.length > 0) {
|
|
2433
|
+
if (!p.includes_for_buttons)
|
|
2434
|
+
p.includes_for_buttons = {};
|
|
2435
|
+
p.includes_for_buttons[receiverId] = Array.from(next);
|
|
2436
|
+
}
|
|
2437
|
+
if ((_h = p.excludes_for_buttons) == null ? void 0 : _h[receiverId]) {
|
|
2438
|
+
p.excludes_for_buttons[receiverId] = p.excludes_for_buttons[receiverId].filter(
|
|
2439
|
+
(x) => !accepted.includes(x)
|
|
2440
|
+
);
|
|
2441
|
+
if (p.excludes_for_buttons[receiverId].length === 0) {
|
|
2442
|
+
delete p.excludes_for_buttons[receiverId];
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
if (!p.fields) p.fields = [];
|
|
2447
|
+
if (!p.filters) p.filters = [];
|
|
2448
|
+
} else {
|
|
2449
|
+
throw new Error(
|
|
2450
|
+
"Receiver must be a tag, button field, or option"
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
}),
|
|
2454
|
+
undo: () => this.api.undo()
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
exclude(receiverId, idOrIds) {
|
|
2458
|
+
this.exec({
|
|
2459
|
+
name: "exclude",
|
|
2460
|
+
do: () => this.patchProps((p) => {
|
|
2461
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2462
|
+
const receiver = this.getNode(receiverId);
|
|
2463
|
+
const ids = Array.isArray(idOrIds) ? idOrIds : [idOrIds];
|
|
2464
|
+
if (receiver.kind === "tag" || receiver.kind === "field" && ((_a = receiver.data) == null ? void 0 : _a.button) || receiver.kind === "option") {
|
|
2465
|
+
if (receiver.kind === "tag") {
|
|
2466
|
+
const t = ((_b = p.filters) != null ? _b : []).find(
|
|
2467
|
+
(x) => x.id === receiverId
|
|
2468
|
+
);
|
|
2469
|
+
if (t) {
|
|
2470
|
+
const accepted = [];
|
|
2471
|
+
const next = new Set((_c = t.excludes) != null ? _c : []);
|
|
2472
|
+
for (const id of ids) {
|
|
2473
|
+
if (this.wouldCreateIncludeExcludeCycle(
|
|
2474
|
+
p,
|
|
2475
|
+
receiverId,
|
|
2476
|
+
id
|
|
2477
|
+
)) {
|
|
2478
|
+
this.emit("editor:error", {
|
|
2479
|
+
message: `Cycle detected: ${receiverId} excluding ${id} would create a cycle.`,
|
|
2480
|
+
code: "cycle_detected",
|
|
2481
|
+
meta: {
|
|
2482
|
+
receiverId,
|
|
2483
|
+
targetId: id,
|
|
2484
|
+
type: "exclude"
|
|
2485
|
+
}
|
|
2486
|
+
});
|
|
2487
|
+
continue;
|
|
2488
|
+
}
|
|
2489
|
+
next.add(id);
|
|
2490
|
+
accepted.push(id);
|
|
2491
|
+
}
|
|
2492
|
+
if (accepted.length > 0 || ((_e = (_d = t.excludes) == null ? void 0 : _d.length) != null ? _e : 0) > 0) {
|
|
2493
|
+
t.excludes = Array.from(next);
|
|
2494
|
+
}
|
|
2495
|
+
if (t.includes) {
|
|
2496
|
+
t.includes = t.includes.filter(
|
|
2497
|
+
(x) => !accepted.includes(x)
|
|
2498
|
+
);
|
|
2499
|
+
if (t.includes.length === 0) {
|
|
2500
|
+
delete t.includes;
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
} else {
|
|
2505
|
+
const accepted = [];
|
|
2506
|
+
const current = (_g = (_f = p.excludes_for_buttons) == null ? void 0 : _f[receiverId]) != null ? _g : [];
|
|
2507
|
+
const next = new Set(current);
|
|
2508
|
+
for (const id of ids) {
|
|
2509
|
+
if (this.wouldCreateIncludeExcludeCycle(
|
|
2510
|
+
p,
|
|
2511
|
+
receiverId,
|
|
2512
|
+
id
|
|
2513
|
+
)) {
|
|
2514
|
+
this.emit("editor:error", {
|
|
2515
|
+
message: `Cycle detected: ${receiverId} excluding ${id} would create a cycle.`,
|
|
2516
|
+
code: "cycle_detected",
|
|
2517
|
+
meta: {
|
|
2518
|
+
receiverId,
|
|
2519
|
+
targetId: id,
|
|
2520
|
+
type: "exclude"
|
|
2521
|
+
}
|
|
2522
|
+
});
|
|
2523
|
+
continue;
|
|
2524
|
+
}
|
|
2525
|
+
next.add(id);
|
|
2526
|
+
accepted.push(id);
|
|
2527
|
+
}
|
|
2528
|
+
if (accepted.length > 0 || current.length > 0) {
|
|
2529
|
+
if (!p.excludes_for_buttons)
|
|
2530
|
+
p.excludes_for_buttons = {};
|
|
2531
|
+
p.excludes_for_buttons[receiverId] = Array.from(next);
|
|
2532
|
+
}
|
|
2533
|
+
if ((_h = p.includes_for_buttons) == null ? void 0 : _h[receiverId]) {
|
|
2534
|
+
p.includes_for_buttons[receiverId] = p.includes_for_buttons[receiverId].filter(
|
|
2535
|
+
(x) => !accepted.includes(x)
|
|
2536
|
+
);
|
|
2537
|
+
if (p.includes_for_buttons[receiverId].length === 0) {
|
|
2538
|
+
delete p.includes_for_buttons[receiverId];
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
if (!p.fields) p.fields = [];
|
|
2543
|
+
if (!p.filters) p.filters = [];
|
|
2544
|
+
} else {
|
|
2545
|
+
throw new Error(
|
|
2546
|
+
"Receiver must be a tag, button field, or option"
|
|
2547
|
+
);
|
|
2548
|
+
}
|
|
2549
|
+
}),
|
|
2550
|
+
undo: () => this.api.undo()
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
/* ──────────────────────────────────────────────────────────────────────────
|
|
2554
|
+
* CONNECT
|
|
2555
|
+
* ────────────────────────────────────────────────────────────────────────── */
|
|
2556
|
+
connect(kind, fromId, toId) {
|
|
2557
|
+
this.exec({
|
|
2558
|
+
name: `connect:${kind}`,
|
|
2559
|
+
do: () => this.patchProps((p) => {
|
|
2560
|
+
var _a, _b, _c, _d, _e;
|
|
2561
|
+
if (kind === "bind") {
|
|
2562
|
+
if (isTagId(fromId) && isTagId(toId)) {
|
|
2563
|
+
if (this.wouldCreateTagCycle(p, fromId, toId)) {
|
|
2564
|
+
throw new Error(
|
|
2565
|
+
`bind would create a cycle: ${fromId} \u2192 ${toId}`
|
|
2566
|
+
);
|
|
2567
|
+
}
|
|
2568
|
+
const child = ((_a = p.filters) != null ? _a : []).find(
|
|
2569
|
+
(t) => t.id === toId
|
|
2570
|
+
);
|
|
2571
|
+
if (child) child.bind_id = fromId;
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
if (isTagId(fromId) && isFieldId(toId) || isFieldId(fromId) && isTagId(toId)) {
|
|
2575
|
+
const fieldId = isFieldId(toId) ? toId : fromId;
|
|
2576
|
+
const tagId = isTagId(fromId) ? fromId : toId;
|
|
2577
|
+
const f = ((_b = p.fields) != null ? _b : []).find(
|
|
2578
|
+
(x) => x.id === fieldId
|
|
2579
|
+
);
|
|
2580
|
+
if (!f) return;
|
|
2581
|
+
if (!f.bind_id) {
|
|
2582
|
+
f.bind_id = tagId;
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
if (typeof f.bind_id === "string") {
|
|
2586
|
+
if (f.bind_id !== tagId)
|
|
2587
|
+
f.bind_id = [f.bind_id, tagId];
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
if (!f.bind_id.includes(tagId))
|
|
2591
|
+
f.bind_id.push(tagId);
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
throw new Error(
|
|
2595
|
+
`bind: unsupported route ${fromId} \u2192 ${toId}`
|
|
2596
|
+
);
|
|
2597
|
+
}
|
|
2598
|
+
if (kind === "include" || kind === "exclude") {
|
|
2599
|
+
const key = kind === "include" ? "includes" : "excludes";
|
|
2600
|
+
if (isTagId(fromId) && isFieldId(toId)) {
|
|
2601
|
+
const t = ((_c = p.filters) != null ? _c : []).find(
|
|
2602
|
+
(x) => x.id === fromId
|
|
2603
|
+
);
|
|
2604
|
+
if (!t) return;
|
|
2605
|
+
const arr = (_d = t[key]) != null ? _d : t[key] = [];
|
|
2606
|
+
if (!arr.includes(toId)) arr.push(toId);
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
if (isOptionId(fromId) && isFieldId(toId)) {
|
|
2610
|
+
const mapKey = kind === "include" ? "includes_for_options" : "excludes_for_options";
|
|
2611
|
+
const maps = p[mapKey];
|
|
2612
|
+
const next = { ...maps != null ? maps : {} };
|
|
2613
|
+
const arr = (_e = next[fromId]) != null ? _e : [];
|
|
2614
|
+
if (!arr.includes(toId)) arr.push(toId);
|
|
2615
|
+
next[fromId] = arr;
|
|
2616
|
+
p[mapKey] = next;
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
throw new Error(
|
|
2620
|
+
`${kind}: unsupported route ${fromId} \u2192 ${toId}`
|
|
2621
|
+
);
|
|
2622
|
+
}
|
|
2623
|
+
if (kind === "service") {
|
|
2624
|
+
ensureServiceExists(this.opts, fromId);
|
|
2625
|
+
if (toId.startsWith("t:")) {
|
|
2626
|
+
this.exec({
|
|
2627
|
+
name: "connect:service\u2192tag",
|
|
2628
|
+
do: () => this.patchProps((p2) => {
|
|
2629
|
+
var _a2;
|
|
2630
|
+
const t = ((_a2 = p2.filters) != null ? _a2 : []).find(
|
|
2631
|
+
(x) => x.id === toId
|
|
2632
|
+
);
|
|
2633
|
+
if (t) t.service_id = fromId;
|
|
2634
|
+
}),
|
|
2635
|
+
undo: () => this.api.undo()
|
|
2636
|
+
});
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
if (toId.startsWith("o:")) {
|
|
2640
|
+
this.exec({
|
|
2641
|
+
name: "connect:service\u2192option",
|
|
2642
|
+
do: () => this.patchProps((p2) => {
|
|
2643
|
+
var _a2, _b2;
|
|
2644
|
+
for (const f of (_a2 = p2.fields) != null ? _a2 : []) {
|
|
2645
|
+
const o = (_b2 = f.options) == null ? void 0 : _b2.find(
|
|
2646
|
+
(x) => x.id === toId
|
|
2647
|
+
);
|
|
2648
|
+
if (o) {
|
|
2649
|
+
o.service_id = fromId;
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}),
|
|
2654
|
+
undo: () => this.api.undo()
|
|
2655
|
+
});
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
throw new Error(
|
|
2659
|
+
'service: to must be a tag ("t:*") or option ("o:*")'
|
|
2660
|
+
);
|
|
2661
|
+
}
|
|
2662
|
+
throw new Error(`Unknown connect kind: ${kind}`);
|
|
2663
|
+
}),
|
|
2664
|
+
undo: () => this.api.undo()
|
|
2665
|
+
// snapshot-based undo will restore prior state
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
/* ──────────────────────────────────────────────────────────────────────────
|
|
2669
|
+
* DISCONNECT
|
|
2670
|
+
* ────────────────────────────────────────────────────────────────────────── */
|
|
2671
|
+
disconnect(kind, fromId, toId) {
|
|
2672
|
+
this.exec({
|
|
2673
|
+
name: `disconnect:${kind}`,
|
|
2674
|
+
do: () => this.patchProps((p) => {
|
|
2675
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2676
|
+
if (kind === "bind") {
|
|
2677
|
+
if (isTagId(fromId) && isTagId(toId)) {
|
|
2678
|
+
const child = ((_a = p.filters) != null ? _a : []).find(
|
|
2679
|
+
(t) => t.id === toId
|
|
2680
|
+
);
|
|
2681
|
+
if ((child == null ? void 0 : child.bind_id) === fromId) delete child.bind_id;
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
if (isTagId(fromId) && isFieldId(toId) || isFieldId(fromId) && isTagId(toId)) {
|
|
2685
|
+
const fieldId = isFieldId(toId) ? toId : fromId;
|
|
2686
|
+
const tagId = isTagId(fromId) ? fromId : toId;
|
|
2687
|
+
const f = ((_b = p.fields) != null ? _b : []).find(
|
|
2688
|
+
(x) => x.id === fieldId
|
|
2689
|
+
);
|
|
2690
|
+
if (!(f == null ? void 0 : f.bind_id)) return;
|
|
2691
|
+
if (typeof f.bind_id === "string") {
|
|
2692
|
+
if (f.bind_id === tagId) delete f.bind_id;
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
f.bind_id = f.bind_id.filter(
|
|
2696
|
+
(x) => x !== tagId
|
|
2697
|
+
);
|
|
2698
|
+
if (((_c = f.bind_id) == null ? void 0 : _c.length) === 0) delete f.bind_id;
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
throw new Error(
|
|
2702
|
+
`unbind: unsupported route ${fromId} \u2192 ${toId}`
|
|
2703
|
+
);
|
|
2704
|
+
}
|
|
2705
|
+
if (kind === "include" || kind === "exclude") {
|
|
2706
|
+
const key = kind === "include" ? "includes" : "excludes";
|
|
2707
|
+
if (isTagId(fromId) && isFieldId(toId)) {
|
|
2708
|
+
const t = ((_d = p.filters) != null ? _d : []).find(
|
|
2709
|
+
(x) => x.id === fromId
|
|
2710
|
+
);
|
|
2711
|
+
if (!t) return;
|
|
2712
|
+
t[key] = ((_e = t[key]) != null ? _e : []).filter((x) => x !== toId);
|
|
2713
|
+
if (!((_f = t[key]) == null ? void 0 : _f.length)) delete t[key];
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
if (isOptionId(fromId) && isFieldId(toId)) {
|
|
2717
|
+
const mapKey = kind === "include" ? "includes_for_options" : "excludes_for_options";
|
|
2718
|
+
const maps = p[mapKey];
|
|
2719
|
+
if (!maps) return;
|
|
2720
|
+
if (maps[fromId]) {
|
|
2721
|
+
maps[fromId] = ((_g = maps[fromId]) != null ? _g : []).filter(
|
|
2722
|
+
(fid) => fid !== toId
|
|
2723
|
+
);
|
|
2724
|
+
if (!((_h = maps[fromId]) == null ? void 0 : _h.length)) delete maps[fromId];
|
|
2725
|
+
}
|
|
2726
|
+
if (!Object.keys(maps).length)
|
|
2727
|
+
delete p[mapKey];
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2730
|
+
throw new Error(
|
|
2731
|
+
`${kind}: unsupported route ${fromId} \u2192 ${toId}`
|
|
2732
|
+
);
|
|
2733
|
+
}
|
|
2734
|
+
if (kind === "service") {
|
|
2735
|
+
ensureServiceExists(this.opts, fromId);
|
|
2736
|
+
if (toId.startsWith("t:")) {
|
|
2737
|
+
this.exec({
|
|
2738
|
+
name: "disconnect:service\u2192tag",
|
|
2739
|
+
do: () => this.patchProps((p2) => {
|
|
2740
|
+
var _a2;
|
|
2741
|
+
const t = ((_a2 = p2.filters) != null ? _a2 : []).find(
|
|
2742
|
+
(x) => x.id === toId
|
|
2743
|
+
);
|
|
2744
|
+
if (t) delete t.service_id;
|
|
2745
|
+
}),
|
|
2746
|
+
undo: () => this.api.undo()
|
|
2747
|
+
});
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
if (toId.startsWith("o:")) {
|
|
2751
|
+
this.exec({
|
|
2752
|
+
name: "disconnect:service\u2192option",
|
|
2753
|
+
do: () => this.patchProps((p2) => {
|
|
2754
|
+
var _a2, _b2;
|
|
2755
|
+
for (const f of (_a2 = p2.fields) != null ? _a2 : []) {
|
|
2756
|
+
const o = (_b2 = f.options) == null ? void 0 : _b2.find(
|
|
2757
|
+
(x) => x.id === toId
|
|
2758
|
+
);
|
|
2759
|
+
if (o) {
|
|
2760
|
+
delete o.service_id;
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
}),
|
|
2765
|
+
undo: () => this.api.undo()
|
|
2766
|
+
});
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
throw new Error(
|
|
2770
|
+
'service: to must be a tag ("t:*") or option ("o:*")'
|
|
2771
|
+
);
|
|
2772
|
+
}
|
|
2773
|
+
throw new Error(`Unknown disconnect kind: ${kind}`);
|
|
2774
|
+
}),
|
|
2775
|
+
undo: () => this.api.undo()
|
|
2776
|
+
});
|
|
2777
|
+
}
|
|
2778
|
+
setConstraint(tagId, flag, value) {
|
|
2779
|
+
let prev;
|
|
2780
|
+
this.exec({
|
|
2781
|
+
name: "setConstraint",
|
|
2782
|
+
do: () => this.patchProps((p) => {
|
|
2783
|
+
var _a, _b;
|
|
2784
|
+
const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === tagId);
|
|
2785
|
+
if (!t) return;
|
|
2786
|
+
prev = (_b = t.constraints) == null ? void 0 : _b[flag];
|
|
2787
|
+
if (!t.constraints) t.constraints = {};
|
|
2788
|
+
if (value === void 0) delete t.constraints[flag];
|
|
2789
|
+
else t.constraints[flag] = value;
|
|
2790
|
+
}),
|
|
2791
|
+
undo: () => this.patchProps((p) => {
|
|
2792
|
+
var _a;
|
|
2793
|
+
const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === tagId);
|
|
2794
|
+
if (!t) return;
|
|
2795
|
+
if (!t.constraints) t.constraints = {};
|
|
2796
|
+
if (prev === void 0) delete t.constraints[flag];
|
|
2797
|
+
else t.constraints[flag] = prev;
|
|
2798
|
+
})
|
|
2799
|
+
});
|
|
2800
|
+
}
|
|
2801
|
+
/**
|
|
2802
|
+
* Clear a constraint override by removing the local constraint that conflicts with an ancestor.
|
|
2803
|
+
*/
|
|
2804
|
+
clearConstraintOverride(tagId, flag) {
|
|
2805
|
+
let prev;
|
|
2806
|
+
let prevOverride;
|
|
2807
|
+
this.exec({
|
|
2808
|
+
name: "clearConstraintOverride",
|
|
2809
|
+
do: () => this.patchProps((p) => {
|
|
2810
|
+
var _a, _b, _c;
|
|
2811
|
+
const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === tagId);
|
|
2812
|
+
if (!t) return;
|
|
2813
|
+
prev = (_b = t.constraints) == null ? void 0 : _b[flag];
|
|
2814
|
+
prevOverride = (_c = t.constraints_overrides) == null ? void 0 : _c[flag];
|
|
2815
|
+
if (t.constraints) delete t.constraints[flag];
|
|
2816
|
+
if (t.constraints_overrides)
|
|
2817
|
+
delete t.constraints_overrides[flag];
|
|
2818
|
+
}),
|
|
2819
|
+
undo: () => this.patchProps((p) => {
|
|
2820
|
+
var _a;
|
|
2821
|
+
const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === tagId);
|
|
2822
|
+
if (!t) return;
|
|
2823
|
+
if (prev !== void 0) {
|
|
2824
|
+
if (!t.constraints) t.constraints = {};
|
|
2825
|
+
t.constraints[flag] = prev;
|
|
2826
|
+
}
|
|
2827
|
+
if (prevOverride !== void 0) {
|
|
2828
|
+
if (!t.constraints_overrides)
|
|
2829
|
+
t.constraints_overrides = {};
|
|
2830
|
+
t.constraints_overrides[flag] = prevOverride;
|
|
2831
|
+
}
|
|
2832
|
+
})
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2835
|
+
/**
|
|
2836
|
+
* Clear a constraint from a tag and its descendants.
|
|
2837
|
+
* If a descendant has an override, it assigns that override's value as local.
|
|
2838
|
+
*/
|
|
2839
|
+
clearConstraint(tagId, flag) {
|
|
2840
|
+
this.exec({
|
|
2841
|
+
name: "clearConstraint",
|
|
2842
|
+
do: () => this.patchProps((p) => {
|
|
2843
|
+
var _a;
|
|
2844
|
+
const tags = (_a = p.filters) != null ? _a : [];
|
|
2845
|
+
const byId = new Map(tags.map((t) => [t.id, t]));
|
|
2846
|
+
const children = /* @__PURE__ */ new Map();
|
|
2847
|
+
for (const t of tags) {
|
|
2848
|
+
if (t.bind_id) {
|
|
2849
|
+
if (!children.has(t.bind_id))
|
|
2850
|
+
children.set(t.bind_id, []);
|
|
2851
|
+
children.get(t.bind_id).push(t.id);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
const process = (id) => {
|
|
2855
|
+
var _a2, _b, _c;
|
|
2856
|
+
const t = byId.get(id);
|
|
2857
|
+
if (!t) return;
|
|
2858
|
+
const override = (_a2 = t.constraints_overrides) == null ? void 0 : _a2[flag];
|
|
2859
|
+
if (override) {
|
|
2860
|
+
if (!t.constraints) t.constraints = {};
|
|
2861
|
+
t.constraints[flag] = override.from;
|
|
2862
|
+
delete t.constraints_overrides[flag];
|
|
2863
|
+
if (Object.keys((_b = t.constraints_overrides) != null ? _b : {}).length === 0) {
|
|
2864
|
+
delete t.constraints_overrides;
|
|
2865
|
+
}
|
|
2866
|
+
} else {
|
|
2867
|
+
if (t.constraints) {
|
|
2868
|
+
delete t.constraints[flag];
|
|
2869
|
+
if (Object.keys(t.constraints).length === 0) {
|
|
2870
|
+
delete t.constraints;
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
for (const childId of (_c = children.get(id)) != null ? _c : []) {
|
|
2875
|
+
process(childId);
|
|
2876
|
+
}
|
|
2877
|
+
};
|
|
2878
|
+
process(tagId);
|
|
2879
|
+
}),
|
|
2880
|
+
undo: () => this.api.undo()
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
/* ───────────────────── Internals ───────────────────── */
|
|
2884
|
+
replaceProps(next) {
|
|
2885
|
+
const norm = normalise(next, {
|
|
2886
|
+
constraints: this.builder.getConstraints().map((item) => item.label),
|
|
2887
|
+
defaultPricingRole: "base"
|
|
2888
|
+
});
|
|
2889
|
+
this.builder.load(norm);
|
|
2890
|
+
this.api.refreshGraph();
|
|
2891
|
+
}
|
|
2892
|
+
patchProps(mut) {
|
|
2893
|
+
const cur = (0, import_lodash_es2.cloneDeep)(this.builder.getProps());
|
|
2894
|
+
mut(cur);
|
|
2895
|
+
this.replaceProps(cur);
|
|
2896
|
+
}
|
|
2897
|
+
afterMutation(command, _before) {
|
|
2898
|
+
if (this.txnDepth > 0) return;
|
|
2899
|
+
this.pushHistory(this.makeSnapshot(command));
|
|
2900
|
+
this.emit("editor:command", { name: command });
|
|
2901
|
+
if (this.opts.validateAfterEach)
|
|
2902
|
+
this.emit("editor:change", {
|
|
2903
|
+
props: this.builder.getProps(),
|
|
2904
|
+
reason: "validate",
|
|
2905
|
+
command
|
|
2906
|
+
});
|
|
2907
|
+
else
|
|
2908
|
+
this.emit("editor:change", {
|
|
2909
|
+
props: this.builder.getProps(),
|
|
2910
|
+
reason: "mutation",
|
|
2911
|
+
command
|
|
2912
|
+
});
|
|
2913
|
+
}
|
|
2914
|
+
commit(label) {
|
|
2915
|
+
const snap = this.makeSnapshot(label);
|
|
2916
|
+
this.pushHistory(snap);
|
|
2917
|
+
this.emit("editor:change", {
|
|
2918
|
+
props: snap.props,
|
|
2919
|
+
reason: "transaction",
|
|
2920
|
+
command: this.txnLabel
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
makeSnapshot(_why) {
|
|
2924
|
+
const props = (0, import_lodash_es2.cloneDeep)(this.builder.getProps());
|
|
2925
|
+
const canvas = this.api.snapshot();
|
|
2926
|
+
return {
|
|
2927
|
+
props,
|
|
2928
|
+
layout: {
|
|
2929
|
+
canvas
|
|
2930
|
+
}
|
|
2931
|
+
};
|
|
2932
|
+
}
|
|
2933
|
+
loadSnapshot(s, reason) {
|
|
2934
|
+
this.builder.load((0, import_lodash_es2.cloneDeep)(s.props));
|
|
2935
|
+
const layout = s.layout;
|
|
2936
|
+
const canvas = layout == null ? void 0 : layout.canvas;
|
|
2937
|
+
if (canvas) {
|
|
2938
|
+
if (canvas.positions) this.api.setPositions(canvas.positions);
|
|
2939
|
+
if (canvas.viewport) this.api.setViewport(canvas.viewport);
|
|
2940
|
+
if (canvas.selection)
|
|
2941
|
+
this.api.select(
|
|
2942
|
+
Array.isArray(canvas.selection) ? canvas.selection : Array.from(canvas.selection)
|
|
2943
|
+
);
|
|
2944
|
+
} else {
|
|
2945
|
+
this.api.refreshGraph();
|
|
2946
|
+
}
|
|
2947
|
+
this.emit("editor:change", { props: this.builder.getProps(), reason });
|
|
2948
|
+
}
|
|
2949
|
+
pushHistory(snap) {
|
|
2950
|
+
if (this.index < this.history.length - 1) {
|
|
2951
|
+
this.history = this.history.slice(0, this.index + 1);
|
|
2952
|
+
}
|
|
2953
|
+
this.history.push(snap);
|
|
2954
|
+
const over = this.history.length - this.opts.historyLimit;
|
|
2955
|
+
if (over > 0) {
|
|
2956
|
+
this.history.splice(0, over);
|
|
2957
|
+
this.index = this.history.length - 1;
|
|
2958
|
+
} else {
|
|
2959
|
+
this.index = this.history.length - 1;
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
// IDs like "t:1", "f:2", "o:3" — must be unique across tags, fields, options.
|
|
2963
|
+
genId(prefix) {
|
|
2964
|
+
var _a, _b, _c;
|
|
2965
|
+
const props = this.builder.getProps();
|
|
2966
|
+
const taken = /* @__PURE__ */ new Set([
|
|
2967
|
+
...((_a = props.filters) != null ? _a : []).map((t) => t.id),
|
|
2968
|
+
...((_b = props.fields) != null ? _b : []).map((f) => f.id),
|
|
2969
|
+
...((_c = props.fields) != null ? _c : []).flatMap(
|
|
2970
|
+
(f) => {
|
|
2971
|
+
var _a2, _b2;
|
|
2972
|
+
return (_b2 = (_a2 = f.options) == null ? void 0 : _a2.map((o) => o.id)) != null ? _b2 : [];
|
|
2973
|
+
}
|
|
2974
|
+
)
|
|
2975
|
+
]);
|
|
2976
|
+
for (let i = 1; i < 1e4; i++) {
|
|
2977
|
+
const id = `${prefix}:${i}`;
|
|
2978
|
+
if (!taken.has(id)) return id;
|
|
2979
|
+
}
|
|
2980
|
+
throw new Error("Unable to generate id");
|
|
2981
|
+
}
|
|
2982
|
+
emit(event, payload) {
|
|
2983
|
+
this.api.emit(event, payload);
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Suggest/filter candidate services against the current visible-group
|
|
2987
|
+
* (single tag) context.
|
|
2988
|
+
*
|
|
2989
|
+
* - Excludes services already used in this group.
|
|
2990
|
+
* - Applies capability presence, tag constraints, rate policy, and compiled policies.
|
|
2991
|
+
*
|
|
2992
|
+
* @param candidates service ids to evaluate
|
|
2993
|
+
* @param ctx
|
|
2994
|
+
* @param ctx.tagId active visible-group tag id
|
|
2995
|
+
* @param ctx.usedServiceIds services already selected for this visible group (first is treated as "primary" for rate policy)
|
|
2996
|
+
* @param ctx.effectiveConstraints effective constraints for the active tag (dripfeed/refill/cancel)
|
|
2997
|
+
* @param ctx.policies raw JSON policies (will be compiled via compilePolicies)
|
|
2998
|
+
* @param ctx.fallback fallback/rate settings (defaults applied if omitted)
|
|
2999
|
+
*/
|
|
3000
|
+
filterServicesForVisibleGroup(candidates, ctx) {
|
|
3001
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
3002
|
+
const svcMap = (_e = (_d = (_a = this.opts) == null ? void 0 : _a.serviceMap) != null ? _d : (_c = (_b = this.builder).getServiceMap) == null ? void 0 : _c.call(_b)) != null ? _e : {};
|
|
3003
|
+
const usedSet = new Set(ctx.usedServiceIds.map(String));
|
|
3004
|
+
const primary = ctx.usedServiceIds[0];
|
|
3005
|
+
const fb = {
|
|
3006
|
+
requireConstraintFit: true,
|
|
3007
|
+
ratePolicy: { kind: "lte_primary" },
|
|
3008
|
+
selectionStrategy: "priority",
|
|
3009
|
+
mode: "strict",
|
|
3010
|
+
...(_f = ctx.fallback) != null ? _f : {}
|
|
3011
|
+
};
|
|
3012
|
+
const evaluatePoliciesRaw = (raw, serviceIds, tagId) => {
|
|
3013
|
+
const { policies } = compilePolicies(raw);
|
|
3014
|
+
return evaluateServicePolicies(policies, serviceIds, svcMap, tagId);
|
|
3015
|
+
};
|
|
3016
|
+
const out = [];
|
|
3017
|
+
for (const id of candidates) {
|
|
3018
|
+
if (usedSet.has(String(id))) continue;
|
|
3019
|
+
const cap = svcMap[Number(id)];
|
|
3020
|
+
if (!cap) {
|
|
3021
|
+
out.push({
|
|
3022
|
+
id,
|
|
3023
|
+
ok: false,
|
|
3024
|
+
fitsConstraints: false,
|
|
3025
|
+
passesRate: false,
|
|
3026
|
+
passesPolicies: false,
|
|
3027
|
+
reasons: ["missing_capability"]
|
|
3028
|
+
});
|
|
3029
|
+
continue;
|
|
3030
|
+
}
|
|
3031
|
+
const fitsConstraints = constraintFitOk(
|
|
3032
|
+
svcMap,
|
|
3033
|
+
cap.id,
|
|
3034
|
+
(_g = ctx.effectiveConstraints) != null ? _g : {}
|
|
3035
|
+
);
|
|
3036
|
+
const passesRate = primary == null ? true : rateOk(svcMap, id, primary, fb);
|
|
3037
|
+
const polRes = evaluatePoliciesRaw(
|
|
3038
|
+
(_h = ctx.policies) != null ? _h : [],
|
|
3039
|
+
[...ctx.usedServiceIds, id],
|
|
3040
|
+
ctx.tagId
|
|
3041
|
+
);
|
|
3042
|
+
const passesPolicies = polRes.ok;
|
|
3043
|
+
const reasons = [];
|
|
3044
|
+
if (!fitsConstraints) reasons.push("constraint_mismatch");
|
|
3045
|
+
if (!passesRate) reasons.push("rate_policy");
|
|
3046
|
+
if (!passesPolicies) reasons.push("policy_error");
|
|
3047
|
+
out.push({
|
|
3048
|
+
id,
|
|
3049
|
+
ok: fitsConstraints && passesRate && passesPolicies,
|
|
3050
|
+
fitsConstraints,
|
|
3051
|
+
passesRate,
|
|
3052
|
+
passesPolicies,
|
|
3053
|
+
policyErrors: polRes.errors.length ? polRes.errors : void 0,
|
|
3054
|
+
policyWarnings: polRes.warnings.length ? polRes.warnings : void 0,
|
|
3055
|
+
reasons,
|
|
3056
|
+
cap,
|
|
3057
|
+
rate: toFiniteNumber(cap.rate)
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
return out;
|
|
3061
|
+
}
|
|
3062
|
+
};
|
|
3063
|
+
function nextCopyLabel(old) {
|
|
3064
|
+
const m = old.match(/^(.*?)(?:\s*\(copy(?:\s+(\d+))?\))$/i);
|
|
3065
|
+
if (!m) return `${old} (copy)`;
|
|
3066
|
+
const stem = m[1].trim();
|
|
3067
|
+
const n = m[2] ? parseInt(m[2], 10) + 1 : 2;
|
|
3068
|
+
return `${stem} (copy ${n})`;
|
|
3069
|
+
}
|
|
3070
|
+
function nextCopyName(old) {
|
|
3071
|
+
if (!old) return void 0;
|
|
3072
|
+
const m = old.match(/^(.*?)(_copy(\d+)?)$/i);
|
|
3073
|
+
if (!m) return `${old}_copy`;
|
|
3074
|
+
const stem = m[1];
|
|
3075
|
+
const n = m[3] ? parseInt(m[3], 10) + 1 : 2;
|
|
3076
|
+
return `${stem}_copy${n}`;
|
|
3077
|
+
}
|
|
3078
|
+
function defaultOptionIdStrategy(old) {
|
|
3079
|
+
return nextCopyId(old);
|
|
3080
|
+
}
|
|
3081
|
+
function nextCopyId(old) {
|
|
3082
|
+
const m = old.match(/^(.*?)(?:_copy(\d+)?)$/i);
|
|
3083
|
+
if (!m) return `${old}_copy`;
|
|
3084
|
+
const stem = m[1];
|
|
3085
|
+
const n = m[2] ? parseInt(m[2], 10) + 1 : 2;
|
|
3086
|
+
return `${stem}_copy${n}`;
|
|
3087
|
+
}
|
|
3088
|
+
function bumpSuffix(old) {
|
|
3089
|
+
const m = old.match(/^(.*?)(\d+)$/);
|
|
3090
|
+
if (!m) return `${old}2`;
|
|
3091
|
+
const stem = m[1];
|
|
3092
|
+
return `${stem}${parseInt(m[2], 10) + 1}`;
|
|
3093
|
+
}
|
|
3094
|
+
function normalizeQuantityRule(input) {
|
|
3095
|
+
if (!input || typeof input !== "object") return void 0;
|
|
3096
|
+
const v = input;
|
|
3097
|
+
const vb = v.valueBy;
|
|
3098
|
+
if (vb !== "value" && vb !== "length" && vb !== "eval") return void 0;
|
|
3099
|
+
const out = { valueBy: vb };
|
|
3100
|
+
if (vb === "eval" && typeof v.code === "string" && v.code.trim()) {
|
|
3101
|
+
out.code = v.code;
|
|
3102
|
+
}
|
|
3103
|
+
return out;
|
|
3104
|
+
}
|
|
3105
|
+
function evaluateServicePolicies(rules, svcIds, svcMap, tagId) {
|
|
3106
|
+
var _a, _b, _c;
|
|
3107
|
+
const errors = [];
|
|
3108
|
+
const warnings = [];
|
|
3109
|
+
if (!rules || !rules.length) return { ok: true, errors, warnings };
|
|
3110
|
+
const relevant = rules.filter(
|
|
3111
|
+
(r) => r.subject === "services" && (r.scope === "visible_group" || r.scope === "global")
|
|
3112
|
+
);
|
|
3113
|
+
for (const r of relevant) {
|
|
3114
|
+
const ids = svcIds.filter(
|
|
3115
|
+
(id) => matchesRuleFilter(svcMap[Number(id)], r, tagId)
|
|
3116
|
+
);
|
|
3117
|
+
const projection = r.projection || "service.id";
|
|
3118
|
+
const values = ids.map(
|
|
3119
|
+
(id) => policyProjectValue(svcMap[Number(id)], projection)
|
|
3120
|
+
);
|
|
3121
|
+
let ok = true;
|
|
3122
|
+
switch (r.op) {
|
|
3123
|
+
case "all_equal":
|
|
3124
|
+
ok = values.length <= 1 || values.every((v) => v === values[0]);
|
|
3125
|
+
break;
|
|
3126
|
+
case "unique": {
|
|
3127
|
+
const uniq = new Set(values.map((v) => String(v)));
|
|
3128
|
+
ok = uniq.size === values.length;
|
|
3129
|
+
break;
|
|
3130
|
+
}
|
|
3131
|
+
case "no_mix": {
|
|
3132
|
+
const uniq = new Set(values.map((v) => String(v)));
|
|
3133
|
+
ok = uniq.size <= 1;
|
|
3134
|
+
break;
|
|
3135
|
+
}
|
|
3136
|
+
case "all_true":
|
|
3137
|
+
ok = values.every((v) => !!v);
|
|
3138
|
+
break;
|
|
3139
|
+
case "any_true":
|
|
3140
|
+
ok = values.some((v) => !!v);
|
|
3141
|
+
break;
|
|
3142
|
+
case "max_count": {
|
|
3143
|
+
const n = typeof r.value === "number" ? r.value : NaN;
|
|
3144
|
+
ok = Number.isFinite(n) ? values.length <= n : true;
|
|
3145
|
+
break;
|
|
3146
|
+
}
|
|
3147
|
+
case "min_count": {
|
|
3148
|
+
const n = typeof r.value === "number" ? r.value : NaN;
|
|
3149
|
+
ok = Number.isFinite(n) ? values.length >= n : true;
|
|
3150
|
+
break;
|
|
3151
|
+
}
|
|
3152
|
+
default:
|
|
3153
|
+
ok = true;
|
|
3154
|
+
}
|
|
3155
|
+
if (!ok) {
|
|
3156
|
+
if (((_a = r.severity) != null ? _a : "error") === "error")
|
|
3157
|
+
errors.push((_b = r.id) != null ? _b : "policy_error");
|
|
3158
|
+
else warnings.push((_c = r.id) != null ? _c : "policy_warning");
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
3162
|
+
}
|
|
3163
|
+
function policyProjectValue(cap, projection) {
|
|
3164
|
+
if (!cap) return void 0;
|
|
3165
|
+
const key = projection.startsWith("service.") ? projection.slice(8) : projection;
|
|
3166
|
+
return cap[key];
|
|
3167
|
+
}
|
|
3168
|
+
function matchesRuleFilter(cap, rule, tagId) {
|
|
3169
|
+
if (!cap) return false;
|
|
3170
|
+
const f = rule.filter;
|
|
3171
|
+
if (!f) return true;
|
|
3172
|
+
if (f.tag_id && !toStrSet(f.tag_id).has(String(tagId))) return false;
|
|
3173
|
+
return true;
|
|
3174
|
+
}
|
|
3175
|
+
function toStrSet(v) {
|
|
3176
|
+
const arr = Array.isArray(v) ? v : [v];
|
|
3177
|
+
const s = /* @__PURE__ */ new Set();
|
|
3178
|
+
for (const x of arr) s.add(String(x));
|
|
3179
|
+
return s;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
// src/react/canvas/selection.ts
|
|
3183
|
+
var isTagId2 = (id) => typeof id === "string" && id.startsWith("t:");
|
|
3184
|
+
var isOptionId2 = (id) => typeof id === "string" && id.startsWith("o:");
|
|
3185
|
+
var Selection = class {
|
|
3186
|
+
constructor(builder, opts) {
|
|
3187
|
+
this.builder = builder;
|
|
3188
|
+
this.opts = opts;
|
|
3189
|
+
this.set = /* @__PURE__ */ new Set();
|
|
3190
|
+
this.onChangeFns = [];
|
|
3191
|
+
}
|
|
3192
|
+
// ── Public mutators ──────────────────────────────────────────────────────
|
|
3193
|
+
replace(id) {
|
|
3194
|
+
if (!id) return this.clear();
|
|
3195
|
+
this.set.clear();
|
|
3196
|
+
this.set.add(id);
|
|
3197
|
+
this.primaryId = id;
|
|
3198
|
+
this.updateCurrentTagFrom(id);
|
|
3199
|
+
this.emit();
|
|
3200
|
+
}
|
|
3201
|
+
add(id) {
|
|
3202
|
+
this.set.add(id);
|
|
3203
|
+
this.primaryId = id;
|
|
3204
|
+
this.updateCurrentTagFrom(id);
|
|
3205
|
+
this.emit();
|
|
3206
|
+
}
|
|
3207
|
+
remove(id) {
|
|
3208
|
+
if (!this.set.delete(id)) return;
|
|
3209
|
+
if (this.primaryId === id) {
|
|
3210
|
+
this.primaryId = this.set.values().next().value;
|
|
3211
|
+
if (this.primaryId) this.updateCurrentTagFrom(this.primaryId);
|
|
3212
|
+
}
|
|
3213
|
+
this.emit();
|
|
3214
|
+
}
|
|
3215
|
+
toggle(id) {
|
|
3216
|
+
if (this.set.has(id)) this.remove(id);
|
|
3217
|
+
else this.add(id);
|
|
3218
|
+
}
|
|
3219
|
+
many(ids, primary) {
|
|
3220
|
+
this.set = new Set(ids);
|
|
3221
|
+
this.primaryId = primary != null ? primary : this.set.values().next().value;
|
|
3222
|
+
if (this.primaryId) this.updateCurrentTagFrom(this.primaryId);
|
|
3223
|
+
this.emit();
|
|
3224
|
+
}
|
|
3225
|
+
clear() {
|
|
3226
|
+
if (!this.set.size && !this.primaryId) return;
|
|
3227
|
+
this.set.clear();
|
|
3228
|
+
this.primaryId = void 0;
|
|
3229
|
+
this.emit();
|
|
3230
|
+
}
|
|
3231
|
+
// ── Read APIs ────────────────────────────────────────────────────────────
|
|
3232
|
+
all() {
|
|
3233
|
+
return this.set;
|
|
3234
|
+
}
|
|
3235
|
+
has(id) {
|
|
3236
|
+
return this.set.has(id);
|
|
3237
|
+
}
|
|
3238
|
+
primary() {
|
|
3239
|
+
return this.primaryId;
|
|
3240
|
+
}
|
|
3241
|
+
currentTag() {
|
|
3242
|
+
return this.currentTagId;
|
|
3243
|
+
}
|
|
3244
|
+
onChange(fn) {
|
|
3245
|
+
this.onChangeFns.push(fn);
|
|
3246
|
+
return () => {
|
|
3247
|
+
const i = this.onChangeFns.indexOf(fn);
|
|
3248
|
+
if (i >= 0) this.onChangeFns.splice(i, 1);
|
|
3249
|
+
};
|
|
3250
|
+
}
|
|
3251
|
+
// ── Main: visible group snapshot (env-aware) ─────────────────────────────
|
|
3252
|
+
visibleGroup() {
|
|
3253
|
+
var _a;
|
|
3254
|
+
const props = this.builder.getProps();
|
|
3255
|
+
if (((_a = this.opts.env) != null ? _a : "client") === "workspace") {
|
|
3256
|
+
const tagIds = Array.from(this.set).filter(isTagId2);
|
|
3257
|
+
if (tagIds.length > 1) {
|
|
3258
|
+
return { kind: "multi", groups: Array.from(this.set) };
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
const tagId = this.resolveTagContextId(props);
|
|
3262
|
+
if (!tagId)
|
|
3263
|
+
return { kind: "single", group: { fields: [], fieldIds: [] } };
|
|
3264
|
+
const group = this.computeGroupForTag(props, tagId);
|
|
3265
|
+
return { kind: "single", group };
|
|
3266
|
+
}
|
|
3267
|
+
// ── Internals ────────────────────────────────────────────────────────────
|
|
3268
|
+
emit() {
|
|
3269
|
+
const payload = {
|
|
3270
|
+
ids: Array.from(this.set),
|
|
3271
|
+
primary: this.primaryId
|
|
3272
|
+
};
|
|
3273
|
+
for (const fn of this.onChangeFns) fn(payload);
|
|
3274
|
+
}
|
|
3275
|
+
updateCurrentTagFrom(id) {
|
|
3276
|
+
var _a, _b;
|
|
3277
|
+
const props = this.builder.getProps();
|
|
3278
|
+
const tags = (_a = props.filters) != null ? _a : [];
|
|
3279
|
+
const fields = (_b = props.fields) != null ? _b : [];
|
|
3280
|
+
if (tags.some((t) => t.id === id)) {
|
|
3281
|
+
this.currentTagId = id;
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
const f = fields.find((x) => x.id === id);
|
|
3285
|
+
if (f == null ? void 0 : f.bind_id) {
|
|
3286
|
+
this.currentTagId = Array.isArray(f.bind_id) ? f.bind_id[0] : f.bind_id;
|
|
3287
|
+
return;
|
|
3288
|
+
}
|
|
3289
|
+
if (isOptionId2(id)) {
|
|
3290
|
+
const host = fields.find(
|
|
3291
|
+
(x) => {
|
|
3292
|
+
var _a2;
|
|
3293
|
+
return ((_a2 = x.options) != null ? _a2 : []).some((o) => o.id === id);
|
|
3294
|
+
}
|
|
3295
|
+
);
|
|
3296
|
+
if (host == null ? void 0 : host.bind_id) {
|
|
3297
|
+
this.currentTagId = Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
|
|
3298
|
+
return;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
if (id.includes("::")) {
|
|
3302
|
+
const [fid] = id.split("::");
|
|
3303
|
+
const host = fields.find((x) => x.id === fid);
|
|
3304
|
+
if (host == null ? void 0 : host.bind_id) {
|
|
3305
|
+
this.currentTagId = Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
resolveTagContextId(props) {
|
|
3311
|
+
var _a;
|
|
3312
|
+
if (this.currentTagId) return this.currentTagId;
|
|
3313
|
+
for (const id of this.set) if (isTagId2(id)) return id;
|
|
3314
|
+
const fields = (_a = props.fields) != null ? _a : [];
|
|
3315
|
+
for (const id of this.set) {
|
|
3316
|
+
const f = fields.find((x) => x.id === id);
|
|
3317
|
+
if (f == null ? void 0 : f.bind_id)
|
|
3318
|
+
return Array.isArray(f.bind_id) ? f.bind_id[0] : f.bind_id;
|
|
3319
|
+
}
|
|
3320
|
+
for (const id of this.set) {
|
|
3321
|
+
if (isOptionId2(id)) {
|
|
3322
|
+
const host = fields.find(
|
|
3323
|
+
(x) => {
|
|
3324
|
+
var _a2;
|
|
3325
|
+
return ((_a2 = x.options) != null ? _a2 : []).some((o) => o.id === id);
|
|
3326
|
+
}
|
|
3327
|
+
);
|
|
3328
|
+
if (host == null ? void 0 : host.bind_id)
|
|
3329
|
+
return Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
|
|
3330
|
+
}
|
|
3331
|
+
if (id.includes("::")) {
|
|
3332
|
+
const [fid] = id.split("::");
|
|
3333
|
+
const host = fields.find((x) => x.id === fid);
|
|
3334
|
+
if (host == null ? void 0 : host.bind_id)
|
|
3335
|
+
return Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
return this.opts.rootTagId;
|
|
3339
|
+
}
|
|
3340
|
+
selectedButtonTriggerIds(props) {
|
|
3341
|
+
var _a;
|
|
3342
|
+
const fields = (_a = props.fields) != null ? _a : [];
|
|
3343
|
+
const fieldById = new Map(fields.map((f) => [f.id, f]));
|
|
3344
|
+
const out = [];
|
|
3345
|
+
for (const selId of this.set) {
|
|
3346
|
+
if (selId.startsWith("o:")) {
|
|
3347
|
+
out.push(selId);
|
|
3348
|
+
continue;
|
|
3349
|
+
}
|
|
3350
|
+
const f = fieldById.get(selId);
|
|
3351
|
+
if ((f == null ? void 0 : f.button) === true) out.push(selId);
|
|
3352
|
+
}
|
|
3353
|
+
return out;
|
|
3354
|
+
}
|
|
3355
|
+
computeGroupForTag(props, tagId) {
|
|
3356
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
3357
|
+
const tags = (_a = props.filters) != null ? _a : [];
|
|
3358
|
+
const fields = (_b = props.fields) != null ? _b : [];
|
|
3359
|
+
const tagById = new Map(tags.map((t) => [t.id, t]));
|
|
3360
|
+
const tag = tagById.get(tagId);
|
|
3361
|
+
const selectedTriggerIds = this.selectedButtonTriggerIds(props);
|
|
3362
|
+
const fieldIds = this.builder.visibleFields(tagId, selectedTriggerIds);
|
|
3363
|
+
const fieldById = new Map(fields.map((f) => [f.id, f]));
|
|
3364
|
+
const visible = fieldIds.map((id) => fieldById.get(id)).filter(Boolean);
|
|
3365
|
+
const parentTags = [];
|
|
3366
|
+
let cur = tag == null ? void 0 : tag.bind_id;
|
|
3367
|
+
const guard = /* @__PURE__ */ new Set();
|
|
3368
|
+
while (cur && !guard.has(cur)) {
|
|
3369
|
+
const t = tagById.get(cur);
|
|
3370
|
+
if (!t) break;
|
|
3371
|
+
parentTags.push(t);
|
|
3372
|
+
guard.add(cur);
|
|
3373
|
+
cur = t.bind_id;
|
|
3374
|
+
}
|
|
3375
|
+
const childrenTags = tags.filter((t) => t.bind_id === tagId);
|
|
3376
|
+
const services = [];
|
|
3377
|
+
const resolve = this.opts.resolveService;
|
|
3378
|
+
let baseAddedFromTag = false;
|
|
3379
|
+
if ((tag == null ? void 0 : tag.service_id) != null) {
|
|
3380
|
+
services.push(
|
|
3381
|
+
(_c = resolve == null ? void 0 : resolve(tag.service_id)) != null ? _c : { id: tag.service_id }
|
|
3382
|
+
);
|
|
3383
|
+
baseAddedFromTag = true;
|
|
3384
|
+
}
|
|
3385
|
+
let baseOverridden = false;
|
|
3386
|
+
for (const selId of this.set) {
|
|
3387
|
+
const opt = this.findOptionById(fields, selId);
|
|
3388
|
+
if ((opt == null ? void 0 : opt.service_id) != null) {
|
|
3389
|
+
const role = (_d = opt.pricing_role) != null ? _d : "base";
|
|
3390
|
+
const cap = (_e = resolve == null ? void 0 : resolve(opt.service_id)) != null ? _e : { id: opt.service_id };
|
|
3391
|
+
baseOverridden = this.addServiceByRole(
|
|
3392
|
+
services,
|
|
3393
|
+
cap,
|
|
3394
|
+
role,
|
|
3395
|
+
baseAddedFromTag,
|
|
3396
|
+
baseOverridden
|
|
3397
|
+
);
|
|
3398
|
+
continue;
|
|
3399
|
+
}
|
|
3400
|
+
const f = fieldById.get(selId);
|
|
3401
|
+
if ((f == null ? void 0 : f.button) === true && f.service_id != null) {
|
|
3402
|
+
const role = (_f = f.pricing_role) != null ? _f : "base";
|
|
3403
|
+
const cap = (_g = resolve == null ? void 0 : resolve(f.service_id)) != null ? _g : { id: f.service_id };
|
|
3404
|
+
baseOverridden = this.addServiceByRole(
|
|
3405
|
+
services,
|
|
3406
|
+
cap,
|
|
3407
|
+
role,
|
|
3408
|
+
baseAddedFromTag,
|
|
3409
|
+
baseOverridden
|
|
3410
|
+
);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
return {
|
|
3414
|
+
tagId,
|
|
3415
|
+
tag,
|
|
3416
|
+
fields: visible,
|
|
3417
|
+
fieldIds,
|
|
3418
|
+
parentTags,
|
|
3419
|
+
childrenTags,
|
|
3420
|
+
services
|
|
3421
|
+
};
|
|
3422
|
+
}
|
|
3423
|
+
addServiceByRole(services, cap, role, baseAddedFromTag, baseOverridden) {
|
|
3424
|
+
if (role === "base") {
|
|
3425
|
+
if (!baseOverridden) {
|
|
3426
|
+
if (baseAddedFromTag && services.length > 0) {
|
|
3427
|
+
services[0] = cap;
|
|
3428
|
+
} else {
|
|
3429
|
+
services.unshift(cap);
|
|
3430
|
+
}
|
|
3431
|
+
return true;
|
|
3432
|
+
} else {
|
|
3433
|
+
services.push(cap);
|
|
3434
|
+
}
|
|
3435
|
+
} else {
|
|
3436
|
+
services.push(cap);
|
|
3437
|
+
}
|
|
3438
|
+
return baseOverridden;
|
|
3439
|
+
}
|
|
3440
|
+
findOptionById(fields, selId) {
|
|
3441
|
+
var _a, _b;
|
|
3442
|
+
if (isOptionId2(selId)) {
|
|
3443
|
+
for (const f of fields) {
|
|
3444
|
+
const o = (_a = f.options) == null ? void 0 : _a.find((x) => x.id === selId);
|
|
3445
|
+
if (o) return o;
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
if (selId.includes("::")) {
|
|
3449
|
+
const [fid, oid] = selId.split("::");
|
|
3450
|
+
const f = fields.find((x) => x.id === fid);
|
|
3451
|
+
const o = (_b = f == null ? void 0 : f.options) == null ? void 0 : _b.find((x) => x.id === oid || x.id === selId);
|
|
3452
|
+
if (o) return o;
|
|
3453
|
+
}
|
|
3454
|
+
return void 0;
|
|
3455
|
+
}
|
|
3456
|
+
};
|
|
3457
|
+
|
|
3458
|
+
// src/react/canvas/api.ts
|
|
3459
|
+
var CanvasAPI = class {
|
|
3460
|
+
constructor(builder, opts = {}) {
|
|
3461
|
+
this.bus = new EventBus();
|
|
3462
|
+
/* ─── Events ─────────────────────────────────────────────── */
|
|
3463
|
+
this.on = this.bus.on.bind(this.bus);
|
|
3464
|
+
this.once = this.bus.once.bind(this.bus);
|
|
3465
|
+
this.edgeRel = "bind";
|
|
3466
|
+
/* ─── Option-node visibility (per field) ───────────────────────────────── */
|
|
3467
|
+
/** Internal mirror of which fields should show their options as nodes. */
|
|
3468
|
+
this.shownOptionFields = /* @__PURE__ */ new Set();
|
|
3469
|
+
var _a, _b, _c;
|
|
3470
|
+
this.builder = builder;
|
|
3471
|
+
this.autoEmit = (_a = opts.autoEmitState) != null ? _a : true;
|
|
3472
|
+
this.selection = new Selection(builder, {
|
|
3473
|
+
env: "workspace",
|
|
3474
|
+
rootTagId: "t:root"
|
|
3475
|
+
});
|
|
3476
|
+
const graph = builder.tree();
|
|
3477
|
+
this.state = {
|
|
3478
|
+
graph,
|
|
3479
|
+
positions: {},
|
|
3480
|
+
selection: /* @__PURE__ */ new Set(),
|
|
3481
|
+
highlighted: /* @__PURE__ */ new Set(),
|
|
3482
|
+
viewport: { x: 0, y: 0, zoom: 1, ...opts.initialViewport },
|
|
3483
|
+
version: 1
|
|
3484
|
+
};
|
|
3485
|
+
this.selection.onChange(({ ids }) => {
|
|
3486
|
+
this.state.selection = new Set(ids);
|
|
3487
|
+
this.bump();
|
|
3488
|
+
this.bus.emit("selection:change", { ids });
|
|
3489
|
+
if (this.autoEmit) this.bus.emit("state:change", this.snapshot());
|
|
3490
|
+
});
|
|
3491
|
+
const scopeProvider = (_b = opts.getScope) != null ? _b : (() => {
|
|
3492
|
+
const anyOpts = opts;
|
|
3493
|
+
if (!anyOpts.workspaceId || !anyOpts.actorId || !anyOpts.branchId) {
|
|
3494
|
+
return void 0;
|
|
3495
|
+
}
|
|
3496
|
+
return {
|
|
3497
|
+
workspaceId: anyOpts.workspaceId,
|
|
3498
|
+
actorId: anyOpts.actorId,
|
|
3499
|
+
branchId: anyOpts.branchId
|
|
3500
|
+
};
|
|
3501
|
+
});
|
|
3502
|
+
this.comments = new CommentsAPI(this.bus, {
|
|
3503
|
+
backend: (_c = opts.backend) == null ? void 0 : _c.comments,
|
|
3504
|
+
getScope: scopeProvider
|
|
3505
|
+
});
|
|
3506
|
+
this.editor = new Editor(builder, this, {
|
|
3507
|
+
serviceMap: builder.getServiceMap(),
|
|
3508
|
+
serviceExists: (id) => builder.getServiceMap().hasOwnProperty(id),
|
|
3509
|
+
...opts
|
|
3510
|
+
});
|
|
3511
|
+
if (this.autoEmit) this.bus.emit("state:change", this.snapshot());
|
|
3512
|
+
}
|
|
3513
|
+
emit(event, payload) {
|
|
3514
|
+
this.bus.emit(event, payload);
|
|
3515
|
+
}
|
|
3516
|
+
/* ─── State accessors ───────────────────────────────────── */
|
|
3517
|
+
snapshot() {
|
|
3518
|
+
return {
|
|
3519
|
+
...this.state,
|
|
3520
|
+
selection: new Set(this.state.selection),
|
|
3521
|
+
highlighted: new Set(this.state.highlighted),
|
|
3522
|
+
graph: {
|
|
3523
|
+
nodes: [...this.state.graph.nodes],
|
|
3524
|
+
edges: [...this.state.graph.edges]
|
|
3525
|
+
},
|
|
3526
|
+
positions: { ...this.state.positions }
|
|
3527
|
+
};
|
|
3528
|
+
}
|
|
3529
|
+
getGraph() {
|
|
3530
|
+
return this.state.graph;
|
|
3531
|
+
}
|
|
3532
|
+
getSelection() {
|
|
3533
|
+
return Array.from(this.state.selection);
|
|
3534
|
+
}
|
|
3535
|
+
getViewport() {
|
|
3536
|
+
return { ...this.state.viewport };
|
|
3537
|
+
}
|
|
3538
|
+
/* ─── Graph lifecycle ───────────────────────────────────── */
|
|
3539
|
+
refreshGraph() {
|
|
3540
|
+
this.state.graph = this.builder.tree();
|
|
3541
|
+
this.bump();
|
|
3542
|
+
this.bus.emit("graph:update", this.state.graph);
|
|
3543
|
+
}
|
|
3544
|
+
setPositions(pos) {
|
|
3545
|
+
this.state.positions = { ...this.state.positions, ...pos };
|
|
3546
|
+
this.bump();
|
|
3547
|
+
}
|
|
3548
|
+
setPosition(id, x, y) {
|
|
3549
|
+
this.state.positions[id] = { x, y };
|
|
3550
|
+
this.bump();
|
|
3551
|
+
}
|
|
3552
|
+
/* ─── Selection ─────────────────────────────────────────── */
|
|
3553
|
+
select(ids) {
|
|
3554
|
+
this.selection.many(ids);
|
|
3555
|
+
}
|
|
3556
|
+
addToSelection(ids) {
|
|
3557
|
+
const next = new Set(this.selection.all());
|
|
3558
|
+
let primary;
|
|
3559
|
+
for (const id of ids) {
|
|
3560
|
+
next.add(id);
|
|
3561
|
+
primary = id;
|
|
3562
|
+
}
|
|
3563
|
+
this.selection.many(next, primary);
|
|
3564
|
+
}
|
|
3565
|
+
toggleSelection(id) {
|
|
3566
|
+
this.selection.toggle(id);
|
|
3567
|
+
}
|
|
3568
|
+
clearSelection() {
|
|
3569
|
+
this.selection.clear();
|
|
3570
|
+
}
|
|
3571
|
+
selectComments(threadId) {
|
|
3572
|
+
this.bus.emit("comment:select", { threadId });
|
|
3573
|
+
}
|
|
3574
|
+
/* ─── Highlight / Hover ─────────────────────────────────── */
|
|
3575
|
+
setHighlighted(ids) {
|
|
3576
|
+
this.state.highlighted = new Set(ids);
|
|
3577
|
+
this.bump();
|
|
3578
|
+
}
|
|
3579
|
+
setHover(id) {
|
|
3580
|
+
this.state.hoverId = id;
|
|
3581
|
+
this.bump();
|
|
3582
|
+
this.bus.emit("hover:change", { id });
|
|
3583
|
+
}
|
|
3584
|
+
/* ─── Viewport ──────────────────────────────────────────── */
|
|
3585
|
+
setViewport(v) {
|
|
3586
|
+
this.state.viewport = { ...this.state.viewport, ...v };
|
|
3587
|
+
this.bump();
|
|
3588
|
+
this.bus.emit("viewport:change", this.getViewport());
|
|
3589
|
+
}
|
|
3590
|
+
/* ─── Wiring draft (for bind/include/exclude UX) ────────── */
|
|
3591
|
+
startWire(from, kind) {
|
|
3592
|
+
this.state.draftWire = { from, kind };
|
|
3593
|
+
this.bump();
|
|
3594
|
+
this.bus.emit("wire:preview", { from, kind });
|
|
3595
|
+
}
|
|
3596
|
+
previewWire(to) {
|
|
3597
|
+
const dw = this.state.draftWire;
|
|
3598
|
+
if (!dw) return;
|
|
3599
|
+
this.bus.emit("wire:preview", { from: dw.from, to, kind: dw.kind });
|
|
3600
|
+
}
|
|
3601
|
+
commitWire(to) {
|
|
3602
|
+
const dw = this.state.draftWire;
|
|
3603
|
+
if (!dw) return;
|
|
3604
|
+
this.bus.emit("wire:commit", { from: dw.from, to, kind: dw.kind });
|
|
3605
|
+
this.state.draftWire = void 0;
|
|
3606
|
+
this.bump();
|
|
3607
|
+
}
|
|
3608
|
+
cancelWire() {
|
|
3609
|
+
const dw = this.state.draftWire;
|
|
3610
|
+
if (!dw) return;
|
|
3611
|
+
this.bus.emit("wire:cancel", { from: dw.from });
|
|
3612
|
+
this.state.draftWire = void 0;
|
|
3613
|
+
this.bump();
|
|
3614
|
+
}
|
|
3615
|
+
/* ─── Utilities ─────────────────────────────────────────── */
|
|
3616
|
+
bump() {
|
|
3617
|
+
this.state.version++;
|
|
3618
|
+
if (this.autoEmit) this.bus.emit("state:change", this.snapshot());
|
|
3619
|
+
}
|
|
3620
|
+
dispose() {
|
|
3621
|
+
this.bus.clear();
|
|
3622
|
+
}
|
|
3623
|
+
undo() {
|
|
3624
|
+
this.builder.undo();
|
|
3625
|
+
this.refreshGraph();
|
|
3626
|
+
}
|
|
3627
|
+
getEdgeRel() {
|
|
3628
|
+
return this.edgeRel;
|
|
3629
|
+
}
|
|
3630
|
+
setEdgeRel(rel) {
|
|
3631
|
+
if (this.edgeRel === rel) return;
|
|
3632
|
+
this.edgeRel = rel;
|
|
3633
|
+
this.bus.emit("edge:change", rel);
|
|
3634
|
+
}
|
|
3635
|
+
/** Return the field ids whose options are currently set to be visible as nodes. */
|
|
3636
|
+
getShownOptionFields() {
|
|
3637
|
+
return Array.from(this.shownOptionFields);
|
|
3638
|
+
}
|
|
3639
|
+
/** True if this field’s options are shown as nodes. */
|
|
3640
|
+
isFieldOptionsShown(fieldId) {
|
|
3641
|
+
return this.shownOptionFields.has(String(fieldId));
|
|
3642
|
+
}
|
|
3643
|
+
/**
|
|
3644
|
+
* Set visibility of option nodes for a field, then rebuild the graph.
|
|
3645
|
+
* When shown = true, the Builder will emit option nodes for this field.
|
|
3646
|
+
*/
|
|
3647
|
+
setFieldOptionsShown(fieldId, shown) {
|
|
3648
|
+
const id = String(fieldId);
|
|
3649
|
+
const before = this.shownOptionFields.has(id);
|
|
3650
|
+
if (shown && !before) this.shownOptionFields.add(id);
|
|
3651
|
+
else if (!shown && before) this.shownOptionFields.delete(id);
|
|
3652
|
+
else return;
|
|
3653
|
+
this.builder.setOptions({
|
|
3654
|
+
showOptionNodes: new Set(this.shownOptionFields)
|
|
3655
|
+
});
|
|
3656
|
+
this.refreshGraph();
|
|
3657
|
+
}
|
|
3658
|
+
/** Toggle option-node visibility for a field. Returns the new visibility. */
|
|
3659
|
+
toggleFieldOptions(fieldId) {
|
|
3660
|
+
const next = !this.isFieldOptionsShown(fieldId);
|
|
3661
|
+
this.setFieldOptionsShown(fieldId, next);
|
|
3662
|
+
return next;
|
|
3663
|
+
}
|
|
3664
|
+
/**
|
|
3665
|
+
* Replace the whole set of fields whose options are visible as nodes.
|
|
3666
|
+
* Useful for restoring a saved UI state.
|
|
3667
|
+
*/
|
|
3668
|
+
setShownOptionFields(ids) {
|
|
3669
|
+
const next = new Set(Array.from(ids, String));
|
|
3670
|
+
if (next.size === this.shownOptionFields.size && Array.from(next).every((id) => this.shownOptionFields.has(id))) {
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3673
|
+
this.shownOptionFields = next;
|
|
3674
|
+
this.builder.setOptions({
|
|
3675
|
+
showOptionNodes: new Set(this.shownOptionFields)
|
|
3676
|
+
});
|
|
3677
|
+
this.refreshGraph();
|
|
3678
|
+
}
|
|
3679
|
+
getConstraints() {
|
|
3680
|
+
return this.builder.getConstraints();
|
|
3681
|
+
}
|
|
3682
|
+
getServiceProps() {
|
|
3683
|
+
return this.builder.getProps();
|
|
3684
|
+
}
|
|
3685
|
+
};
|
|
3686
|
+
|
|
3687
|
+
// src/react/inputs/form-context.tsx
|
|
3688
|
+
var import_react2 = require("react");
|
|
3689
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
3690
|
+
var FormCtx = (0, import_react2.createContext)(null);
|
|
3691
|
+
function FormProvider({
|
|
3692
|
+
initial,
|
|
3693
|
+
children
|
|
3694
|
+
}) {
|
|
3695
|
+
var _a, _b;
|
|
3696
|
+
const [values, setValues] = (0, import_react2.useState)(
|
|
3697
|
+
(_a = initial == null ? void 0 : initial.values) != null ? _a : {}
|
|
3698
|
+
);
|
|
3699
|
+
const [selections, setSelections] = (0, import_react2.useState)(
|
|
3700
|
+
(_b = initial == null ? void 0 : initial.selections) != null ? _b : {}
|
|
3701
|
+
);
|
|
3702
|
+
const subsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
3703
|
+
const publish = (0, import_react2.useCallback)(() => {
|
|
3704
|
+
for (const fn of Array.from(subsRef.current)) {
|
|
3705
|
+
try {
|
|
3706
|
+
fn();
|
|
3707
|
+
} catch {
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
}, []);
|
|
3711
|
+
const api = (0, import_react2.useMemo)(
|
|
3712
|
+
() => ({
|
|
3713
|
+
get: (fieldId) => values[fieldId],
|
|
3714
|
+
set: (fieldId, value) => {
|
|
3715
|
+
setValues((prev) => {
|
|
3716
|
+
if (prev[fieldId] === value) return prev;
|
|
3717
|
+
const next = { ...prev, [fieldId]: value };
|
|
3718
|
+
return next;
|
|
3719
|
+
});
|
|
3720
|
+
publish();
|
|
3721
|
+
},
|
|
3722
|
+
getSelections: (fieldId) => {
|
|
3723
|
+
var _a2;
|
|
3724
|
+
return (_a2 = selections[fieldId]) != null ? _a2 : [];
|
|
3725
|
+
},
|
|
3726
|
+
setSelections: (fieldId, optionIds) => {
|
|
3727
|
+
setSelections((prev) => {
|
|
3728
|
+
const next = {
|
|
3729
|
+
...prev,
|
|
3730
|
+
[fieldId]: Array.from(new Set(optionIds))
|
|
3731
|
+
};
|
|
3732
|
+
return next;
|
|
3733
|
+
});
|
|
3734
|
+
publish();
|
|
3735
|
+
},
|
|
3736
|
+
toggleSelection: (fieldId, optionId) => {
|
|
3737
|
+
setSelections((prev) => {
|
|
3738
|
+
var _a2;
|
|
3739
|
+
const cur = new Set((_a2 = prev[fieldId]) != null ? _a2 : []);
|
|
3740
|
+
if (cur.has(optionId)) cur.delete(optionId);
|
|
3741
|
+
else cur.add(optionId);
|
|
3742
|
+
return { ...prev, [fieldId]: Array.from(cur) };
|
|
3743
|
+
});
|
|
3744
|
+
publish();
|
|
3745
|
+
},
|
|
3746
|
+
snapshot: () => ({
|
|
3747
|
+
values: { ...values },
|
|
3748
|
+
selections: { ...selections }
|
|
3749
|
+
}),
|
|
3750
|
+
subscribe: (fn) => {
|
|
3751
|
+
subsRef.current.add(fn);
|
|
3752
|
+
return () => subsRef.current.delete(fn);
|
|
3753
|
+
}
|
|
3754
|
+
}),
|
|
3755
|
+
[publish, selections, values]
|
|
3756
|
+
);
|
|
3757
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormCtx.Provider, { value: api, children });
|
|
3758
|
+
}
|
|
3759
|
+
function useFormApi() {
|
|
3760
|
+
const ctx = (0, import_react2.useContext)(FormCtx);
|
|
3761
|
+
if (!ctx) throw new Error("useFormApi must be used within <FormProvider>");
|
|
3762
|
+
return ctx;
|
|
3763
|
+
}
|
|
3764
|
+
function useOptionalFormApi() {
|
|
3765
|
+
return (0, import_react2.useContext)(FormCtx);
|
|
3766
|
+
}
|
|
3767
|
+
function useFormField(fieldId) {
|
|
3768
|
+
const api = useFormApi();
|
|
3769
|
+
const value = api.get(fieldId);
|
|
3770
|
+
const set = (v) => api.set(fieldId, v);
|
|
3771
|
+
return { value, set };
|
|
3772
|
+
}
|
|
3773
|
+
function useFormSelections(fieldId) {
|
|
3774
|
+
const api = useFormApi();
|
|
3775
|
+
return {
|
|
3776
|
+
selected: api.getSelections(fieldId),
|
|
3777
|
+
set: (arr) => api.setSelections(fieldId, arr),
|
|
3778
|
+
toggle: (oid) => api.toggleSelection(fieldId, oid)
|
|
3779
|
+
};
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3782
|
+
// src/react/inputs/registry.ts
|
|
3783
|
+
function createInputRegistry() {
|
|
3784
|
+
const store = /* @__PURE__ */ new Map();
|
|
3785
|
+
const get = (kind, variant) => {
|
|
3786
|
+
var _a;
|
|
3787
|
+
const vm = store.get(kind);
|
|
3788
|
+
if (!vm) return void 0;
|
|
3789
|
+
const v = variant != null ? variant : "default";
|
|
3790
|
+
return (_a = vm.get(v)) != null ? _a : vm.get("default");
|
|
3791
|
+
};
|
|
3792
|
+
const register = (kind, descriptor, variant) => {
|
|
3793
|
+
let vm = store.get(kind);
|
|
3794
|
+
if (!vm) {
|
|
3795
|
+
vm = /* @__PURE__ */ new Map();
|
|
3796
|
+
store.set(kind, vm);
|
|
3797
|
+
}
|
|
3798
|
+
vm.set(variant != null ? variant : "default", descriptor);
|
|
3799
|
+
};
|
|
3800
|
+
const unregister = (kind, variant) => {
|
|
3801
|
+
const vm = store.get(kind);
|
|
3802
|
+
if (!vm) return;
|
|
3803
|
+
const key = variant != null ? variant : "default";
|
|
3804
|
+
vm.delete(key);
|
|
3805
|
+
if (vm.size === 0) store.delete(kind);
|
|
3806
|
+
};
|
|
3807
|
+
const registerMany = (entries) => {
|
|
3808
|
+
for (const e of entries) register(e.kind, e.descriptor, e.variant);
|
|
3809
|
+
};
|
|
3810
|
+
return { get, register, unregister, registerMany, _store: store };
|
|
3811
|
+
}
|
|
3812
|
+
function resolveInputDescriptor(registry, kind, variant) {
|
|
3813
|
+
return registry.get(kind, variant);
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
// src/react/inputs/provider.tsx
|
|
3817
|
+
var import_react3 = require("react");
|
|
3818
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
3819
|
+
var Ctx = (0, import_react3.createContext)(null);
|
|
3820
|
+
function Provider({
|
|
3821
|
+
children,
|
|
3822
|
+
initialRegistry
|
|
3823
|
+
}) {
|
|
3824
|
+
const registry = (0, import_react3.useMemo)(
|
|
3825
|
+
() => initialRegistry != null ? initialRegistry : createInputRegistry(),
|
|
3826
|
+
[initialRegistry]
|
|
3827
|
+
);
|
|
3828
|
+
const value = (0, import_react3.useMemo)(
|
|
3829
|
+
() => ({
|
|
3830
|
+
registry,
|
|
3831
|
+
register: registry.register,
|
|
3832
|
+
unregister: registry.unregister,
|
|
3833
|
+
registerMany: registry.registerMany
|
|
3834
|
+
}),
|
|
3835
|
+
[registry]
|
|
3836
|
+
);
|
|
3837
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Ctx.Provider, { value, children });
|
|
3838
|
+
}
|
|
3839
|
+
function useInputs() {
|
|
3840
|
+
const v = (0, import_react3.useContext)(Ctx);
|
|
3841
|
+
if (!v) throw new Error("useInputs() must be used within <InputsProvider>");
|
|
3842
|
+
return v;
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
// src/react/inputs/wrapper.tsx
|
|
3846
|
+
var import_react5 = require("react");
|
|
3847
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
3848
|
+
function toKind(field) {
|
|
3849
|
+
var _a;
|
|
3850
|
+
if (field.type === "custom") {
|
|
3851
|
+
const comp = ((_a = field.component) != null ? _a : "").trim();
|
|
3852
|
+
return `custom:${comp}`;
|
|
3853
|
+
}
|
|
3854
|
+
return field.type;
|
|
3855
|
+
}
|
|
3856
|
+
function toVariant(field) {
|
|
3857
|
+
var _a;
|
|
3858
|
+
const v = (_a = field.meta) == null ? void 0 : _a.variant;
|
|
3859
|
+
return typeof v === "string" && v.trim() ? v : void 0;
|
|
3860
|
+
}
|
|
3861
|
+
function Wrapper({
|
|
3862
|
+
field,
|
|
3863
|
+
disabled,
|
|
3864
|
+
extraProps
|
|
3865
|
+
}) {
|
|
3866
|
+
var _a, _b, _c;
|
|
3867
|
+
const { registry } = useInputs();
|
|
3868
|
+
const form = useOptionalFormApi();
|
|
3869
|
+
const kind = toKind(field);
|
|
3870
|
+
const variant = toVariant(field);
|
|
3871
|
+
const descriptor = (0, import_react5.useMemo)(
|
|
3872
|
+
() => resolveInputDescriptor(registry, kind, variant),
|
|
3873
|
+
[kind, registry, variant]
|
|
3874
|
+
);
|
|
3875
|
+
if (!descriptor) {
|
|
3876
|
+
console.warn("[InputWrapper] No descriptor for", {
|
|
3877
|
+
kind,
|
|
3878
|
+
variant,
|
|
3879
|
+
field
|
|
3880
|
+
});
|
|
3881
|
+
return null;
|
|
3882
|
+
}
|
|
3883
|
+
const { Component, adapter, defaultProps } = descriptor;
|
|
3884
|
+
const valueProp = (_a = adapter == null ? void 0 : adapter.valueProp) != null ? _a : "value";
|
|
3885
|
+
const changeProp = (_b = adapter == null ? void 0 : adapter.changeProp) != null ? _b : "onChange";
|
|
3886
|
+
const isOptionBased = Array.isArray(field.options) && field.options.length > 0;
|
|
3887
|
+
const multi = !!(isOptionBased && isMultiField(field));
|
|
3888
|
+
const isButton = field.button === true || isOptionBased;
|
|
3889
|
+
const optionById = (0, import_react5.useMemo)(() => {
|
|
3890
|
+
var _a2;
|
|
3891
|
+
if (!isOptionBased) return /* @__PURE__ */ new Map();
|
|
3892
|
+
return new Map(((_a2 = field.options) != null ? _a2 : []).map((o) => [o.id, o]));
|
|
3893
|
+
}, [isOptionBased, field.options]);
|
|
3894
|
+
const enrich = (bv) => {
|
|
3895
|
+
var _a2, _b2, _c2;
|
|
3896
|
+
if (isOptionBased) {
|
|
3897
|
+
const opt = optionById.get(bv.id);
|
|
3898
|
+
if (opt) {
|
|
3899
|
+
const role2 = (_a2 = opt.pricing_role) != null ? _a2 : "base";
|
|
3900
|
+
const sid2 = opt.service_id;
|
|
3901
|
+
const meta2 = (_b2 = opt.meta) != null ? _b2 : field.meta;
|
|
3902
|
+
return {
|
|
3903
|
+
...bv,
|
|
3904
|
+
pricing_role: role2,
|
|
3905
|
+
service_id: role2 === "utility" ? void 0 : sid2,
|
|
3906
|
+
...meta2 ? { meta: meta2 } : {}
|
|
3907
|
+
};
|
|
3908
|
+
}
|
|
3909
|
+
return bv;
|
|
3910
|
+
}
|
|
3911
|
+
const role = (_c2 = field.pricing_role) != null ? _c2 : "base";
|
|
3912
|
+
const sid = field.service_id;
|
|
3913
|
+
const meta = field.meta;
|
|
3914
|
+
return {
|
|
3915
|
+
...bv,
|
|
3916
|
+
pricing_role: role,
|
|
3917
|
+
service_id: role === "utility" ? void 0 : sid,
|
|
3918
|
+
...meta ? { meta } : {}
|
|
3919
|
+
};
|
|
3920
|
+
};
|
|
3921
|
+
function normalizeToButtonValues(input) {
|
|
3922
|
+
const coerceOne = (v) => {
|
|
3923
|
+
if (v && typeof v === "object" && "id" in v) {
|
|
3924
|
+
const id = String(v.id);
|
|
3925
|
+
const valueRaw = v.value;
|
|
3926
|
+
const value = typeof valueRaw === "number" || typeof valueRaw === "string" ? valueRaw : 1;
|
|
3927
|
+
return enrich({ id, value });
|
|
3928
|
+
}
|
|
3929
|
+
if (typeof v === "string" || typeof v === "number") {
|
|
3930
|
+
return enrich({ id: String(v), value: 1 });
|
|
3931
|
+
}
|
|
3932
|
+
return null;
|
|
3933
|
+
};
|
|
3934
|
+
if (Array.isArray(input)) {
|
|
3935
|
+
const arr = [];
|
|
3936
|
+
for (const x of input) {
|
|
3937
|
+
const one2 = coerceOne(x);
|
|
3938
|
+
if (one2) arr.push(one2);
|
|
3939
|
+
}
|
|
3940
|
+
return arr;
|
|
3941
|
+
}
|
|
3942
|
+
const one = coerceOne(input);
|
|
3943
|
+
return one ? [one] : [];
|
|
3944
|
+
}
|
|
3945
|
+
let current = void 0;
|
|
3946
|
+
let onChange = void 0;
|
|
3947
|
+
if (form) {
|
|
3948
|
+
if (isButton) {
|
|
3949
|
+
if (isOptionBased) {
|
|
3950
|
+
const selIds = form.getSelections(field.id);
|
|
3951
|
+
current = multi ? selIds : (_c = selIds[0]) != null ? _c : null;
|
|
3952
|
+
onChange = (next) => {
|
|
3953
|
+
var _a2;
|
|
3954
|
+
const normalized = (adapter == null ? void 0 : adapter.getValue) ? adapter.getValue(next, current) : next;
|
|
3955
|
+
const bvs = normalizeToButtonValues(normalized);
|
|
3956
|
+
const ids = bvs.map((b) => b.id);
|
|
3957
|
+
form.setSelections(field.id, Array.from(new Set(ids)));
|
|
3958
|
+
const values = multi ? bvs : (_a2 = bvs[0]) != null ? _a2 : null;
|
|
3959
|
+
form.set(field.id, values);
|
|
3960
|
+
};
|
|
3961
|
+
} else {
|
|
3962
|
+
const val = form.get(field.id);
|
|
3963
|
+
current = val;
|
|
3964
|
+
onChange = (next) => {
|
|
3965
|
+
const normalized = (adapter == null ? void 0 : adapter.getValue) ? adapter.getValue(next, current) : next;
|
|
3966
|
+
const bvs = normalizeToButtonValues(normalized);
|
|
3967
|
+
const first = bvs[0];
|
|
3968
|
+
const active = first && (typeof first.value === "number" ? first.value !== 0 : String(first.value).length > 0);
|
|
3969
|
+
if (active) {
|
|
3970
|
+
form.setSelections(field.id, [field.id]);
|
|
3971
|
+
form.set(field.id, first.value);
|
|
3972
|
+
} else {
|
|
3973
|
+
form.setSelections(field.id, []);
|
|
3974
|
+
form.set(field.id, null);
|
|
3975
|
+
}
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
} else {
|
|
3979
|
+
current = form.get(field.id);
|
|
3980
|
+
onChange = (next) => {
|
|
3981
|
+
const normalized = (adapter == null ? void 0 : adapter.getValue) ? adapter.getValue(next, current) : next;
|
|
3982
|
+
form.set(field.id, normalized);
|
|
3983
|
+
};
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
const hostProps = {
|
|
3987
|
+
id: field.id,
|
|
3988
|
+
field,
|
|
3989
|
+
disabled: !!disabled,
|
|
3990
|
+
...defaultProps != null ? defaultProps : {},
|
|
3991
|
+
...extraProps != null ? extraProps : {},
|
|
3992
|
+
...isOptionBased ? { options: field.options } : {}
|
|
3993
|
+
};
|
|
3994
|
+
if (form) {
|
|
3995
|
+
hostProps[valueProp] = current;
|
|
3996
|
+
hostProps[changeProp] = onChange;
|
|
3997
|
+
}
|
|
3998
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { ...hostProps });
|
|
3999
|
+
}
|
|
4000
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4001
|
+
0 && (module.exports = {
|
|
4002
|
+
CanvasAPI,
|
|
4003
|
+
EventBus,
|
|
4004
|
+
FormProvider,
|
|
4005
|
+
Provider,
|
|
4006
|
+
Wrapper,
|
|
4007
|
+
createInputRegistry,
|
|
4008
|
+
resolveInputDescriptor,
|
|
4009
|
+
useFormApi,
|
|
4010
|
+
useFormField,
|
|
4011
|
+
useFormSelections,
|
|
4012
|
+
useInputs,
|
|
4013
|
+
useOptionalFormApi
|
|
4014
|
+
});
|
|
4015
|
+
//# sourceMappingURL=index.cjs.map
|