@vizij/node-graph-authoring 0.0.2

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/index.cjs ADDED
@@ -0,0 +1,2367 @@
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/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ PRIMARY_SLOT_ALIAS: () => PRIMARY_SLOT_ALIAS,
24
+ PRIMARY_SLOT_ID: () => PRIMARY_SLOT_ID,
25
+ addBindingSlot: () => addBindingSlot,
26
+ bindingFromDefinition: () => bindingFromDefinition,
27
+ bindingOperatorDefinitions: () => bindingOperatorDefinitions,
28
+ bindingOperatorTypes: () => bindingOperatorTypes,
29
+ bindingTargetFromComponent: () => bindingTargetFromComponent,
30
+ bindingTargetFromInput: () => bindingTargetFromInput,
31
+ bindingToDefinition: () => bindingToDefinition,
32
+ buildRigGraphSpec: () => buildRigGraphSpec,
33
+ collectExpressionReferences: () => collectExpressionReferences,
34
+ createDefaultBinding: () => createDefaultBinding,
35
+ createDefaultBindings: () => createDefaultBindings,
36
+ createDefaultInputValues: () => createDefaultInputValues,
37
+ createDefaultOperatorSettings: () => createDefaultOperatorSettings,
38
+ createDefaultParentBinding: () => createDefaultParentBinding,
39
+ createDefaultRemap: () => createDefaultRemap,
40
+ ensureBindingStructure: () => ensureBindingStructure,
41
+ ensureOperatorParams: () => ensureOperatorParams,
42
+ getBindingOperatorDefinition: () => getBindingOperatorDefinition,
43
+ getPrimaryBindingSlot: () => getPrimaryBindingSlot,
44
+ mapExpression: () => mapExpression,
45
+ parseControlExpression: () => parseControlExpression,
46
+ reconcileBindings: () => reconcileBindings,
47
+ remapValue: () => remapValue,
48
+ removeBindingSlot: () => removeBindingSlot,
49
+ setBindingOperatorEnabled: () => setBindingOperatorEnabled,
50
+ updateBindingExpression: () => updateBindingExpression,
51
+ updateBindingOperatorParam: () => updateBindingOperatorParam,
52
+ updateBindingSlotAlias: () => updateBindingSlotAlias,
53
+ updateBindingSlotRemap: () => updateBindingSlotRemap,
54
+ updateBindingWithInput: () => updateBindingWithInput
55
+ });
56
+ module.exports = __toCommonJS(index_exports);
57
+
58
+ // src/graphBuilder.ts
59
+ var import_utils2 = require("@vizij/utils");
60
+ var import_utils3 = require("@vizij/utils");
61
+
62
+ // src/state.ts
63
+ var import_utils = require("@vizij/utils");
64
+
65
+ // src/operators.ts
66
+ var import_metadata = require("@vizij/node-graph-wasm/metadata");
67
+ var OPERATOR_TYPES = [
68
+ "spring",
69
+ "damp",
70
+ "slew"
71
+ ];
72
+ function valueJsonToNumber(value) {
73
+ if (!value || typeof value !== "object") {
74
+ return typeof value === "number" ? value : 0;
75
+ }
76
+ if ("float" in value && typeof value.float === "number") {
77
+ return value.float;
78
+ }
79
+ if ("int" in value && typeof value.int === "number") {
80
+ return value.int;
81
+ }
82
+ return 0;
83
+ }
84
+ var operatorDefinitionMap = /* @__PURE__ */ new Map();
85
+ OPERATOR_TYPES.forEach((type) => {
86
+ const signature = (0, import_metadata.requireNodeSignature)(type);
87
+ const params = signature.params.map(
88
+ (param) => ({
89
+ id: param.id,
90
+ label: param.label,
91
+ doc: param.doc,
92
+ min: param.min ?? void 0,
93
+ max: param.max ?? void 0,
94
+ defaultValue: valueJsonToNumber(param.default_json)
95
+ })
96
+ );
97
+ operatorDefinitionMap.set(type, {
98
+ type,
99
+ nodeType: signature.type_id,
100
+ label: signature.name,
101
+ description: signature.doc,
102
+ inputs: signature.inputs.map((input) => input.id),
103
+ params
104
+ });
105
+ });
106
+ function getBindingOperatorDefinition(type) {
107
+ const definition = operatorDefinitionMap.get(type);
108
+ if (!definition) {
109
+ throw new Error(`Unknown binding operator type '${type}'.`);
110
+ }
111
+ return definition;
112
+ }
113
+ var bindingOperatorDefinitions = OPERATOR_TYPES.map((type) => getBindingOperatorDefinition(type));
114
+ function createDefaultOperatorSettings(type) {
115
+ const definition = getBindingOperatorDefinition(type);
116
+ const params = {};
117
+ definition.params.forEach((param) => {
118
+ params[param.id] = param.defaultValue;
119
+ });
120
+ return {
121
+ type,
122
+ enabled: false,
123
+ params
124
+ };
125
+ }
126
+ function ensureOperatorParams(operator) {
127
+ const definition = getBindingOperatorDefinition(operator.type);
128
+ const params = {};
129
+ definition.params.forEach((param) => {
130
+ const value = operator.params?.[param.id];
131
+ params[param.id] = typeof value === "number" ? value : param.defaultValue;
132
+ });
133
+ return {
134
+ type: operator.type,
135
+ enabled: !!operator.enabled,
136
+ params
137
+ };
138
+ }
139
+ var bindingOperatorTypes = OPERATOR_TYPES;
140
+
141
+ // src/state.ts
142
+ var VECTOR_ANIMATABLE_TYPES = /* @__PURE__ */ new Set(["vector2", "vector3", "euler", "rgb"]);
143
+ function deriveComponentValueType(component) {
144
+ if (component.component) {
145
+ return "scalar";
146
+ }
147
+ return VECTOR_ANIMATABLE_TYPES.has(component.animatableType) ? "vector" : "scalar";
148
+ }
149
+ function isBindingValueType(value) {
150
+ return value === "scalar" || value === "vector";
151
+ }
152
+ function getTargetValueType(target) {
153
+ return isBindingValueType(target.valueType) ? target.valueType : "scalar";
154
+ }
155
+ function sanitizeSlotValueType(value, targetType) {
156
+ return isBindingValueType(value) ? value : targetType;
157
+ }
158
+ function bindingTargetFromComponent(component) {
159
+ return {
160
+ id: component.id,
161
+ defaultValue: component.defaultValue,
162
+ range: {
163
+ min: component.range.min,
164
+ max: component.range.max
165
+ },
166
+ valueType: deriveComponentValueType(component)
167
+ };
168
+ }
169
+ function bindingTargetFromInput(input) {
170
+ return {
171
+ id: input.id,
172
+ defaultValue: input.defaultValue,
173
+ range: {
174
+ min: input.range.min,
175
+ max: input.range.max
176
+ },
177
+ valueType: "scalar"
178
+ };
179
+ }
180
+ var DEFAULT_INPUT_RANGE = { min: -1, max: 1 };
181
+ var DEFAULT_INPUT_ANCHOR = 0;
182
+ var EPSILON = 1e-6;
183
+ var LEGACY_SLOT_PATTERN = /^slot_(\d+)$/i;
184
+ var ALIAS_SANITIZE_PATTERN = /[^A-Za-z0-9_]+/g;
185
+ var PRIMARY_SLOT_ID = "s1";
186
+ var PRIMARY_SLOT_ALIAS = "s1";
187
+ function createDefaultOperators() {
188
+ return bindingOperatorTypes.map(
189
+ (type) => createDefaultOperatorSettings(type)
190
+ );
191
+ }
192
+ function normalizeOperator(operator) {
193
+ const normalized = ensureOperatorParams(operator);
194
+ const definition = getBindingOperatorDefinition(normalized.type);
195
+ const params = {};
196
+ definition.params.forEach((param) => {
197
+ const value = normalized.params[param.id];
198
+ params[param.id] = typeof value === "number" ? value : param.defaultValue;
199
+ });
200
+ return {
201
+ type: normalized.type,
202
+ enabled: !!normalized.enabled,
203
+ params
204
+ };
205
+ }
206
+ function ensureOperators(binding) {
207
+ const existing = /* @__PURE__ */ new Map();
208
+ (binding.operators ?? []).forEach((operator) => {
209
+ try {
210
+ existing.set(operator.type, normalizeOperator(operator));
211
+ } catch {
212
+ }
213
+ });
214
+ return bindingOperatorTypes.map((type) => {
215
+ const current = existing.get(type);
216
+ if (current) {
217
+ return current;
218
+ }
219
+ return createDefaultOperatorSettings(type);
220
+ });
221
+ }
222
+ function paramsEqual(a, b) {
223
+ const aKeys = Object.keys(a);
224
+ const bKeys = Object.keys(b);
225
+ if (aKeys.length !== bKeys.length) {
226
+ return false;
227
+ }
228
+ for (const key of aKeys) {
229
+ if (a[key] !== b[key]) {
230
+ return false;
231
+ }
232
+ }
233
+ return true;
234
+ }
235
+ function operatorsEqual(a, b) {
236
+ if (!a || a.length !== b.length) {
237
+ return false;
238
+ }
239
+ for (let index = 0; index < a.length; index += 1) {
240
+ const left = a[index];
241
+ const right = b[index];
242
+ if (left.type !== right.type || left.enabled !== right.enabled) {
243
+ return false;
244
+ }
245
+ if (!paramsEqual(left.params, right.params)) {
246
+ return false;
247
+ }
248
+ }
249
+ return true;
250
+ }
251
+ function normalizeBindingWithOperators(binding) {
252
+ const operators = ensureOperators(binding);
253
+ if (operatorsEqual(binding.operators, operators)) {
254
+ return { binding, operators };
255
+ }
256
+ return {
257
+ binding: {
258
+ ...binding,
259
+ operators
260
+ },
261
+ operators
262
+ };
263
+ }
264
+ function defaultSlotId(index) {
265
+ return `s${index + 1}`;
266
+ }
267
+ function normalizeSlotId(value, index) {
268
+ if (value && value.length > 0) {
269
+ const match = value.match(LEGACY_SLOT_PATTERN);
270
+ if (match) {
271
+ const suffix = match[1] ?? String(index + 1);
272
+ return `s${suffix}`;
273
+ }
274
+ return value;
275
+ }
276
+ return defaultSlotId(index);
277
+ }
278
+ function normalizeSlotAlias(value, fallback, index) {
279
+ if (value && value.length > 0) {
280
+ const match = value.match(LEGACY_SLOT_PATTERN);
281
+ if (match) {
282
+ const suffix = match[1] ?? String(index + 1);
283
+ return { alias: `s${suffix}`, replaced: value };
284
+ }
285
+ return { alias: value, replaced: null };
286
+ }
287
+ if (fallback && fallback.length > 0) {
288
+ return { alias: fallback, replaced: null };
289
+ }
290
+ return { alias: defaultSlotId(index), replaced: null };
291
+ }
292
+ function sanitizeAliasInput(raw, fallback, index) {
293
+ const trimmed = raw.trim();
294
+ if (trimmed.length === 0) {
295
+ return fallback || defaultSlotId(index);
296
+ }
297
+ let sanitized = trimmed.replace(/\s+/g, "_").replace(ALIAS_SANITIZE_PATTERN, "");
298
+ if (sanitized.length === 0) {
299
+ sanitized = fallback || defaultSlotId(index);
300
+ }
301
+ if (/^\d/.test(sanitized)) {
302
+ sanitized = `s${sanitized}`;
303
+ }
304
+ return sanitized;
305
+ }
306
+ function ensureUniqueAlias(candidate, existing) {
307
+ if (!existing.has(candidate.toLowerCase())) {
308
+ existing.add(candidate.toLowerCase());
309
+ return candidate;
310
+ }
311
+ let suffix = 2;
312
+ let next = `${candidate}_${suffix}`;
313
+ while (existing.has(next.toLowerCase())) {
314
+ suffix += 1;
315
+ next = `${candidate}_${suffix}`;
316
+ }
317
+ existing.add(next.toLowerCase());
318
+ return next;
319
+ }
320
+ function escapeRegExp(value) {
321
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
322
+ }
323
+ function rewriteLegacyExpression(expression, replacements) {
324
+ if (expression.trim().length === 0) {
325
+ return expression;
326
+ }
327
+ return expression.replace(/\bslot_(\d+)\b/gi, (match, digits) => {
328
+ const replacement = replacements.get(match);
329
+ if (replacement) {
330
+ return replacement;
331
+ }
332
+ return `s${digits}`;
333
+ });
334
+ }
335
+ function clamp(value, min, max) {
336
+ return Math.min(max, Math.max(min, value));
337
+ }
338
+ function isFiniteNumber(value) {
339
+ return typeof value === "number" && Number.isFinite(value);
340
+ }
341
+ function deriveOutputDefaults(target) {
342
+ const { min, max } = target.range;
343
+ const anchor = clamp(target.defaultValue, min, max);
344
+ return {
345
+ outLow: min,
346
+ outAnchor: anchor,
347
+ outHigh: max
348
+ };
349
+ }
350
+ function deriveInputDefaults() {
351
+ return {
352
+ inLow: DEFAULT_INPUT_RANGE.min,
353
+ inAnchor: DEFAULT_INPUT_ANCHOR,
354
+ inHigh: DEFAULT_INPUT_RANGE.max
355
+ };
356
+ }
357
+ function migrateLegacyRemap(legacy, target) {
358
+ const inputDefaults = deriveInputDefaults();
359
+ const outputDefaults = deriveOutputDefaults(target);
360
+ const defaults = {
361
+ inLow: inputDefaults.inLow,
362
+ inAnchor: inputDefaults.inAnchor,
363
+ inHigh: inputDefaults.inHigh,
364
+ outLow: outputDefaults.outLow,
365
+ outAnchor: outputDefaults.outAnchor,
366
+ outHigh: outputDefaults.outHigh
367
+ };
368
+ if ("inLow" in legacy && "inHigh" in legacy && "outLow" in legacy && "outHigh" in legacy) {
369
+ const inLow2 = isFiniteNumber(legacy.inLow) ? legacy.inLow : defaults.inLow;
370
+ const inAnchor2 = isFiniteNumber(legacy.inAnchor) ? legacy.inAnchor : defaults.inAnchor;
371
+ const inHigh2 = isFiniteNumber(legacy.inHigh) ? legacy.inHigh : defaults.inHigh;
372
+ let outLow = isFiniteNumber(legacy.outLow) ? legacy.outLow : defaults.outLow;
373
+ let outHigh = isFiniteNumber(legacy.outHigh) ? legacy.outHigh : defaults.outHigh;
374
+ if (outLow > outHigh) {
375
+ const low = outHigh;
376
+ const high = outLow;
377
+ outLow = low;
378
+ outHigh = high;
379
+ }
380
+ const outAnchor2 = clamp(
381
+ isFiniteNumber(legacy.outAnchor) ? legacy.outAnchor : defaults.outAnchor,
382
+ outLow,
383
+ outHigh
384
+ );
385
+ return {
386
+ inLow: inLow2,
387
+ inAnchor: inAnchor2,
388
+ inHigh: inHigh2,
389
+ outLow,
390
+ outAnchor: outAnchor2,
391
+ outHigh
392
+ };
393
+ }
394
+ const legacyTyped = legacy;
395
+ const inLow = isFiniteNumber(legacyTyped.inMin) ? legacyTyped.inMin : defaults.inLow;
396
+ const inHigh = isFiniteNumber(legacyTyped.inMax) ? legacyTyped.inMax : defaults.inHigh;
397
+ const inAnchor = (inLow + inHigh) / 2;
398
+ const legacyOutMid = isFiniteNumber(legacyTyped.outMin) && isFiniteNumber(legacyTyped.outMax) ? (legacyTyped.outMin + legacyTyped.outMax) / 2 : defaults.outAnchor;
399
+ const outAnchor = clamp(legacyOutMid, defaults.outLow, defaults.outHigh);
400
+ return {
401
+ inLow,
402
+ inAnchor,
403
+ inHigh,
404
+ outLow: defaults.outLow,
405
+ outAnchor,
406
+ outHigh: defaults.outHigh
407
+ };
408
+ }
409
+ function normalizeRemap(remap, target) {
410
+ if (!remap) {
411
+ return createDefaultRemap(target);
412
+ }
413
+ return migrateLegacyRemap(remap, target);
414
+ }
415
+ function cloneRemap(remap) {
416
+ return (0, import_utils.cloneRemapSettings)(remap);
417
+ }
418
+ function sanitizeRemap(remap, target) {
419
+ const normalized = normalizeRemap(remap, target);
420
+ const outputDefaults = deriveOutputDefaults(target);
421
+ if (!Number.isFinite(normalized.outLow)) {
422
+ normalized.outLow = outputDefaults.outLow;
423
+ }
424
+ if (!Number.isFinite(normalized.outHigh)) {
425
+ normalized.outHigh = outputDefaults.outHigh;
426
+ }
427
+ if (!Number.isFinite(normalized.outAnchor)) {
428
+ normalized.outAnchor = outputDefaults.outAnchor;
429
+ }
430
+ if (normalized.outLow > normalized.outHigh) {
431
+ const low = normalized.outHigh;
432
+ const high = normalized.outLow;
433
+ normalized.outLow = low;
434
+ normalized.outHigh = high;
435
+ }
436
+ normalized.outAnchor = clamp(
437
+ normalized.outAnchor,
438
+ normalized.outLow,
439
+ normalized.outHigh
440
+ );
441
+ return normalized;
442
+ }
443
+ function createDefaultRemap(target) {
444
+ const inputDefaults = deriveInputDefaults();
445
+ const outputDefaults = deriveOutputDefaults(target);
446
+ return {
447
+ inLow: inputDefaults.inLow,
448
+ inAnchor: inputDefaults.inAnchor,
449
+ inHigh: inputDefaults.inHigh,
450
+ outLow: outputDefaults.outLow,
451
+ outAnchor: outputDefaults.outAnchor,
452
+ outHigh: outputDefaults.outHigh
453
+ };
454
+ }
455
+ function createDefaultBindings(components) {
456
+ const bindings = {};
457
+ components.forEach((component) => {
458
+ bindings[component.id] = createDefaultBinding(component);
459
+ });
460
+ return bindings;
461
+ }
462
+ function createDefaultBinding(component) {
463
+ const remap = createDefaultRemap(component);
464
+ const valueType = getTargetValueType(component);
465
+ return {
466
+ targetId: component.id,
467
+ inputId: null,
468
+ remap,
469
+ slots: [
470
+ {
471
+ id: PRIMARY_SLOT_ID,
472
+ alias: PRIMARY_SLOT_ALIAS,
473
+ inputId: null,
474
+ remap: cloneRemap(remap),
475
+ valueType
476
+ }
477
+ ],
478
+ expression: PRIMARY_SLOT_ALIAS,
479
+ operators: createDefaultOperators()
480
+ };
481
+ }
482
+ function createDefaultParentBinding(component) {
483
+ const base = createDefaultBinding(component);
484
+ const ensured = ensurePrimarySlot(base, component);
485
+ const slots = ensured.slots.map((slot, index) => {
486
+ if (index === 0) {
487
+ return {
488
+ ...slot,
489
+ alias: "self",
490
+ inputId: import_utils.SELF_BINDING_ID
491
+ };
492
+ }
493
+ return slot;
494
+ });
495
+ return {
496
+ ...ensured,
497
+ inputId: import_utils.SELF_BINDING_ID,
498
+ slots,
499
+ expression: "self"
500
+ };
501
+ }
502
+ function ensurePrimarySlot(binding, target) {
503
+ const normalizedBindingRemap = sanitizeRemap(binding.remap, target);
504
+ const targetValueType = getTargetValueType(target);
505
+ const aliasReplacements = /* @__PURE__ */ new Map();
506
+ const sourceSlots = Array.isArray(binding.slots) && binding.slots.length > 0 ? binding.slots : [
507
+ {
508
+ id: PRIMARY_SLOT_ID,
509
+ alias: PRIMARY_SLOT_ALIAS,
510
+ inputId: binding.inputId ?? null,
511
+ remap: cloneRemap(normalizedBindingRemap),
512
+ valueType: targetValueType
513
+ }
514
+ ];
515
+ const normalizedSlots = sourceSlots.map(
516
+ (slot, index) => {
517
+ const normalizedId = normalizeSlotId(slot.id, index);
518
+ const { alias: normalizedAlias, replaced } = normalizeSlotAlias(
519
+ slot.alias,
520
+ normalizedId,
521
+ index
522
+ );
523
+ if (replaced && replaced !== normalizedAlias) {
524
+ aliasReplacements.set(replaced, normalizedAlias);
525
+ }
526
+ const slotRemapSource = slot.remap ?? (index === 0 ? normalizedBindingRemap : createDefaultRemap(target));
527
+ const normalizedSlotRemap = sanitizeRemap(slotRemapSource, target);
528
+ const inputId = slot.inputId !== void 0 && slot.inputId !== null ? slot.inputId : index === 0 ? binding.inputId ?? null : null;
529
+ const slotValueType = sanitizeSlotValueType(
530
+ slot.valueType,
531
+ targetValueType
532
+ );
533
+ return {
534
+ id: normalizedId,
535
+ alias: normalizedAlias,
536
+ inputId,
537
+ remap: cloneRemap(normalizedSlotRemap),
538
+ valueType: slotValueType
539
+ };
540
+ }
541
+ );
542
+ const primary = normalizedSlots[0];
543
+ const primaryRemap = sanitizeRemap(primary.remap, target);
544
+ const primaryInputId = primary.inputId === import_utils.SELF_BINDING_ID ? import_utils.SELF_BINDING_ID : primary.inputId ?? binding.inputId ?? null;
545
+ const primaryAlias = primaryInputId === import_utils.SELF_BINDING_ID ? "self" : primary.alias || PRIMARY_SLOT_ALIAS;
546
+ normalizedSlots[0] = {
547
+ ...primary,
548
+ id: primary.id || PRIMARY_SLOT_ID,
549
+ alias: primaryAlias,
550
+ inputId: primaryInputId,
551
+ remap: cloneRemap(primaryRemap),
552
+ valueType: sanitizeSlotValueType(primary.valueType, targetValueType)
553
+ };
554
+ normalizedSlots.slice(1).forEach((slot, index) => {
555
+ const slotRemap = sanitizeRemap(slot.remap, target);
556
+ normalizedSlots[index + 1] = {
557
+ ...slot,
558
+ id: slot.id || defaultSlotId(index + 1),
559
+ alias: slot.alias || defaultSlotId(index + 1),
560
+ remap: cloneRemap(slotRemap),
561
+ valueType: sanitizeSlotValueType(slot.valueType, targetValueType)
562
+ };
563
+ });
564
+ const rawExpression = typeof binding.expression === "string" ? binding.expression.trim() : "";
565
+ let expression = rawExpression.length > 0 ? rawExpression : normalizedSlots[0].alias;
566
+ expression = rewriteLegacyExpression(expression, aliasReplacements);
567
+ const normalizedBinding = {
568
+ ...binding,
569
+ inputId: normalizedSlots[0].inputId ?? null,
570
+ remap: cloneRemap(primaryRemap),
571
+ slots: normalizedSlots,
572
+ expression
573
+ };
574
+ const operators = ensureOperators(normalizedBinding);
575
+ if (operatorsEqual(normalizedBinding.operators, operators)) {
576
+ return normalizedBinding;
577
+ }
578
+ return {
579
+ ...normalizedBinding,
580
+ operators
581
+ };
582
+ }
583
+ function createDefaultInputValues(inputs = []) {
584
+ const values = {};
585
+ inputs.forEach((input) => {
586
+ values[input.id] = input.defaultValue;
587
+ });
588
+ return values;
589
+ }
590
+ function ensureBindingStructure(binding, target) {
591
+ return ensurePrimarySlot(binding, target);
592
+ }
593
+ function getPrimaryBindingSlot(binding) {
594
+ if (!binding.slots || binding.slots.length === 0) {
595
+ return null;
596
+ }
597
+ return binding.slots[0];
598
+ }
599
+ function addBindingSlot(binding, target) {
600
+ const base = ensurePrimarySlot(binding, target);
601
+ const nextIndex = base.slots.length + 1;
602
+ const slotId = defaultSlotId(nextIndex - 1);
603
+ const alias = slotId;
604
+ const remap = createDefaultRemap(target);
605
+ const nextSlots = [
606
+ ...base.slots,
607
+ {
608
+ id: slotId,
609
+ alias,
610
+ inputId: null,
611
+ remap: cloneRemap(remap)
612
+ }
613
+ ];
614
+ return ensurePrimarySlot(
615
+ {
616
+ ...base,
617
+ slots: nextSlots
618
+ },
619
+ target
620
+ );
621
+ }
622
+ function removeBindingSlot(binding, target, slotId) {
623
+ const base = ensurePrimarySlot(binding, target);
624
+ if (base.slots.length <= 1) {
625
+ return base;
626
+ }
627
+ const nextSlots = base.slots.filter((slot) => slot.id !== slotId);
628
+ if (nextSlots.length === base.slots.length) {
629
+ return base;
630
+ }
631
+ const nextBinding = ensurePrimarySlot(
632
+ {
633
+ ...base,
634
+ slots: nextSlots
635
+ },
636
+ target
637
+ );
638
+ if (!nextBinding.expression) {
639
+ return nextBinding;
640
+ }
641
+ const hasExpressionAlias = nextBinding.slots.some(
642
+ (slot) => slot.alias === nextBinding.expression
643
+ );
644
+ if (!hasExpressionAlias) {
645
+ return {
646
+ ...nextBinding,
647
+ expression: nextBinding.slots[0]?.alias ?? PRIMARY_SLOT_ALIAS
648
+ };
649
+ }
650
+ return nextBinding;
651
+ }
652
+ function updateBindingSlotAlias(binding, target, slotId, nextAlias) {
653
+ const base = ensurePrimarySlot(binding, target);
654
+ const slotIndex = base.slots.findIndex((slot) => slot.id === slotId);
655
+ if (slotIndex < 0) {
656
+ return base;
657
+ }
658
+ const slots = base.slots.map((slot) => ({ ...slot }));
659
+ const currentSlot = slots[slotIndex];
660
+ const fallbackAlias = currentSlot.alias || currentSlot.id || defaultSlotId(slotIndex);
661
+ const sanitized = sanitizeAliasInput(nextAlias, fallbackAlias, slotIndex);
662
+ const existingAliases = /* @__PURE__ */ new Set();
663
+ slots.forEach((slot, index) => {
664
+ if (index === slotIndex) {
665
+ return;
666
+ }
667
+ if (slot.alias) {
668
+ existingAliases.add(slot.alias.toLowerCase());
669
+ }
670
+ });
671
+ const uniqueAlias = ensureUniqueAlias(sanitized, existingAliases);
672
+ const previousAlias = currentSlot.alias;
673
+ slots[slotIndex] = {
674
+ ...currentSlot,
675
+ alias: uniqueAlias
676
+ };
677
+ let nextExpression = base.expression;
678
+ if (previousAlias && previousAlias !== uniqueAlias && typeof nextExpression === "string") {
679
+ const pattern = new RegExp(`\\b${escapeRegExp(previousAlias)}\\b`, "g");
680
+ nextExpression = nextExpression.replace(pattern, uniqueAlias);
681
+ }
682
+ const updated = ensurePrimarySlot(
683
+ {
684
+ ...base,
685
+ slots,
686
+ expression: nextExpression
687
+ },
688
+ target
689
+ );
690
+ return updated;
691
+ }
692
+ function setBindingOperatorEnabled(binding, type, enabled) {
693
+ const { binding: normalizedBinding, operators } = normalizeBindingWithOperators(binding);
694
+ let changed = false;
695
+ const nextOperators = operators.map((operator) => {
696
+ if (operator.type !== type) {
697
+ return operator;
698
+ }
699
+ if (operator.enabled === enabled) {
700
+ return operator;
701
+ }
702
+ changed = true;
703
+ return {
704
+ ...operator,
705
+ enabled
706
+ };
707
+ });
708
+ if (!changed) {
709
+ return normalizedBinding;
710
+ }
711
+ if (operatorsEqual(normalizedBinding.operators, nextOperators)) {
712
+ return normalizedBinding;
713
+ }
714
+ return {
715
+ ...normalizedBinding,
716
+ operators: nextOperators
717
+ };
718
+ }
719
+ function updateBindingOperatorParam(binding, type, paramId, value) {
720
+ const { binding: normalizedBinding, operators } = normalizeBindingWithOperators(binding);
721
+ const definition = getBindingOperatorDefinition(type);
722
+ const paramDefinition = definition.params.find(
723
+ (param) => param.id === paramId
724
+ );
725
+ if (!paramDefinition) {
726
+ return normalizedBinding;
727
+ }
728
+ let nextValue = value;
729
+ if (typeof paramDefinition.min === "number") {
730
+ nextValue = Math.max(paramDefinition.min, nextValue);
731
+ }
732
+ if (typeof paramDefinition.max === "number") {
733
+ nextValue = Math.min(paramDefinition.max, nextValue);
734
+ }
735
+ let changed = false;
736
+ const nextOperators = operators.map((operator) => {
737
+ if (operator.type !== type) {
738
+ return operator;
739
+ }
740
+ if (operator.params[paramId] === nextValue) {
741
+ return operator;
742
+ }
743
+ changed = true;
744
+ return {
745
+ ...operator,
746
+ params: {
747
+ ...operator.params,
748
+ [paramId]: nextValue
749
+ }
750
+ };
751
+ });
752
+ if (!changed) {
753
+ return normalizedBinding;
754
+ }
755
+ if (operatorsEqual(normalizedBinding.operators, nextOperators)) {
756
+ return normalizedBinding;
757
+ }
758
+ return {
759
+ ...normalizedBinding,
760
+ operators: nextOperators
761
+ };
762
+ }
763
+ function updateBindingExpression(binding, target, expression) {
764
+ const base = ensurePrimarySlot(binding, target);
765
+ const trimmed = expression.trim();
766
+ return {
767
+ ...base,
768
+ expression: trimmed.length > 0 ? trimmed : base.slots[0]?.alias ?? PRIMARY_SLOT_ALIAS
769
+ };
770
+ }
771
+ function updateBindingSlotRemap(binding, target, slotId, field, value) {
772
+ const base = ensurePrimarySlot(binding, target);
773
+ const nextSlots = base.slots.map((slot) => {
774
+ if (slot.id !== slotId) {
775
+ return slot;
776
+ }
777
+ const updatedRemap = {
778
+ ...slot.remap,
779
+ [field]: value
780
+ };
781
+ const sanitized = sanitizeRemap(updatedRemap, target);
782
+ return {
783
+ ...slot,
784
+ remap: cloneRemap(sanitized)
785
+ };
786
+ });
787
+ const updated = ensurePrimarySlot(
788
+ {
789
+ ...base,
790
+ slots: nextSlots
791
+ },
792
+ target
793
+ );
794
+ if (updated.slots[0]?.id === slotId) {
795
+ updated.remap = {
796
+ ...updated.remap,
797
+ [field]: value
798
+ };
799
+ }
800
+ return updated;
801
+ }
802
+ function updateBindingWithInput(binding, target, input, slotId = PRIMARY_SLOT_ID) {
803
+ const base = ensurePrimarySlot(binding, target);
804
+ const slotIndex = base.slots.findIndex((slot) => slot.id === slotId);
805
+ const effectiveIndex = slotIndex >= 0 ? slotIndex : base.slots.length;
806
+ const slots = base.slots.map((slot) => ({
807
+ ...slot,
808
+ remap: cloneRemap(slot.remap)
809
+ }));
810
+ if (slotIndex === -1) {
811
+ const alias = slotId === PRIMARY_SLOT_ID && slots.length === 0 ? PRIMARY_SLOT_ALIAS : slotId;
812
+ slots.push({
813
+ id: slotId,
814
+ alias,
815
+ inputId: null,
816
+ remap: cloneRemap(createDefaultRemap(target))
817
+ });
818
+ }
819
+ const currentSlot = slots[effectiveIndex];
820
+ if (!input) {
821
+ const normalizedSlotRemap = sanitizeRemap(currentSlot.remap, target);
822
+ const updatedRemap2 = {
823
+ ...normalizedSlotRemap,
824
+ inLow: DEFAULT_INPUT_RANGE.min,
825
+ inAnchor: DEFAULT_INPUT_ANCHOR,
826
+ inHigh: DEFAULT_INPUT_RANGE.max
827
+ };
828
+ slots[effectiveIndex] = {
829
+ ...currentSlot,
830
+ inputId: null,
831
+ remap: cloneRemap(updatedRemap2)
832
+ };
833
+ if (effectiveIndex === 0) {
834
+ return {
835
+ ...base,
836
+ inputId: null,
837
+ remap: cloneRemap(updatedRemap2),
838
+ slots
839
+ };
840
+ }
841
+ return {
842
+ ...base,
843
+ slots
844
+ };
845
+ }
846
+ const normalizedRemap = sanitizeRemap(currentSlot.remap, target);
847
+ const updatedRemap = {
848
+ ...normalizedRemap,
849
+ inLow: input.range.min,
850
+ inAnchor: clamp(input.defaultValue, input.range.min, input.range.max),
851
+ inHigh: input.range.max,
852
+ ...deriveOutputDefaults(target)
853
+ };
854
+ slots[effectiveIndex] = {
855
+ ...currentSlot,
856
+ inputId: input.id,
857
+ remap: cloneRemap(updatedRemap)
858
+ };
859
+ if (effectiveIndex === 0) {
860
+ return {
861
+ ...base,
862
+ inputId: input.id,
863
+ remap: cloneRemap(updatedRemap),
864
+ slots
865
+ };
866
+ }
867
+ return {
868
+ ...base,
869
+ slots
870
+ };
871
+ }
872
+ function remapValue(value, remap) {
873
+ const { inLow, inAnchor, inHigh, outLow, outAnchor, outHigh } = remap;
874
+ if (Number.isNaN(value)) {
875
+ return outAnchor;
876
+ }
877
+ if (value <= inAnchor) {
878
+ const span2 = inAnchor - inLow;
879
+ if (Math.abs(span2) < EPSILON) {
880
+ return outLow;
881
+ }
882
+ const t2 = (value - inLow) / span2;
883
+ return outLow + t2 * (outAnchor - outLow);
884
+ }
885
+ const span = inHigh - inAnchor;
886
+ if (Math.abs(span) < EPSILON) {
887
+ return outHigh;
888
+ }
889
+ const t = (value - inAnchor) / span;
890
+ return outAnchor + t * (outHigh - outAnchor);
891
+ }
892
+ function reconcileBindings(previous, components) {
893
+ const next = {};
894
+ components.forEach((component) => {
895
+ const existing = previous[component.id];
896
+ if (existing) {
897
+ const ensured = ensureBindingStructure(existing, component);
898
+ const aliasReplacements = /* @__PURE__ */ new Map();
899
+ const slots = ensured.slots.map((slot, index) => {
900
+ const normalizedId = normalizeSlotId(slot.id, index);
901
+ const { alias: normalizedAlias, replaced } = normalizeSlotAlias(
902
+ slot.alias,
903
+ normalizedId,
904
+ index
905
+ );
906
+ if (replaced && replaced !== normalizedAlias) {
907
+ aliasReplacements.set(replaced, normalizedAlias);
908
+ }
909
+ const slotRemap = sanitizeRemap(slot.remap, component);
910
+ return {
911
+ ...slot,
912
+ id: normalizedId,
913
+ alias: normalizedAlias,
914
+ remap: cloneRemap(slotRemap)
915
+ };
916
+ });
917
+ const primary = slots[0];
918
+ let expression = typeof ensured.expression === "string" && ensured.expression.trim().length > 0 ? ensured.expression.trim() : primary.alias;
919
+ expression = rewriteLegacyExpression(expression, aliasReplacements);
920
+ next[component.id] = {
921
+ ...ensured,
922
+ targetId: component.id,
923
+ inputId: primary.inputId ?? null,
924
+ remap: cloneRemap(primary.remap),
925
+ slots,
926
+ expression
927
+ };
928
+ } else {
929
+ next[component.id] = createDefaultBinding(component);
930
+ }
931
+ });
932
+ return next;
933
+ }
934
+ function bindingToDefinition(binding) {
935
+ const operators = binding.operators ? binding.operators.map((operator) => ({
936
+ type: operator.type,
937
+ enabled: !!operator.enabled,
938
+ params: { ...operator.params }
939
+ })) : void 0;
940
+ const definition = {
941
+ inputId: binding.inputId ?? null,
942
+ remap: cloneRemap(binding.remap),
943
+ slots: binding.slots.map((slot) => ({
944
+ ...slot,
945
+ remap: cloneRemap(slot.remap)
946
+ })),
947
+ expression: binding.expression,
948
+ operators
949
+ };
950
+ return definition;
951
+ }
952
+ function bindingFromDefinition(target, definition) {
953
+ if (!definition) {
954
+ return createDefaultBinding(target);
955
+ }
956
+ const definitionOperators = definition.operators;
957
+ const binding = {
958
+ targetId: target.id,
959
+ inputId: definition.inputId ?? null,
960
+ remap: cloneRemap(definition.remap),
961
+ slots: definition.slots.map((slot) => ({
962
+ ...slot,
963
+ remap: cloneRemap(slot.remap)
964
+ })),
965
+ expression: definition.expression,
966
+ operators: definitionOperators ? definitionOperators.map((operator) => ({
967
+ type: operator.type,
968
+ enabled: !!operator.enabled,
969
+ params: { ...operator.params }
970
+ })) : void 0
971
+ };
972
+ return ensureBindingStructure(binding, target);
973
+ }
974
+
975
+ // src/expression.ts
976
+ var WHITESPACE = /\s/;
977
+ var IDENT_START = /[A-Za-z_]/;
978
+ var IDENT_PART = /[A-Za-z0-9_]/;
979
+ var DIGIT = /[0-9]/;
980
+ var ControlExpressionParser = class {
981
+ constructor(input) {
982
+ this.input = input;
983
+ this.index = 0;
984
+ this.errors = [];
985
+ }
986
+ parse() {
987
+ this.skipWhitespace();
988
+ const node = this.parseExpression();
989
+ this.skipWhitespace();
990
+ if (!node) {
991
+ if (this.errors.length === 0) {
992
+ this.errors.push({
993
+ index: this.index,
994
+ message: "Empty expression."
995
+ });
996
+ }
997
+ return { node: null, errors: this.errors };
998
+ }
999
+ if (!this.isAtEnd()) {
1000
+ this.errors.push({
1001
+ index: this.index,
1002
+ message: `Unexpected token "${this.peek()}"`
1003
+ });
1004
+ return { node: null, errors: this.errors };
1005
+ }
1006
+ return { node, errors: this.errors };
1007
+ }
1008
+ parseExpression() {
1009
+ return this.parseLogicalOr();
1010
+ }
1011
+ parseLogicalOr() {
1012
+ let left = this.parseLogicalAnd();
1013
+ if (!left) {
1014
+ return null;
1015
+ }
1016
+ while (true) {
1017
+ this.skipWhitespace();
1018
+ const operator = this.matchAny(["||"]);
1019
+ if (!operator) {
1020
+ break;
1021
+ }
1022
+ const right = this.parseLogicalAnd();
1023
+ if (!right) {
1024
+ this.errors.push({
1025
+ index: this.index,
1026
+ message: "Expected expression after operator."
1027
+ });
1028
+ return null;
1029
+ }
1030
+ left = {
1031
+ type: "Binary",
1032
+ operator,
1033
+ left,
1034
+ right
1035
+ };
1036
+ }
1037
+ return left;
1038
+ }
1039
+ parseLogicalAnd() {
1040
+ let left = this.parseComparison();
1041
+ if (!left) {
1042
+ return null;
1043
+ }
1044
+ while (true) {
1045
+ this.skipWhitespace();
1046
+ const operator = this.matchAny(["&&"]);
1047
+ if (!operator) {
1048
+ break;
1049
+ }
1050
+ const right = this.parseComparison();
1051
+ if (!right) {
1052
+ this.errors.push({
1053
+ index: this.index,
1054
+ message: "Expected expression after operator."
1055
+ });
1056
+ return null;
1057
+ }
1058
+ left = {
1059
+ type: "Binary",
1060
+ operator,
1061
+ left,
1062
+ right
1063
+ };
1064
+ }
1065
+ return left;
1066
+ }
1067
+ parseComparison() {
1068
+ let left = this.parseAdditive();
1069
+ if (!left) {
1070
+ return null;
1071
+ }
1072
+ while (true) {
1073
+ this.skipWhitespace();
1074
+ if (this.input.startsWith(">=", this.index) || this.input.startsWith("<=", this.index)) {
1075
+ const op = this.input.slice(this.index, this.index + 2);
1076
+ this.index += 2;
1077
+ this.errors.push({
1078
+ index: this.index - 2,
1079
+ message: `Operator "${op}" is not supported.`
1080
+ });
1081
+ return null;
1082
+ }
1083
+ const operator = this.matchAny(["==", "!=", ">", "<"]);
1084
+ if (!operator) {
1085
+ break;
1086
+ }
1087
+ const right = this.parseAdditive();
1088
+ if (!right) {
1089
+ this.errors.push({
1090
+ index: this.index,
1091
+ message: "Expected expression after operator."
1092
+ });
1093
+ return null;
1094
+ }
1095
+ left = {
1096
+ type: "Binary",
1097
+ operator,
1098
+ left,
1099
+ right
1100
+ };
1101
+ }
1102
+ return left;
1103
+ }
1104
+ parseAdditive() {
1105
+ let left = this.parseMultiplicative();
1106
+ if (!left) {
1107
+ return null;
1108
+ }
1109
+ while (true) {
1110
+ this.skipWhitespace();
1111
+ const operator = this.peek();
1112
+ if (operator !== "+" && operator !== "-") {
1113
+ break;
1114
+ }
1115
+ this.index += 1;
1116
+ const right = this.parseMultiplicative();
1117
+ if (!right) {
1118
+ this.errors.push({
1119
+ index: this.index,
1120
+ message: "Expected expression after operator."
1121
+ });
1122
+ return null;
1123
+ }
1124
+ left = {
1125
+ type: "Binary",
1126
+ operator,
1127
+ left,
1128
+ right
1129
+ };
1130
+ }
1131
+ return left;
1132
+ }
1133
+ parseMultiplicative() {
1134
+ let left = this.parseUnary();
1135
+ if (!left) {
1136
+ return null;
1137
+ }
1138
+ while (true) {
1139
+ this.skipWhitespace();
1140
+ const operator = this.peek();
1141
+ if (operator !== "*" && operator !== "/") {
1142
+ break;
1143
+ }
1144
+ this.index += 1;
1145
+ const right = this.parseUnary();
1146
+ if (!right) {
1147
+ this.errors.push({
1148
+ index: this.index,
1149
+ message: "Expected expression after operator."
1150
+ });
1151
+ return null;
1152
+ }
1153
+ left = {
1154
+ type: "Binary",
1155
+ operator,
1156
+ left,
1157
+ right
1158
+ };
1159
+ }
1160
+ return left;
1161
+ }
1162
+ parseUnary() {
1163
+ this.skipWhitespace();
1164
+ const char = this.peek();
1165
+ if (!char) {
1166
+ this.errors.push({
1167
+ index: this.index,
1168
+ message: "Unexpected end of expression."
1169
+ });
1170
+ return null;
1171
+ }
1172
+ if (char === "+" || char === "-" || char === "!") {
1173
+ this.index += 1;
1174
+ const operand = this.parseUnary();
1175
+ if (!operand) {
1176
+ this.errors.push({
1177
+ index: this.index,
1178
+ message: `Expected operand after unary "${char}".`
1179
+ });
1180
+ return null;
1181
+ }
1182
+ return {
1183
+ type: "Unary",
1184
+ operator: char,
1185
+ operand
1186
+ };
1187
+ }
1188
+ return this.parsePrimary();
1189
+ }
1190
+ parsePrimary() {
1191
+ this.skipWhitespace();
1192
+ const char = this.peek();
1193
+ if (!char) {
1194
+ this.errors.push({
1195
+ index: this.index,
1196
+ message: "Unexpected end of expression."
1197
+ });
1198
+ return null;
1199
+ }
1200
+ if (char === "(") {
1201
+ this.index += 1;
1202
+ const expression = this.parseExpression();
1203
+ this.skipWhitespace();
1204
+ if (this.peek() === ")") {
1205
+ this.index += 1;
1206
+ return expression;
1207
+ }
1208
+ this.errors.push({
1209
+ index: this.index,
1210
+ message: "Unmatched parenthesis."
1211
+ });
1212
+ return null;
1213
+ }
1214
+ if (IDENT_START.test(char)) {
1215
+ return this.parseIdentifierOrFunction();
1216
+ }
1217
+ if (DIGIT.test(char) || char === ".") {
1218
+ return this.parseNumber();
1219
+ }
1220
+ this.errors.push({
1221
+ index: this.index,
1222
+ message: `Unexpected character "${char}".`
1223
+ });
1224
+ return null;
1225
+ }
1226
+ parseIdentifierOrFunction() {
1227
+ const start = this.index;
1228
+ while (!this.isAtEnd() && IDENT_PART.test(this.peek())) {
1229
+ this.index += 1;
1230
+ }
1231
+ const name = this.input.slice(start, this.index);
1232
+ if (!name) {
1233
+ this.errors.push({
1234
+ index: start,
1235
+ message: "Invalid identifier."
1236
+ });
1237
+ return null;
1238
+ }
1239
+ this.skipWhitespace();
1240
+ if (this.peek() === "(") {
1241
+ this.index += 1;
1242
+ const args = [];
1243
+ this.skipWhitespace();
1244
+ if (this.peek() === ")") {
1245
+ this.index += 1;
1246
+ return {
1247
+ type: "Function",
1248
+ name,
1249
+ args
1250
+ };
1251
+ }
1252
+ while (true) {
1253
+ const argument = this.parseExpression();
1254
+ if (!argument) {
1255
+ this.errors.push({
1256
+ index: this.index,
1257
+ message: `Expected expression for argument ${args.length + 1} of "${name}".`
1258
+ });
1259
+ return null;
1260
+ }
1261
+ args.push(argument);
1262
+ this.skipWhitespace();
1263
+ const next = this.peek();
1264
+ if (next === ",") {
1265
+ this.index += 1;
1266
+ this.skipWhitespace();
1267
+ if (this.peek() === ")") {
1268
+ this.errors.push({
1269
+ index: this.index,
1270
+ message: `Expected expression after "," in call to "${name}".`
1271
+ });
1272
+ return null;
1273
+ }
1274
+ continue;
1275
+ }
1276
+ if (next === ")") {
1277
+ this.index += 1;
1278
+ break;
1279
+ }
1280
+ if (next === null) {
1281
+ this.errors.push({
1282
+ index: this.index,
1283
+ message: `Unterminated call to "${name}".`
1284
+ });
1285
+ } else {
1286
+ this.errors.push({
1287
+ index: this.index,
1288
+ message: `Expected "," or ")" in call to "${name}".`
1289
+ });
1290
+ }
1291
+ return null;
1292
+ }
1293
+ return {
1294
+ type: "Function",
1295
+ name,
1296
+ args
1297
+ };
1298
+ }
1299
+ return {
1300
+ type: "Reference",
1301
+ name
1302
+ };
1303
+ }
1304
+ parseNumber() {
1305
+ const start = this.index;
1306
+ let hasDigits = false;
1307
+ while (!this.isAtEnd()) {
1308
+ const char = this.peek();
1309
+ if (DIGIT.test(char)) {
1310
+ hasDigits = true;
1311
+ this.index += 1;
1312
+ continue;
1313
+ }
1314
+ if (char === ".") {
1315
+ this.index += 1;
1316
+ continue;
1317
+ }
1318
+ break;
1319
+ }
1320
+ const raw = this.input.slice(start, this.index);
1321
+ const value = Number(raw);
1322
+ if (!hasDigits || Number.isNaN(value)) {
1323
+ this.errors.push({
1324
+ index: start,
1325
+ message: `Invalid numeric literal "${raw}".`
1326
+ });
1327
+ return null;
1328
+ }
1329
+ return {
1330
+ type: "Literal",
1331
+ value
1332
+ };
1333
+ }
1334
+ matchAny(operators) {
1335
+ for (const operator of operators) {
1336
+ if (this.input.startsWith(operator, this.index)) {
1337
+ this.index += operator.length;
1338
+ return operator;
1339
+ }
1340
+ }
1341
+ return null;
1342
+ }
1343
+ skipWhitespace() {
1344
+ while (!this.isAtEnd() && WHITESPACE.test(this.peek())) {
1345
+ this.index += 1;
1346
+ }
1347
+ }
1348
+ peek() {
1349
+ if (this.index >= this.input.length) {
1350
+ return null;
1351
+ }
1352
+ return this.input[this.index] ?? null;
1353
+ }
1354
+ isAtEnd() {
1355
+ return this.index >= this.input.length;
1356
+ }
1357
+ };
1358
+ function parseControlExpression(expression) {
1359
+ const parser = new ControlExpressionParser(expression);
1360
+ return parser.parse();
1361
+ }
1362
+ function collectExpressionReferences(node, target = /* @__PURE__ */ new Set()) {
1363
+ if (!node) {
1364
+ return target;
1365
+ }
1366
+ switch (node.type) {
1367
+ case "Reference":
1368
+ target.add(node.name);
1369
+ break;
1370
+ case "Unary":
1371
+ collectExpressionReferences(node.operand, target);
1372
+ break;
1373
+ case "Binary":
1374
+ collectExpressionReferences(node.left, target);
1375
+ collectExpressionReferences(node.right, target);
1376
+ break;
1377
+ case "Function":
1378
+ node.args.forEach((arg) => collectExpressionReferences(arg, target));
1379
+ break;
1380
+ default:
1381
+ break;
1382
+ }
1383
+ return target;
1384
+ }
1385
+ function mapExpression(node, visit) {
1386
+ visit(node);
1387
+ switch (node.type) {
1388
+ case "Unary":
1389
+ mapExpression(node.operand, visit);
1390
+ break;
1391
+ case "Binary":
1392
+ mapExpression(node.left, visit);
1393
+ mapExpression(node.right, visit);
1394
+ break;
1395
+ case "Function":
1396
+ node.args.forEach((arg) => mapExpression(arg, visit));
1397
+ break;
1398
+ default:
1399
+ break;
1400
+ }
1401
+ }
1402
+
1403
+ // src/expressionFunctions.ts
1404
+ var import_metadata2 = require("@vizij/node-graph-wasm/metadata");
1405
+ var FUNCTION_CONFIGS = [
1406
+ { typeId: "sin", names: ["sin"] },
1407
+ { typeId: "cos", names: ["cos"] },
1408
+ { typeId: "tan", names: ["tan"] },
1409
+ { typeId: "power", names: ["power", "pow"] },
1410
+ { typeId: "log", names: ["log"] },
1411
+ { typeId: "clamp", names: ["clamp"] },
1412
+ { typeId: "add", names: ["add"] },
1413
+ { typeId: "multiply", names: ["multiply"] },
1414
+ { typeId: "subtract", names: ["subtract"] },
1415
+ { typeId: "divide", names: ["divide"] },
1416
+ {
1417
+ typeId: "greaterthan",
1418
+ names: ["greaterthan", "gt"],
1419
+ minArgs: 2,
1420
+ maxArgs: 2
1421
+ },
1422
+ { typeId: "lessthan", names: ["lessthan", "lt"], minArgs: 2, maxArgs: 2 },
1423
+ { typeId: "equal", names: ["equal", "eq"], minArgs: 2, maxArgs: 2 },
1424
+ {
1425
+ typeId: "notequal",
1426
+ names: ["notequal", "neq", "ne"],
1427
+ minArgs: 2,
1428
+ maxArgs: 2
1429
+ },
1430
+ { typeId: "and", names: ["and"], minArgs: 2, maxArgs: 2 },
1431
+ { typeId: "or", names: ["or"], minArgs: 2, maxArgs: 2 },
1432
+ { typeId: "xor", names: ["xor"], minArgs: 2, maxArgs: 2 },
1433
+ { typeId: "not", names: ["not"], minArgs: 1, maxArgs: 1 },
1434
+ { typeId: "if", names: ["if"], minArgs: 2, maxArgs: 3 },
1435
+ { typeId: "time", names: ["time"], minArgs: 0, maxArgs: 0 },
1436
+ { typeId: "oscillator", names: ["oscillator"], minArgs: 2, maxArgs: 2 }
1437
+ ];
1438
+ var SCALAR_FUNCTIONS = /* @__PURE__ */ new Map();
1439
+ for (const config of FUNCTION_CONFIGS) {
1440
+ const signature = (0, import_metadata2.requireNodeSignature)(config.typeId);
1441
+ const signatureInputs = signature.inputs;
1442
+ const inputs = signatureInputs.map((input) => ({
1443
+ id: input.id,
1444
+ optional: Boolean(input.optional)
1445
+ }));
1446
+ const variadic = signature.variadic_inputs ? {
1447
+ id: signature.variadic_inputs.id,
1448
+ min: signature.variadic_inputs.min,
1449
+ max: signature.variadic_inputs.max ?? null
1450
+ } : null;
1451
+ const derivedMin = variadic ? variadic.min : inputs.filter((input) => !input.optional).length;
1452
+ const derivedMax = variadic ? variadic.max : inputs.length;
1453
+ const minArgs = config.minArgs ?? derivedMin;
1454
+ const maxArgs = config.maxArgs !== void 0 ? config.maxArgs : derivedMax ?? null;
1455
+ const definition = {
1456
+ nodeType: config.typeId,
1457
+ inputs,
1458
+ variadic,
1459
+ minArgs,
1460
+ maxArgs
1461
+ };
1462
+ const names = new Set(
1463
+ [config.typeId, ...config.names].map((name) => name.toLowerCase())
1464
+ );
1465
+ names.forEach((name) => {
1466
+ SCALAR_FUNCTIONS.set(name, definition);
1467
+ });
1468
+ }
1469
+
1470
+ // src/graphBuilder.ts
1471
+ function evaluateBinding({
1472
+ binding,
1473
+ target,
1474
+ targetId,
1475
+ animatableId,
1476
+ component,
1477
+ safeId,
1478
+ context,
1479
+ selfNodeId
1480
+ }) {
1481
+ const { nodes, edges, ensureInputNode, bindingIssues, summaryBindings } = context;
1482
+ const exprContext = {
1483
+ componentSafeId: safeId,
1484
+ nodes,
1485
+ edges,
1486
+ constants: /* @__PURE__ */ new Map(),
1487
+ counter: 0
1488
+ };
1489
+ const targetValueType = target.valueType === "vector" ? "vector" : "scalar";
1490
+ const aliasNodes = /* @__PURE__ */ new Map();
1491
+ const slotSummaries = [];
1492
+ const expressionIssues = [];
1493
+ const rawExpression = typeof binding.expression === "string" ? binding.expression : "";
1494
+ const trimmedExpression = rawExpression.trim();
1495
+ let hasActiveSlot = false;
1496
+ binding.slots.forEach((slot, index) => {
1497
+ const aliasBase = slot.alias?.trim() ?? "";
1498
+ const fallbackAlias = `s${index + 1}`;
1499
+ const alias = aliasBase.length > 0 ? aliasBase : fallbackAlias;
1500
+ const slotId = slot.id && slot.id.length > 0 ? slot.id : alias;
1501
+ const slotValueType = slot.valueType === "vector" ? "vector" : "scalar";
1502
+ let slotOutputId;
1503
+ if (slot.inputId === import_utils3.SELF_BINDING_ID) {
1504
+ if (selfNodeId) {
1505
+ slotOutputId = selfNodeId;
1506
+ hasActiveSlot = true;
1507
+ } else {
1508
+ expressionIssues.push("Self reference unavailable for this input.");
1509
+ slotOutputId = getConstantNodeId(exprContext, target.defaultValue);
1510
+ }
1511
+ } else if (slot.inputId) {
1512
+ const inputNode = ensureInputNode(slot.inputId);
1513
+ if (inputNode) {
1514
+ const remapNodeId = `remap_${safeId}_${sanitizeNodeId(slotId)}`;
1515
+ nodes.push({
1516
+ id: remapNodeId,
1517
+ type: "centered_remap",
1518
+ input_defaults: {
1519
+ in_low: slot.remap.inLow,
1520
+ in_anchor: slot.remap.inAnchor,
1521
+ in_high: slot.remap.inHigh,
1522
+ out_low: slot.remap.outLow,
1523
+ out_anchor: slot.remap.outAnchor,
1524
+ out_high: slot.remap.outHigh
1525
+ }
1526
+ });
1527
+ edges.push({
1528
+ from: { node_id: inputNode.nodeId },
1529
+ to: { node_id: remapNodeId, input: "in" }
1530
+ });
1531
+ slotOutputId = remapNodeId;
1532
+ hasActiveSlot = true;
1533
+ } else {
1534
+ expressionIssues.push(`Missing standard input "${slot.inputId}".`);
1535
+ slotOutputId = getConstantNodeId(exprContext, 0);
1536
+ }
1537
+ } else {
1538
+ slotOutputId = getConstantNodeId(exprContext, 0);
1539
+ }
1540
+ aliasNodes.set(alias, slotOutputId);
1541
+ slotSummaries.push({
1542
+ targetId,
1543
+ animatableId,
1544
+ component,
1545
+ slotId,
1546
+ slotAlias: alias,
1547
+ inputId: slot.inputId ?? null,
1548
+ remap: { ...slot.remap },
1549
+ expression: trimmedExpression,
1550
+ valueType: slotValueType
1551
+ });
1552
+ });
1553
+ if (slotSummaries.length === 0) {
1554
+ const alias = PRIMARY_SLOT_ALIAS;
1555
+ aliasNodes.set(alias, getConstantNodeId(exprContext, 0));
1556
+ slotSummaries.push({
1557
+ targetId,
1558
+ animatableId,
1559
+ component,
1560
+ slotId: PRIMARY_SLOT_ID,
1561
+ slotAlias: alias,
1562
+ inputId: null,
1563
+ remap: createDefaultRemap(target),
1564
+ expression: trimmedExpression,
1565
+ valueType: targetValueType
1566
+ });
1567
+ }
1568
+ const defaultAlias = slotSummaries[0]?.slotAlias ?? PRIMARY_SLOT_ALIAS;
1569
+ const expressionText = trimmedExpression.length > 0 ? trimmedExpression : defaultAlias;
1570
+ const parseResult = parseControlExpression(expressionText);
1571
+ let expressionAst = null;
1572
+ if (parseResult.node && parseResult.errors.length === 0) {
1573
+ const references = collectExpressionReferences(parseResult.node);
1574
+ const missing = [];
1575
+ references.forEach((ref) => {
1576
+ if (!aliasNodes.has(ref)) {
1577
+ missing.push(ref);
1578
+ }
1579
+ });
1580
+ if (missing.length === 0) {
1581
+ expressionAst = parseResult.node;
1582
+ } else {
1583
+ missing.forEach((ref) => {
1584
+ expressionIssues.push(`Unknown control "${ref}".`);
1585
+ });
1586
+ }
1587
+ } else {
1588
+ parseResult.errors.forEach((error) => {
1589
+ expressionIssues.push(error.message);
1590
+ });
1591
+ }
1592
+ let valueNodeId = null;
1593
+ if (expressionAst) {
1594
+ valueNodeId = materializeExpression(
1595
+ expressionAst,
1596
+ exprContext,
1597
+ aliasNodes,
1598
+ expressionIssues
1599
+ );
1600
+ }
1601
+ if (!valueNodeId) {
1602
+ const fallbackAlias = aliasNodes.has(defaultAlias) ? defaultAlias : aliasNodes.keys().next().value;
1603
+ valueNodeId = (fallbackAlias ? aliasNodes.get(fallbackAlias) : void 0) ?? getConstantNodeId(exprContext, 0);
1604
+ }
1605
+ const issuesCopy = expressionIssues.length ? [...new Set(expressionIssues)] : void 0;
1606
+ slotSummaries.forEach((summary) => {
1607
+ summary.expression = expressionText;
1608
+ if (issuesCopy && issuesCopy.length > 0) {
1609
+ summary.issues = issuesCopy;
1610
+ const issueSet = bindingIssues.get(summary.targetId) ?? /* @__PURE__ */ new Set();
1611
+ issuesCopy.forEach((issue) => issueSet.add(issue));
1612
+ bindingIssues.set(summary.targetId, issueSet);
1613
+ }
1614
+ });
1615
+ summaryBindings.push(...slotSummaries);
1616
+ return {
1617
+ valueNodeId,
1618
+ hasActiveSlot
1619
+ };
1620
+ }
1621
+ function sanitizeNodeId(value) {
1622
+ return value.replace(/[^a-zA-Z0-9_]/g, "_");
1623
+ }
1624
+ function buildRigInputPath(faceId, inputPath) {
1625
+ const trimmed = inputPath.startsWith("/") ? inputPath.slice(1) : inputPath;
1626
+ return `rig/${faceId}/${trimmed}`;
1627
+ }
1628
+ function getComponentOrder(animatable) {
1629
+ switch (animatable.type) {
1630
+ case "vector2":
1631
+ return ["x", "y"];
1632
+ case "vector3":
1633
+ case "euler":
1634
+ return ["x", "y", "z"];
1635
+ case "rgb":
1636
+ return ["r", "g", "b"];
1637
+ default:
1638
+ return null;
1639
+ }
1640
+ }
1641
+ function isComponentRecord(value) {
1642
+ return typeof value === "object" && value !== null;
1643
+ }
1644
+ function componentIndex(component) {
1645
+ switch (component) {
1646
+ case "x":
1647
+ case "r":
1648
+ return 0;
1649
+ case "y":
1650
+ case "g":
1651
+ return 1;
1652
+ default:
1653
+ return 2;
1654
+ }
1655
+ }
1656
+ function extractComponentDefault(value, component) {
1657
+ if (typeof value === "number") {
1658
+ return value;
1659
+ }
1660
+ if (Array.isArray(value)) {
1661
+ const candidate = value[componentIndex(component)];
1662
+ if (typeof candidate === "number" && Number.isFinite(candidate)) {
1663
+ return candidate;
1664
+ }
1665
+ return 0;
1666
+ }
1667
+ if (value && typeof value === "object") {
1668
+ if (!isComponentRecord(value)) {
1669
+ return 0;
1670
+ }
1671
+ const direct = value[component];
1672
+ if (typeof direct === "number" && Number.isFinite(direct)) {
1673
+ return direct;
1674
+ }
1675
+ const alt = component === "x" ? value.r : component === "y" ? value.g : value.b;
1676
+ if (typeof alt === "number" && Number.isFinite(alt)) {
1677
+ return alt;
1678
+ }
1679
+ }
1680
+ return 0;
1681
+ }
1682
+ function getConstantNodeId(context, value) {
1683
+ const key = Number.isFinite(value) ? value.toString() : "NaN";
1684
+ const existing = context.constants.get(key);
1685
+ if (existing) {
1686
+ return existing;
1687
+ }
1688
+ const nodeId = `const_${context.componentSafeId}_${context.constants.size + 1}`;
1689
+ context.nodes.push({
1690
+ id: nodeId,
1691
+ type: "constant",
1692
+ params: {
1693
+ value: Number.isFinite(value) ? value : 0
1694
+ }
1695
+ });
1696
+ context.constants.set(key, nodeId);
1697
+ return nodeId;
1698
+ }
1699
+ function createBinaryOperationNode(context, operator, leftId, rightId) {
1700
+ const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
1701
+ context.nodes.push({
1702
+ id: nodeId,
1703
+ type: operator
1704
+ });
1705
+ const leftInput = operator === "subtract" || operator === "divide" ? "lhs" : "operand_1";
1706
+ const rightInput = operator === "subtract" || operator === "divide" ? "rhs" : "operand_2";
1707
+ context.edges.push({
1708
+ from: { node_id: leftId },
1709
+ to: { node_id: nodeId, input: leftInput }
1710
+ });
1711
+ context.edges.push({
1712
+ from: { node_id: rightId },
1713
+ to: { node_id: nodeId, input: rightInput }
1714
+ });
1715
+ return nodeId;
1716
+ }
1717
+ function createVariadicOperationNode(context, operator, operandIds) {
1718
+ if (operandIds.length === 0) {
1719
+ return getConstantNodeId(context, operator === "add" ? 0 : 1);
1720
+ }
1721
+ if (operandIds.length === 1) {
1722
+ return operandIds[0];
1723
+ }
1724
+ const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
1725
+ context.nodes.push({
1726
+ id: nodeId,
1727
+ type: operator
1728
+ });
1729
+ operandIds.forEach((operandId, index) => {
1730
+ context.edges.push({
1731
+ from: { node_id: operandId },
1732
+ to: { node_id: nodeId, input: `operand_${index + 1}` }
1733
+ });
1734
+ });
1735
+ return nodeId;
1736
+ }
1737
+ function createNamedOperationNode(context, operator, inputNames, operandIds) {
1738
+ const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
1739
+ context.nodes.push({
1740
+ id: nodeId,
1741
+ type: operator
1742
+ });
1743
+ inputNames.forEach((inputName, index) => {
1744
+ const operandId = operandIds[index];
1745
+ context.edges.push({
1746
+ from: { node_id: operandId },
1747
+ to: { node_id: nodeId, input: inputName }
1748
+ });
1749
+ });
1750
+ return nodeId;
1751
+ }
1752
+ function emitScalarFunctionNode(definition, operands, context) {
1753
+ if (definition.variadic) {
1754
+ const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
1755
+ context.nodes.push({
1756
+ id: nodeId,
1757
+ type: definition.nodeType
1758
+ });
1759
+ operands.forEach((operandId, index) => {
1760
+ context.edges.push({
1761
+ from: { node_id: operandId },
1762
+ to: {
1763
+ node_id: nodeId,
1764
+ input: `${definition.variadic.id}_${index + 1}`
1765
+ }
1766
+ });
1767
+ });
1768
+ return nodeId;
1769
+ }
1770
+ const providedNames = [];
1771
+ const providedOperands = [];
1772
+ definition.inputs.forEach((input, index) => {
1773
+ const operandId = operands[index];
1774
+ if (!operandId) {
1775
+ return;
1776
+ }
1777
+ providedNames.push(input.id);
1778
+ providedOperands.push(operandId);
1779
+ });
1780
+ return createNamedOperationNode(
1781
+ context,
1782
+ definition.nodeType,
1783
+ providedNames,
1784
+ providedOperands
1785
+ );
1786
+ }
1787
+ function applyBindingOperators(operators, baseNodeId, safeId, nodes, edges) {
1788
+ let currentNodeId = baseNodeId;
1789
+ operators.forEach((operator, index) => {
1790
+ if (!operator.enabled) {
1791
+ return;
1792
+ }
1793
+ let definition;
1794
+ try {
1795
+ definition = getBindingOperatorDefinition(operator.type);
1796
+ } catch {
1797
+ return;
1798
+ }
1799
+ const nodeId = `${operator.type}_${safeId}_${index + 1}`;
1800
+ const params = {};
1801
+ definition.params.forEach((param) => {
1802
+ const configured = operator.params?.[param.id];
1803
+ params[param.id] = typeof configured === "number" ? configured : param.defaultValue;
1804
+ });
1805
+ nodes.push({
1806
+ id: nodeId,
1807
+ type: definition.nodeType,
1808
+ params
1809
+ });
1810
+ const inputId = definition.inputs[0] ?? "in";
1811
+ edges.push({
1812
+ from: { node_id: currentNodeId },
1813
+ to: { node_id: nodeId, input: inputId }
1814
+ });
1815
+ currentNodeId = nodeId;
1816
+ });
1817
+ return currentNodeId;
1818
+ }
1819
+ function collectOperands(node, operator, target) {
1820
+ if (node.type === "Binary" && node.operator === operator) {
1821
+ collectOperands(node.left, operator, target);
1822
+ collectOperands(node.right, operator, target);
1823
+ return;
1824
+ }
1825
+ target.push(node);
1826
+ }
1827
+ var BINARY_FUNCTION_OPERATOR_MAP = {
1828
+ ">": "greaterthan",
1829
+ "<": "lessthan",
1830
+ "==": "equal",
1831
+ "!=": "notequal",
1832
+ "&&": "and",
1833
+ "||": "or"
1834
+ };
1835
+ function materializeExpression(node, context, aliasNodes, issues) {
1836
+ switch (node.type) {
1837
+ case "Literal": {
1838
+ return getConstantNodeId(context, node.value);
1839
+ }
1840
+ case "Reference": {
1841
+ const mapped = aliasNodes.get(node.name);
1842
+ if (!mapped) {
1843
+ issues.push(`Unknown control "${node.name}".`);
1844
+ return getConstantNodeId(context, 0);
1845
+ }
1846
+ return mapped;
1847
+ }
1848
+ case "Unary": {
1849
+ const operandId = materializeExpression(
1850
+ node.operand,
1851
+ context,
1852
+ aliasNodes,
1853
+ issues
1854
+ );
1855
+ switch (node.operator) {
1856
+ case "+":
1857
+ return operandId;
1858
+ case "-": {
1859
+ const negativeOne = getConstantNodeId(context, -1);
1860
+ return createVariadicOperationNode(context, "multiply", [
1861
+ negativeOne,
1862
+ operandId
1863
+ ]);
1864
+ }
1865
+ case "!": {
1866
+ const definition = SCALAR_FUNCTIONS.get("not");
1867
+ if (!definition) {
1868
+ issues.push('Function "not" is not available in metadata.');
1869
+ return getConstantNodeId(context, 0);
1870
+ }
1871
+ return emitScalarFunctionNode(definition, [operandId], context);
1872
+ }
1873
+ default:
1874
+ issues.push("Unsupported unary operator.");
1875
+ return operandId;
1876
+ }
1877
+ }
1878
+ case "Binary": {
1879
+ const operator = node.operator;
1880
+ if (operator === "+") {
1881
+ const children = [];
1882
+ collectOperands(node, "+", children);
1883
+ const operandIds = children.map(
1884
+ (child) => materializeExpression(child, context, aliasNodes, issues)
1885
+ );
1886
+ return createVariadicOperationNode(context, "add", operandIds);
1887
+ }
1888
+ if (operator === "*") {
1889
+ const children = [];
1890
+ collectOperands(node, "*", children);
1891
+ const operandIds = children.map(
1892
+ (child) => materializeExpression(child, context, aliasNodes, issues)
1893
+ );
1894
+ return createVariadicOperationNode(context, "multiply", operandIds);
1895
+ }
1896
+ const leftId = materializeExpression(
1897
+ node.left,
1898
+ context,
1899
+ aliasNodes,
1900
+ issues
1901
+ );
1902
+ const rightId = materializeExpression(
1903
+ node.right,
1904
+ context,
1905
+ aliasNodes,
1906
+ issues
1907
+ );
1908
+ if (operator === "-") {
1909
+ return createBinaryOperationNode(context, "subtract", leftId, rightId);
1910
+ }
1911
+ if (operator === "/") {
1912
+ return createBinaryOperationNode(context, "divide", leftId, rightId);
1913
+ }
1914
+ const mappedFunction = BINARY_FUNCTION_OPERATOR_MAP[operator];
1915
+ if (mappedFunction) {
1916
+ const definition = SCALAR_FUNCTIONS.get(mappedFunction);
1917
+ if (!definition) {
1918
+ issues.push(`Function "${mappedFunction}" is not available.`);
1919
+ return getConstantNodeId(context, 0);
1920
+ }
1921
+ return emitScalarFunctionNode(definition, [leftId, rightId], context);
1922
+ }
1923
+ issues.push(`Unsupported operator "${operator}".`);
1924
+ return getConstantNodeId(context, 0);
1925
+ }
1926
+ case "Function": {
1927
+ const name = node.name;
1928
+ const normalized = name.toLowerCase();
1929
+ const definition = SCALAR_FUNCTIONS.get(normalized);
1930
+ if (!definition) {
1931
+ issues.push(`Unknown function "${name}".`);
1932
+ return getConstantNodeId(context, 0);
1933
+ }
1934
+ const operands = node.args.map(
1935
+ (arg) => materializeExpression(arg, context, aliasNodes, issues)
1936
+ );
1937
+ if (operands.length < definition.minArgs) {
1938
+ issues.push(
1939
+ `Function "${name}" expects at least ${definition.minArgs} arguments, received ${operands.length}.`
1940
+ );
1941
+ return getConstantNodeId(context, 0);
1942
+ }
1943
+ if (definition.maxArgs !== null && operands.length > definition.maxArgs) {
1944
+ issues.push(
1945
+ `Function "${name}" expects at most ${definition.maxArgs} arguments, received ${operands.length}.`
1946
+ );
1947
+ return getConstantNodeId(context, 0);
1948
+ }
1949
+ return emitScalarFunctionNode(definition, operands, context);
1950
+ }
1951
+ default: {
1952
+ issues.push("Unsupported expression node.");
1953
+ return getConstantNodeId(context, 0);
1954
+ }
1955
+ }
1956
+ }
1957
+ function buildRigGraphSpec({
1958
+ faceId,
1959
+ animatables,
1960
+ components,
1961
+ bindings,
1962
+ inputsById,
1963
+ inputBindings
1964
+ }) {
1965
+ const nodes = [];
1966
+ const edges = [];
1967
+ const inputNodes = /* @__PURE__ */ new Map();
1968
+ const buildingDerived = /* @__PURE__ */ new Set();
1969
+ const computedInputs = /* @__PURE__ */ new Set();
1970
+ const summaryBindings = [];
1971
+ const bindingIssues = /* @__PURE__ */ new Map();
1972
+ const animatableEntries = /* @__PURE__ */ new Map();
1973
+ const outputs = /* @__PURE__ */ new Set();
1974
+ const ensureInputNode = (inputId) => {
1975
+ const existing = inputNodes.get(inputId);
1976
+ if (existing) {
1977
+ return existing;
1978
+ }
1979
+ const input = inputsById.get(inputId);
1980
+ if (!input) {
1981
+ return null;
1982
+ }
1983
+ const defaultValue = Number.isFinite(input.defaultValue) ? input.defaultValue : 0;
1984
+ const inputBindingRaw = inputBindings[inputId];
1985
+ if (inputBindingRaw) {
1986
+ if (buildingDerived.has(inputId)) {
1987
+ const issueSet = bindingIssues.get(inputId) ?? /* @__PURE__ */ new Set();
1988
+ issueSet.add("Derived input cycle detected.");
1989
+ bindingIssues.set(inputId, issueSet);
1990
+ return null;
1991
+ }
1992
+ buildingDerived.add(inputId);
1993
+ try {
1994
+ const target = bindingTargetFromInput(input);
1995
+ const binding = ensureBindingStructure(inputBindingRaw, target);
1996
+ const requiresSelf = binding.inputId === import_utils3.SELF_BINDING_ID || binding.slots.some((slot) => slot.inputId === import_utils3.SELF_BINDING_ID);
1997
+ let selfNodeId;
1998
+ if (requiresSelf) {
1999
+ const sliderNodeId = `input_raw_${sanitizeNodeId(inputId)}`;
2000
+ nodes.push({
2001
+ id: sliderNodeId,
2002
+ type: "input",
2003
+ params: {
2004
+ path: buildRigInputPath(faceId, input.path),
2005
+ value: { float: defaultValue }
2006
+ }
2007
+ });
2008
+ selfNodeId = sliderNodeId;
2009
+ }
2010
+ const { valueNodeId, hasActiveSlot } = evaluateBinding({
2011
+ binding,
2012
+ target,
2013
+ targetId: inputId,
2014
+ animatableId: inputId,
2015
+ component: void 0,
2016
+ safeId: sanitizeNodeId(inputId),
2017
+ context: {
2018
+ nodes,
2019
+ edges,
2020
+ ensureInputNode,
2021
+ bindingIssues,
2022
+ summaryBindings
2023
+ },
2024
+ selfNodeId
2025
+ });
2026
+ if (!valueNodeId || !hasActiveSlot) {
2027
+ const constNodeId = `derived_default_${sanitizeNodeId(inputId)}`;
2028
+ nodes.push({
2029
+ id: constNodeId,
2030
+ type: "constant",
2031
+ params: {
2032
+ value: input.defaultValue
2033
+ }
2034
+ });
2035
+ const record3 = { nodeId: constNodeId, input };
2036
+ inputNodes.set(inputId, record3);
2037
+ return record3;
2038
+ }
2039
+ computedInputs.add(inputId);
2040
+ const record2 = { nodeId: valueNodeId, input };
2041
+ inputNodes.set(inputId, record2);
2042
+ return record2;
2043
+ } finally {
2044
+ buildingDerived.delete(inputId);
2045
+ }
2046
+ }
2047
+ const nodeId = `input_${sanitizeNodeId(inputId)}`;
2048
+ nodes.push({
2049
+ id: nodeId,
2050
+ type: "input",
2051
+ params: {
2052
+ path: buildRigInputPath(faceId, input.path),
2053
+ value: { float: defaultValue }
2054
+ }
2055
+ });
2056
+ const record = { nodeId, input };
2057
+ inputNodes.set(inputId, record);
2058
+ return record;
2059
+ };
2060
+ const ensureAnimatableEntry = (animatableId) => {
2061
+ const existing = animatableEntries.get(animatableId);
2062
+ if (existing) {
2063
+ return existing;
2064
+ }
2065
+ const animatable = animatables[animatableId];
2066
+ if (!animatable) {
2067
+ return null;
2068
+ }
2069
+ const entry = {
2070
+ animatable,
2071
+ values: /* @__PURE__ */ new Map(),
2072
+ defaults: /* @__PURE__ */ new Map(),
2073
+ isDriven: false
2074
+ };
2075
+ animatableEntries.set(animatableId, entry);
2076
+ return entry;
2077
+ };
2078
+ components.forEach((component) => {
2079
+ const bindingRaw = bindings[component.id];
2080
+ const target = bindingTargetFromComponent(component);
2081
+ const binding = bindingRaw ? ensureBindingStructure(bindingRaw, target) : null;
2082
+ const entry = ensureAnimatableEntry(component.animatableId);
2083
+ if (!entry) {
2084
+ return;
2085
+ }
2086
+ const key = component.component ?? "scalar";
2087
+ let valueNodeId = null;
2088
+ let hasActiveSlot = false;
2089
+ if (binding) {
2090
+ const { valueNodeId: producedNodeId, hasActiveSlot: active } = evaluateBinding({
2091
+ binding,
2092
+ target,
2093
+ targetId: component.id,
2094
+ animatableId: component.animatableId,
2095
+ component: component.component,
2096
+ safeId: component.safeId,
2097
+ context: {
2098
+ nodes,
2099
+ edges,
2100
+ ensureInputNode,
2101
+ bindingIssues,
2102
+ summaryBindings
2103
+ }
2104
+ });
2105
+ valueNodeId = producedNodeId;
2106
+ hasActiveSlot = active;
2107
+ if (active) {
2108
+ entry.isDriven = true;
2109
+ }
2110
+ } else {
2111
+ summaryBindings.push({
2112
+ targetId: component.id,
2113
+ animatableId: component.animatableId,
2114
+ component: component.component,
2115
+ slotId: PRIMARY_SLOT_ID,
2116
+ slotAlias: PRIMARY_SLOT_ALIAS,
2117
+ inputId: null,
2118
+ remap: createDefaultRemap(target),
2119
+ expression: PRIMARY_SLOT_ALIAS,
2120
+ valueType: target.valueType === "vector" ? "vector" : "scalar",
2121
+ issues: ["Binding not found."]
2122
+ });
2123
+ const fallbackIssues = bindingIssues.get(component.id) ?? /* @__PURE__ */ new Set();
2124
+ fallbackIssues.add("Binding not found.");
2125
+ bindingIssues.set(component.id, fallbackIssues);
2126
+ }
2127
+ if (!valueNodeId || !hasActiveSlot) {
2128
+ entry.defaults.set(key, component.defaultValue);
2129
+ return;
2130
+ }
2131
+ const operatorList = binding ? binding.operators ?? [] : [];
2132
+ const finalNodeId = applyBindingOperators(
2133
+ operatorList,
2134
+ valueNodeId,
2135
+ component.safeId,
2136
+ nodes,
2137
+ edges
2138
+ );
2139
+ entry.values.set(key, finalNodeId);
2140
+ });
2141
+ inputsById.forEach((_input, inputId) => {
2142
+ ensureInputNode(inputId);
2143
+ });
2144
+ animatableEntries.forEach((entry, animatableId) => {
2145
+ if (!entry.isDriven) {
2146
+ return;
2147
+ }
2148
+ outputs.add(animatableId);
2149
+ const safeId = sanitizeNodeId(animatableId);
2150
+ const order = getComponentOrder(entry.animatable);
2151
+ if (!order) {
2152
+ const valueNodeId = entry.values.get("scalar");
2153
+ if (!valueNodeId) {
2154
+ return;
2155
+ }
2156
+ const outputNodeId2 = `out_${safeId}`;
2157
+ nodes.push({
2158
+ id: outputNodeId2,
2159
+ type: "output",
2160
+ params: {
2161
+ path: animatableId
2162
+ }
2163
+ });
2164
+ edges.push({
2165
+ from: { node_id: valueNodeId },
2166
+ to: { node_id: outputNodeId2, input: "in" }
2167
+ });
2168
+ return;
2169
+ }
2170
+ const joinNodeId = `join_${safeId}`;
2171
+ nodes.push({
2172
+ id: joinNodeId,
2173
+ type: "join"
2174
+ });
2175
+ order.forEach((componentKey, index) => {
2176
+ let sourceId = entry.values.get(componentKey);
2177
+ if (!sourceId) {
2178
+ const componentDefault = entry.defaults.get(componentKey) ?? extractComponentDefault(
2179
+ (0, import_utils2.buildAnimatableValue)(entry.animatable, void 0),
2180
+ componentKey
2181
+ );
2182
+ const constNodeId = `const_${safeId}_${componentKey}`;
2183
+ nodes.push({
2184
+ id: constNodeId,
2185
+ type: "constant",
2186
+ params: {
2187
+ value: componentDefault
2188
+ }
2189
+ });
2190
+ sourceId = constNodeId;
2191
+ }
2192
+ edges.push({
2193
+ from: { node_id: sourceId },
2194
+ to: { node_id: joinNodeId, input: `operand_${index + 1}` }
2195
+ });
2196
+ });
2197
+ const outputNodeId = `out_${safeId}`;
2198
+ nodes.push({
2199
+ id: outputNodeId,
2200
+ type: "output",
2201
+ params: {
2202
+ path: animatableId
2203
+ }
2204
+ });
2205
+ edges.push({
2206
+ from: { node_id: joinNodeId },
2207
+ to: { node_id: outputNodeId, input: "in" }
2208
+ });
2209
+ });
2210
+ const nodeById = /* @__PURE__ */ new Map();
2211
+ nodes.forEach((node) => {
2212
+ nodeById.set(node.id, node);
2213
+ });
2214
+ const constantUsage = /* @__PURE__ */ new Map();
2215
+ edges.forEach((edge) => {
2216
+ const source = nodeById.get(edge.from.node_id);
2217
+ if (source?.type === "constant") {
2218
+ constantUsage.set(source.id, (constantUsage.get(source.id) ?? 0) + 1);
2219
+ }
2220
+ });
2221
+ const updatedEdges = [];
2222
+ const constantsToRemove = /* @__PURE__ */ new Set();
2223
+ edges.forEach((edge) => {
2224
+ const source = nodeById.get(edge.from.node_id);
2225
+ if (source?.type === "constant" && constantUsage.get(source.id) === 1 && source.params && Object.prototype.hasOwnProperty.call(source.params, "value")) {
2226
+ const target = nodeById.get(edge.to.node_id);
2227
+ if (target) {
2228
+ const value = source.params.value;
2229
+ if (value !== void 0) {
2230
+ target.input_defaults = {
2231
+ ...target.input_defaults ?? {},
2232
+ [edge.to.input]: value
2233
+ };
2234
+ nodeById.set(target.id, target);
2235
+ constantsToRemove.add(source.id);
2236
+ return;
2237
+ }
2238
+ }
2239
+ }
2240
+ updatedEdges.push(edge);
2241
+ });
2242
+ const filteredNodes = nodes.filter((node) => !constantsToRemove.has(node.id)).map((node) => nodeById.get(node.id) ?? node);
2243
+ const remapDefaultIssues = validateRemapDefaults(filteredNodes);
2244
+ const dynamicOutputs = Array.from(outputs);
2245
+ const computedInputList = Array.from(computedInputs);
2246
+ const filteredSummaryBindings = summaryBindings.filter(
2247
+ (binding) => outputs.has(binding.animatableId) || computedInputs.has(binding.animatableId)
2248
+ );
2249
+ const spec = {
2250
+ nodes: filteredNodes,
2251
+ edges: updatedEdges.length ? updatedEdges : void 0
2252
+ };
2253
+ const baseSpec = spec;
2254
+ const specWithMetadata = {
2255
+ ...baseSpec,
2256
+ metadata: {
2257
+ ...baseSpec.metadata,
2258
+ vizij: {
2259
+ faceId,
2260
+ inputs: Array.from(inputsById.values()).map((input) => ({
2261
+ id: input.id,
2262
+ path: input.path,
2263
+ sourceId: input.sourceId,
2264
+ label: input.label,
2265
+ group: input.group,
2266
+ defaultValue: input.defaultValue,
2267
+ range: {
2268
+ min: input.range.min,
2269
+ max: input.range.max
2270
+ }
2271
+ })),
2272
+ bindings: filteredSummaryBindings.map((binding) => ({
2273
+ ...binding,
2274
+ remap: { ...binding.remap },
2275
+ expression: binding.expression,
2276
+ valueType: binding.valueType,
2277
+ issues: binding.issues ? [...binding.issues] : void 0
2278
+ }))
2279
+ }
2280
+ }
2281
+ };
2282
+ const issuesByTarget = {};
2283
+ bindingIssues.forEach((issues, targetId) => {
2284
+ if (issues.size === 0) {
2285
+ return;
2286
+ }
2287
+ issuesByTarget[targetId] = Array.from(issues);
2288
+ });
2289
+ const fatalIssues = /* @__PURE__ */ new Set();
2290
+ Object.values(issuesByTarget).forEach((issues) => {
2291
+ issues.forEach((issue) => fatalIssues.add(issue));
2292
+ });
2293
+ remapDefaultIssues.forEach((issue) => fatalIssues.add(issue));
2294
+ return {
2295
+ spec: specWithMetadata,
2296
+ summary: {
2297
+ faceId,
2298
+ inputs: Array.from(inputNodes.values()).map(
2299
+ ({ input }) => buildRigInputPath(faceId, input.path)
2300
+ ),
2301
+ outputs: [...dynamicOutputs, ...computedInputList],
2302
+ bindings: filteredSummaryBindings
2303
+ },
2304
+ issues: {
2305
+ byTarget: issuesByTarget,
2306
+ fatal: Array.from(fatalIssues)
2307
+ }
2308
+ };
2309
+ }
2310
+ function validateRemapDefaults(nodes) {
2311
+ const issues = [];
2312
+ nodes.forEach((node) => {
2313
+ if (node.type !== "centered_remap") {
2314
+ return;
2315
+ }
2316
+ const defaults = node.input_defaults ?? {};
2317
+ [
2318
+ "in_low",
2319
+ "in_anchor",
2320
+ "in_high",
2321
+ "out_low",
2322
+ "out_anchor",
2323
+ "out_high"
2324
+ ].forEach((key) => {
2325
+ const value = defaults[key];
2326
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2327
+ issues.push(`Remap node ${node.id} missing ${key} default.`);
2328
+ }
2329
+ });
2330
+ });
2331
+ return issues;
2332
+ }
2333
+ // Annotate the CommonJS export names for ESM import in node:
2334
+ 0 && (module.exports = {
2335
+ PRIMARY_SLOT_ALIAS,
2336
+ PRIMARY_SLOT_ID,
2337
+ addBindingSlot,
2338
+ bindingFromDefinition,
2339
+ bindingOperatorDefinitions,
2340
+ bindingOperatorTypes,
2341
+ bindingTargetFromComponent,
2342
+ bindingTargetFromInput,
2343
+ bindingToDefinition,
2344
+ buildRigGraphSpec,
2345
+ collectExpressionReferences,
2346
+ createDefaultBinding,
2347
+ createDefaultBindings,
2348
+ createDefaultInputValues,
2349
+ createDefaultOperatorSettings,
2350
+ createDefaultParentBinding,
2351
+ createDefaultRemap,
2352
+ ensureBindingStructure,
2353
+ ensureOperatorParams,
2354
+ getBindingOperatorDefinition,
2355
+ getPrimaryBindingSlot,
2356
+ mapExpression,
2357
+ parseControlExpression,
2358
+ reconcileBindings,
2359
+ remapValue,
2360
+ removeBindingSlot,
2361
+ setBindingOperatorEnabled,
2362
+ updateBindingExpression,
2363
+ updateBindingOperatorParam,
2364
+ updateBindingSlotAlias,
2365
+ updateBindingSlotRemap,
2366
+ updateBindingWithInput
2367
+ });