@imperosoft/cris-webui-components 1.1.4-beta.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -55,6 +55,7 @@ function isTouchActive() {
55
55
 
56
56
  // src/components/CrisButton.tsx
57
57
  import { jsx, jsxs } from "react/jsx-runtime";
58
+ var MIN_PRESS_VISUAL_MS = 120;
58
59
  function CrisButton({
59
60
  join,
60
61
  joinFeedback,
@@ -87,6 +88,12 @@ function CrisButton({
87
88
  children,
88
89
  onPress,
89
90
  onRelease,
91
+ onTap,
92
+ onLongPress,
93
+ holdMs = 2e3,
94
+ onHoldRepeat,
95
+ repeatMs = 150,
96
+ onHoldProgress,
90
97
  debug = false
91
98
  }) {
92
99
  const debugRef = useRef(debug);
@@ -104,6 +111,12 @@ function CrisButton({
104
111
  const touchMovedRef = useRef(false);
105
112
  const touchPressTimerRef = useRef(null);
106
113
  const touchPressedRef = useRef(false);
114
+ const holdTimerRef = useRef(null);
115
+ const repeatIntervalRef = useRef(null);
116
+ const progressRafRef = useRef(null);
117
+ const pressStartRef = useRef(0);
118
+ const gestureFiredRef = useRef(false);
119
+ const pressVisualTimerRef = useRef(null);
107
120
  const feedbackJoin = joinFeedback ?? join;
108
121
  const feedback = useDigital(feedbackJoin ?? 0);
109
122
  const enabledJoin = useDigital(joinEnable ?? 0);
@@ -119,6 +132,12 @@ function CrisButton({
119
132
  setPressed(false);
120
133
  touchingRef.current = false;
121
134
  touchStartedHereRef.current = false;
135
+ if (pressVisualTimerRef.current) {
136
+ clearTimeout(pressVisualTimerRef.current);
137
+ pressVisualTimerRef.current = null;
138
+ }
139
+ clearGestureTimers();
140
+ onHoldProgress?.(0);
122
141
  if (join != null && smartId == null) {
123
142
  log("sending release via dSet(false)");
124
143
  dSet(join, false);
@@ -131,6 +150,22 @@ function CrisButton({
131
150
  clearTimeout(touchPressTimerRef.current);
132
151
  touchPressTimerRef.current = null;
133
152
  }
153
+ if (holdTimerRef.current) {
154
+ clearTimeout(holdTimerRef.current);
155
+ holdTimerRef.current = null;
156
+ }
157
+ if (repeatIntervalRef.current) {
158
+ clearInterval(repeatIntervalRef.current);
159
+ repeatIntervalRef.current = null;
160
+ }
161
+ if (progressRafRef.current != null) {
162
+ cancelAnimationFrame(progressRafRef.current);
163
+ progressRafRef.current = null;
164
+ }
165
+ if (pressVisualTimerRef.current) {
166
+ clearTimeout(pressVisualTimerRef.current);
167
+ pressVisualTimerRef.current = null;
168
+ }
134
169
  if (pressedRef.current && join != null && smartId == null) {
135
170
  log("UNMOUNT RELEASE - component unmounting while pressed");
136
171
  dSet(join, false);
@@ -146,6 +181,44 @@ function CrisButton({
146
181
  } else if (hasControlFeedback && textSelected != null) {
147
182
  currentText = textSelected;
148
183
  }
184
+ const clearGestureTimers = () => {
185
+ if (holdTimerRef.current) {
186
+ clearTimeout(holdTimerRef.current);
187
+ holdTimerRef.current = null;
188
+ }
189
+ if (repeatIntervalRef.current) {
190
+ clearInterval(repeatIntervalRef.current);
191
+ repeatIntervalRef.current = null;
192
+ }
193
+ if (progressRafRef.current != null) {
194
+ cancelAnimationFrame(progressRafRef.current);
195
+ progressRafRef.current = null;
196
+ }
197
+ };
198
+ const armGestures = () => {
199
+ gestureFiredRef.current = false;
200
+ pressStartRef.current = Date.now();
201
+ if (onLongPress) {
202
+ holdTimerRef.current = setTimeout(() => {
203
+ holdTimerRef.current = null;
204
+ gestureFiredRef.current = true;
205
+ onLongPress();
206
+ }, holdMs);
207
+ }
208
+ if (onHoldProgress) {
209
+ const tick = () => {
210
+ const p = Math.min(1, (Date.now() - pressStartRef.current) / holdMs);
211
+ onHoldProgress(p);
212
+ progressRafRef.current = p < 1 ? requestAnimationFrame(tick) : null;
213
+ };
214
+ progressRafRef.current = requestAnimationFrame(tick);
215
+ }
216
+ if (onHoldRepeat) {
217
+ repeatIntervalRef.current = setInterval(() => {
218
+ onHoldRepeat();
219
+ }, repeatMs);
220
+ }
221
+ };
149
222
  const handlePress = () => {
150
223
  log("handlePress called", { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });
151
224
  if (suppressKeyClicks) {
@@ -158,18 +231,24 @@ function CrisButton({
158
231
  }
159
232
  pressedRef.current = true;
160
233
  setPressed(true);
234
+ pressStartRef.current = Date.now();
235
+ if (pressVisualTimerRef.current) {
236
+ clearTimeout(pressVisualTimerRef.current);
237
+ pressVisualTimerRef.current = null;
238
+ }
161
239
  if (!isEnabled) {
162
240
  log("SKIPPED dSet: not enabled");
163
241
  return;
164
242
  }
165
243
  onPress?.();
244
+ armGestures();
166
245
  if (join != null && smartId == null) {
167
246
  log("SENDING PRESS via dSet(true)");
168
247
  dSet(join, true);
169
248
  }
170
249
  };
171
- const handleRelease = () => {
172
- log("handleRelease called", { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });
250
+ const handleRelease = (clean = true) => {
251
+ log("handleRelease called", { clean, suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });
173
252
  if (suppressKeyClicks) {
174
253
  log("BLOCKED: suppressKeyClicks");
175
254
  return;
@@ -179,7 +258,17 @@ function CrisButton({
179
258
  return;
180
259
  }
181
260
  pressedRef.current = false;
182
- setPressed(false);
261
+ const heldMs = Date.now() - pressStartRef.current;
262
+ if (heldMs >= MIN_PRESS_VISUAL_MS) {
263
+ setPressed(false);
264
+ } else {
265
+ pressVisualTimerRef.current = setTimeout(() => {
266
+ pressVisualTimerRef.current = null;
267
+ setPressed(false);
268
+ }, MIN_PRESS_VISUAL_MS - heldMs);
269
+ }
270
+ clearGestureTimers();
271
+ onHoldProgress?.(0);
183
272
  if (!isEnabled) {
184
273
  log("SKIPPED dSet: not enabled");
185
274
  return;
@@ -189,6 +278,9 @@ function CrisButton({
189
278
  log("SENDING RELEASE via dSet(false)");
190
279
  dSet(join, false);
191
280
  }
281
+ if (clean && onTap && !gestureFiredRef.current) {
282
+ onTap();
283
+ }
192
284
  };
193
285
  const SCROLL_THRESHOLD = 8;
194
286
  const PRESS_DELAY = 80;
@@ -224,7 +316,7 @@ function CrisButton({
224
316
  cancelPressTimer();
225
317
  if (touchPressedRef.current) {
226
318
  touchPressedRef.current = false;
227
- handleRelease();
319
+ handleRelease(false);
228
320
  log("touchMove: scroll detected after press, releasing");
229
321
  } else {
230
322
  log("touchMove: scroll detected, press cancelled");
@@ -245,11 +337,11 @@ function CrisButton({
245
337
  touchStartedHereRef.current = false;
246
338
  if (touchPressedRef.current) {
247
339
  touchPressedRef.current = false;
248
- handleRelease();
340
+ handleRelease(true);
249
341
  } else {
250
342
  touchPressedRef.current = false;
251
343
  handlePress();
252
- handleRelease();
344
+ handleRelease(true);
253
345
  }
254
346
  };
255
347
  const handleTouchCancel = () => {
@@ -260,7 +352,7 @@ function CrisButton({
260
352
  touchStartedHereRef.current = false;
261
353
  if (touchPressedRef.current) {
262
354
  touchPressedRef.current = false;
263
- handleRelease();
355
+ handleRelease(false);
264
356
  }
265
357
  touchMovedRef.current = false;
266
358
  };
@@ -270,11 +362,11 @@ function CrisButton({
270
362
  };
271
363
  const handleMouseUp = () => {
272
364
  if (isTouchActive() || touchingRef.current) return;
273
- handleRelease();
365
+ handleRelease(true);
274
366
  };
275
367
  const handleMouseLeave = () => {
276
368
  if (isTouchActive() || touchingRef.current) return;
277
- handleRelease();
369
+ handleRelease(false);
278
370
  };
279
371
  if (!isVisible) return null;
280
372
  const classes = [
@@ -450,6 +542,10 @@ function CrisSlider({
450
542
  trackSizePercent = 20,
451
543
  thumbSizePercent = 4,
452
544
  delayMsAfterDragUpdateFeedback = 1e3,
545
+ value,
546
+ onChange,
547
+ onCommit,
548
+ changeThrottleMs = 100,
453
549
  className = "",
454
550
  style,
455
551
  barClassName = "",
@@ -469,15 +565,27 @@ function CrisSlider({
469
565
  const aSet = useJoinsStore2((state) => state.aSet);
470
566
  const [ratioCurrent, setRatioCurrent] = useState2(0);
471
567
  const [isDragging, setIsDragging] = useState2(false);
568
+ const draggingRef = useRef2(false);
472
569
  const isDraggingOrJustAfterRef = useRef2(false);
473
570
  const ratioBeforeDragRef = useRef2(0);
474
571
  const afterDragTimeoutRef = useRef2(null);
475
572
  const touchingRef = useRef2(false);
573
+ const changeTimerRef = useRef2(null);
574
+ const lastChangeAtRef = useRef2(0);
575
+ const latestValueRef = useRef2(0);
576
+ const touchStartXRef = useRef2(0);
577
+ const touchStartYRef = useRef2(0);
578
+ const touchDecidedRef = useRef2(false);
579
+ const valueRef = useRef2(value);
580
+ valueRef.current = value;
581
+ const analogValueRef = useRef2(analogValue);
582
+ analogValueRef.current = analogValue;
476
583
  const isEnabled = joinEnable == null ? true : enabled;
477
584
  const isVisible = joinVisible == null ? true : visible;
478
585
  useEffect2(() => {
479
586
  if (!isVisible && isDraggingOrJustAfterRef.current) {
480
587
  setIsDragging(false);
588
+ draggingRef.current = false;
481
589
  isDraggingOrJustAfterRef.current = false;
482
590
  touchingRef.current = false;
483
591
  if (effectiveDigitalJoin != null) {
@@ -487,16 +595,24 @@ function CrisSlider({
487
595
  }, [isVisible, effectiveDigitalJoin, dSet]);
488
596
  useEffect2(() => {
489
597
  return () => {
598
+ if (changeTimerRef.current != null) {
599
+ clearTimeout(changeTimerRef.current);
600
+ changeTimerRef.current = null;
601
+ }
602
+ if (afterDragTimeoutRef.current !== null) {
603
+ window.clearTimeout(afterDragTimeoutRef.current);
604
+ afterDragTimeoutRef.current = null;
605
+ }
490
606
  if (isDraggingOrJustAfterRef.current && effectiveDigitalJoin != null) {
491
607
  dSet(effectiveDigitalJoin, false);
492
608
  }
493
609
  };
494
610
  }, [effectiveDigitalJoin, dSet]);
495
611
  const analogToRatio = useCallback2(
496
- (value) => {
612
+ (value2) => {
497
613
  const range = maxValue - minValue;
498
614
  if (range <= 0) return 0;
499
- return Math.max(0, Math.min(1, (value - minValue) / range));
615
+ return Math.max(0, Math.min(1, (value2 - minValue) / range));
500
616
  },
501
617
  [minValue, maxValue]
502
618
  );
@@ -508,16 +624,17 @@ function CrisSlider({
508
624
  [minValue, maxValue]
509
625
  );
510
626
  const updateFromFeedback = useCallback2(() => {
511
- if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {
512
- setRatioCurrent(analogToRatio(analogValue));
513
- }
514
- }, [analogValue, analogToRatio, effectiveAnalogJoin]);
627
+ if (isDraggingOrJustAfterRef.current) return;
628
+ const v = valueRef.current !== void 0 ? valueRef.current : effectiveAnalogJoin != null ? analogValueRef.current : void 0;
629
+ if (v !== void 0) setRatioCurrent(analogToRatio(v));
630
+ }, [analogToRatio, effectiveAnalogJoin]);
515
631
  useEffect2(() => {
516
632
  updateFromFeedback();
517
- }, [updateFromFeedback]);
633
+ }, [value, analogValue, updateFromFeedback]);
518
634
  const handleDragStart = useCallback2(() => {
519
635
  if (!isEnabled) return;
520
636
  setIsDragging(true);
637
+ draggingRef.current = true;
521
638
  if (effectiveDigitalJoin != null) {
522
639
  dSet(effectiveDigitalJoin, true);
523
640
  }
@@ -531,11 +648,17 @@ function CrisSlider({
531
648
  }
532
649
  }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);
533
650
  const handleDragEnd = useCallback2(() => {
534
- if (!isDragging) return;
651
+ if (!draggingRef.current) return;
652
+ draggingRef.current = false;
535
653
  setIsDragging(false);
536
654
  if (effectiveDigitalJoin != null) {
537
655
  dSet(effectiveDigitalJoin, false);
538
656
  }
657
+ if (changeTimerRef.current != null) {
658
+ clearTimeout(changeTimerRef.current);
659
+ changeTimerRef.current = null;
660
+ }
661
+ onCommit?.(latestValueRef.current);
539
662
  if (delayMsAfterDragUpdateFeedback > 0) {
540
663
  afterDragTimeoutRef.current = window.setTimeout(() => {
541
664
  isDraggingOrJustAfterRef.current = false;
@@ -546,10 +669,10 @@ function CrisSlider({
546
669
  isDraggingOrJustAfterRef.current = false;
547
670
  updateFromFeedback();
548
671
  }
549
- }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);
672
+ }, [effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback, onCommit]);
550
673
  const handleMove = useCallback2(
551
674
  (clientX, clientY, bounds) => {
552
- if (!isDragging) return;
675
+ if (!draggingRef.current) return;
553
676
  let newRatio;
554
677
  if (horizontal) {
555
678
  newRatio = (clientX - bounds.left) / bounds.width;
@@ -558,11 +681,31 @@ function CrisSlider({
558
681
  }
559
682
  newRatio = Math.max(0, Math.min(1, newRatio));
560
683
  setRatioCurrent(newRatio);
684
+ const outValue = ratioToAnalog(newRatio);
685
+ latestValueRef.current = outValue;
561
686
  if (effectiveAnalogJoin != null) {
562
- aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));
687
+ aSet(effectiveAnalogJoin, outValue);
688
+ }
689
+ if (onChange) {
690
+ const now = Date.now();
691
+ const wait = changeThrottleMs - (now - lastChangeAtRef.current);
692
+ if (wait <= 0) {
693
+ if (changeTimerRef.current != null) {
694
+ clearTimeout(changeTimerRef.current);
695
+ changeTimerRef.current = null;
696
+ }
697
+ lastChangeAtRef.current = now;
698
+ onChange(outValue);
699
+ } else if (changeTimerRef.current == null) {
700
+ changeTimerRef.current = window.setTimeout(() => {
701
+ changeTimerRef.current = null;
702
+ lastChangeAtRef.current = Date.now();
703
+ onChange(latestValueRef.current);
704
+ }, wait);
705
+ }
563
706
  }
564
707
  },
565
- [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]
708
+ [horizontal, effectiveAnalogJoin, aSet, ratioToAnalog, onChange, changeThrottleMs]
566
709
  );
567
710
  const handleMouseDown = (event) => {
568
711
  if (isTouchActive() || touchingRef.current) return;
@@ -572,7 +715,7 @@ function CrisSlider({
572
715
  handleMove(event.clientX, event.clientY, bounds);
573
716
  };
574
717
  const handleMouseMove = (event) => {
575
- if (!isDragging) return;
718
+ if (!draggingRef.current) return;
576
719
  const bounds = event.currentTarget.getBoundingClientRect();
577
720
  handleMove(event.clientX, event.clientY, bounds);
578
721
  };
@@ -580,38 +723,58 @@ function CrisSlider({
580
723
  handleDragEnd();
581
724
  };
582
725
  const handleMouseLeave = (event) => {
583
- if (isDragging) {
726
+ if (draggingRef.current) {
584
727
  const bounds = event.currentTarget.getBoundingClientRect();
585
728
  handleMove(event.clientX, event.clientY, bounds);
586
729
  }
587
730
  handleDragEnd();
588
731
  };
732
+ const TOUCH_SLOP = 8;
589
733
  const handleTouchStart = (event) => {
590
734
  touchStart();
591
735
  touchingRef.current = true;
592
- handleDragStart();
593
- const bounds = event.currentTarget.getBoundingClientRect();
594
- const touch = event.touches[0];
595
- handleMove(touch.clientX, touch.clientY, bounds);
736
+ const t = event.touches[0];
737
+ touchStartXRef.current = t.clientX;
738
+ touchStartYRef.current = t.clientY;
739
+ touchDecidedRef.current = false;
596
740
  };
597
741
  const handleTouchMove = (event) => {
598
- if (!isDragging) return;
599
- const bounds = event.currentTarget.getBoundingClientRect();
600
- const touch = event.touches[0];
601
- handleMove(touch.clientX, touch.clientY, bounds);
742
+ const t = event.touches[0];
743
+ if (!t) return;
744
+ if (draggingRef.current) {
745
+ handleMove(t.clientX, t.clientY, event.currentTarget.getBoundingClientRect());
746
+ return;
747
+ }
748
+ if (touchDecidedRef.current) return;
749
+ const dx = Math.abs(t.clientX - touchStartXRef.current);
750
+ const dy = Math.abs(t.clientY - touchStartYRef.current);
751
+ if (dx < TOUCH_SLOP && dy < TOUCH_SLOP) return;
752
+ touchDecidedRef.current = true;
753
+ const along = horizontal ? dx : dy;
754
+ const cross = horizontal ? dy : dx;
755
+ if (along >= cross) {
756
+ handleDragStart();
757
+ handleMove(t.clientX, t.clientY, event.currentTarget.getBoundingClientRect());
758
+ }
602
759
  };
603
- const handleTouchEnd = () => {
760
+ const handleTouchEnd = (event) => {
604
761
  touchEnd();
605
- handleDragEnd();
762
+ if (draggingRef.current) {
763
+ handleDragEnd();
764
+ } else if (!touchDecidedRef.current) {
765
+ const t = event.changedTouches[0];
766
+ if (t) {
767
+ handleDragStart();
768
+ handleMove(t.clientX, t.clientY, event.currentTarget.getBoundingClientRect());
769
+ handleDragEnd();
770
+ }
771
+ }
772
+ touchDecidedRef.current = false;
606
773
  };
607
- const handleTouchCancel = (event) => {
774
+ const handleTouchCancel = () => {
608
775
  touchEnd();
609
- if (isDragging && event.touches.length > 0) {
610
- const bounds = event.currentTarget.getBoundingClientRect();
611
- const touch = event.touches[0];
612
- handleMove(touch.clientX, touch.clientY, bounds);
613
- }
614
- handleDragEnd();
776
+ touchDecidedRef.current = false;
777
+ if (draggingRef.current) handleDragEnd();
615
778
  };
616
779
  if (!isVisible) return null;
617
780
  const containerClasses = [
@@ -639,8 +802,10 @@ function CrisSlider({
639
802
  computedBarStyle.height = `${height}%`;
640
803
  computedBarStyle.top = `${top}%`;
641
804
  }
805
+ const dragLayerStyle = isDragging ? { willChange: "transform" } : {};
642
806
  const computedFillStyle = {
643
807
  ...fillStyle,
808
+ ...dragLayerStyle,
644
809
  position: "absolute"
645
810
  };
646
811
  if (horizontal) {
@@ -662,6 +827,7 @@ function CrisSlider({
662
827
  }
663
828
  const computedThumbStyle = {
664
829
  ...thumbStyle,
830
+ ...dragLayerStyle,
665
831
  position: "absolute"
666
832
  };
667
833
  if (horizontal) {
@@ -685,7 +851,9 @@ function CrisSlider({
685
851
  ...style,
686
852
  position: "relative",
687
853
  cursor: isEnabled ? "pointer" : "default",
688
- touchAction: "none",
854
+ // Let the browser scroll the cross-axis (so a horizontal swipe over a vertical
855
+ // fader scrolls the row); the slider handles drags along its own axis.
856
+ touchAction: horizontal ? "pan-y" : "pan-x",
689
857
  userSelect: "none"
690
858
  },
691
859
  onMouseDown: handleMouseDown,
@@ -2120,6 +2288,654 @@ function CrisCoMatrixListsTie({
2120
2288
  }
2121
2289
  );
2122
2290
  }
2291
+
2292
+ // src/components/CrisViewComm.tsx
2293
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2294
+ var ETH = /* @__PURE__ */ new Set(["tcp", "udp", "ssh", "ws"]);
2295
+ function commIndicators(c) {
2296
+ const isEth = !!c?.kd && ETH.has(c.kd);
2297
+ return {
2298
+ eth: { visible: isEth || c?.kd === "native", on: !!c?.co },
2299
+ serial: { visible: c?.kd === "serial" || isEth && !!c?.so, on: !!c?.al }
2300
+ };
2301
+ }
2302
+ var COMM_DEFAULTS = {
2303
+ root: "flex items-center gap-[0.4em]",
2304
+ dot: "w-[2.2em] h-[2.2em] rounded-full flex items-center justify-center",
2305
+ dotOn: "bg-[#22c55e]",
2306
+ dotOff: "bg-[#dc2626]",
2307
+ icon: ""
2308
+ };
2309
+ function isNode(v) {
2310
+ return v !== void 0 && typeof v !== "string";
2311
+ }
2312
+ function IndicatorDot({ icon, on, iconScale, cls }) {
2313
+ const iconStyle = {
2314
+ width: iconScale,
2315
+ height: iconScale,
2316
+ filter: "brightness(0) invert(1)",
2317
+ opacity: on ? 1 : 0.5
2318
+ };
2319
+ return /* @__PURE__ */ jsx11("div", { className: `${cls.dot} ${on ? cls.dotOn : cls.dotOff}`, children: isNode(icon) ? icon : /* @__PURE__ */ jsx11("img", { className: cls.icon, src: getIconUrl(icon), alt: "", draggable: false, style: iconStyle }) });
2320
+ }
2321
+ function CrisViewComm({ comm, classes, icons, className }) {
2322
+ const cls = {
2323
+ root: classes?.root ?? COMM_DEFAULTS.root,
2324
+ dot: classes?.dot ?? COMM_DEFAULTS.dot,
2325
+ dotOn: classes?.dotOn ?? COMM_DEFAULTS.dotOn,
2326
+ dotOff: classes?.dotOff ?? COMM_DEFAULTS.dotOff,
2327
+ icon: classes?.icon ?? COMM_DEFAULTS.icon
2328
+ };
2329
+ const ethIcon = icons?.ethernet ?? "ind-ethernet";
2330
+ const serialIcon = icons?.serial ?? "rs232";
2331
+ const { eth, serial } = commIndicators(comm);
2332
+ if (!eth.visible && !serial.visible) return null;
2333
+ return /* @__PURE__ */ jsxs9("div", { className: `${cls.root} ${className ?? ""}`, children: [
2334
+ eth.visible && /* @__PURE__ */ jsx11(IndicatorDot, { icon: ethIcon, on: eth.on, iconScale: "85%", cls }),
2335
+ serial.visible && /* @__PURE__ */ jsx11(IndicatorDot, { icon: serialIcon, on: serial.on, iconScale: "95%", cls })
2336
+ ] });
2337
+ }
2338
+
2339
+ // src/components/dsp/CrisViewDspFull.tsx
2340
+ import { useState as useState6 } from "react";
2341
+ import { useCustomObject as useCustomObject4, useCustomObjectSend as useCustomObjectSend4 } from "@imperosoft/cris-webui-ch5-core";
2342
+
2343
+ // src/components/dsp/DspIoPage.tsx
2344
+ import { useRef as useRef4 } from "react";
2345
+
2346
+ // src/components/dsp/dspModel.ts
2347
+ function levelToPercent(lv) {
2348
+ return Math.round(clampLevel(lv) / 65535 * 100);
2349
+ }
2350
+ function clampLevel(lv) {
2351
+ if (!Number.isFinite(lv)) return 0;
2352
+ return Math.max(0, Math.min(65535, lv));
2353
+ }
2354
+ function normalizeLinked(raw) {
2355
+ return {
2356
+ label: raw.lb ?? "",
2357
+ channels: raw.ch ?? []
2358
+ };
2359
+ }
2360
+ function collapseStrips(channels, links) {
2361
+ const byId = new Map(channels.map((c) => [c.id, c]));
2362
+ const grouped = /* @__PURE__ */ new Set();
2363
+ const groups = [];
2364
+ for (const raw of links ?? []) {
2365
+ const { label, channels: members } = normalizeLinked(raw);
2366
+ if (members.length === 0) continue;
2367
+ members.forEach((id) => grouped.add(id));
2368
+ const primary = members.find((id) => byId.has(id));
2369
+ const ref = primary !== void 0 ? byId.get(primary) : void 0;
2370
+ groups.push({
2371
+ kind: "group",
2372
+ key: `g:${members.join("-")}`,
2373
+ label: label || ref?.lb || `Grupo ${members[0]}`,
2374
+ channels: members,
2375
+ lv: clampLevel(ref?.lv),
2376
+ mt: ref?.mt ?? false
2377
+ });
2378
+ }
2379
+ const singles = channels.filter((c) => !grouped.has(c.id)).map((c) => ({
2380
+ kind: "single",
2381
+ key: `c:${c.id}`,
2382
+ label: c.lb ?? `Canal ${c.id}`,
2383
+ channels: [c.id],
2384
+ lv: clampLevel(c.lv),
2385
+ mt: c.mt
2386
+ }));
2387
+ return [...singles, ...groups];
2388
+ }
2389
+ function linksFor(ln, io) {
2390
+ return io === "in" ? ln?.ip : ln?.op;
2391
+ }
2392
+ function buildMixerAxis(channels, links) {
2393
+ const items = channels.map((c) => ({
2394
+ id: c.id,
2395
+ channelLabel: c.lb ?? `${c.id}`
2396
+ }));
2397
+ const indexById = new Map(channels.map((c, idx) => [c.id, idx]));
2398
+ for (const raw of links ?? []) {
2399
+ const { label, channels: members } = normalizeLinked(raw);
2400
+ if (members.length < 2) continue;
2401
+ const sorted = [...members].sort((a, b) => a - b);
2402
+ const consecutiveNumbers = sorted.every((n, k) => k === 0 || n === sorted[k - 1] + 1);
2403
+ if (!consecutiveNumbers) continue;
2404
+ const idxs = sorted.map((n) => indexById.get(n));
2405
+ if (idxs.some((x) => x === void 0)) continue;
2406
+ const indices = idxs;
2407
+ const consecutiveIdx = indices.every((x, k) => k === 0 || x === indices[k - 1] + 1);
2408
+ if (!consecutiveIdx) continue;
2409
+ const startIdx = indices[0];
2410
+ items[startIdx].groupStart = true;
2411
+ items[startIdx].groupSpan = indices.length;
2412
+ for (const idx of indices) items[idx].groupCommon = label;
2413
+ }
2414
+ return items;
2415
+ }
2416
+
2417
+ // src/components/dsp/DspChannelStrip.tsx
2418
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2419
+ function DspChannelStrip({ strip, io, oid, send, cls, icons }) {
2420
+ const primary = strip.channels[0];
2421
+ const sendLevel = (channel, value, queued) => send(oid, { action: "level.set", io, channel, value, queued });
2422
+ const streamLevel = (value) => {
2423
+ if (primary !== void 0) sendLevel(primary, value, false);
2424
+ };
2425
+ const commitLevel = (value) => {
2426
+ strip.channels.forEach((ch) => sendLevel(ch, value, true));
2427
+ };
2428
+ const toggleMute = () => {
2429
+ const value = !strip.mt;
2430
+ strip.channels.forEach(
2431
+ (channel) => send(oid, { action: "mute.set", io, channel, value, queued: false })
2432
+ );
2433
+ };
2434
+ return /* @__PURE__ */ jsxs10("div", { className: cls.strip, style: { minWidth: 0 }, children: [
2435
+ /* @__PURE__ */ jsx12("div", { className: "w-full h-[4.1em] flex items-center justify-center", children: /* @__PURE__ */ jsx12("span", { className: cls.stripLabel, title: strip.label, children: strip.label }) }),
2436
+ /* @__PURE__ */ jsxs10("span", { className: cls.levelReadout, children: [
2437
+ levelToPercent(strip.lv),
2438
+ "%"
2439
+ ] }),
2440
+ /* @__PURE__ */ jsx12(
2441
+ CrisSlider,
2442
+ {
2443
+ value: strip.lv,
2444
+ onChange: streamLevel,
2445
+ onCommit: commitLevel,
2446
+ minValue: 0,
2447
+ maxValue: 65535,
2448
+ thumbSizePercent: 7,
2449
+ className: "flex-1 w-full",
2450
+ barClassName: cls.faderBar,
2451
+ fillClassName: cls.faderFill,
2452
+ thumbClassName: cls.faderThumb
2453
+ }
2454
+ ),
2455
+ /* @__PURE__ */ jsx12("div", { className: "w-full h-[3.8em] shrink-0 mt-[0.6em]", children: /* @__PURE__ */ jsx12(
2456
+ CrisButton,
2457
+ {
2458
+ selected: strip.mt,
2459
+ onPress: toggleMute,
2460
+ iconName: icons.muteOff,
2461
+ iconNameActive: icons.muteOn,
2462
+ iconSize: "3.6em",
2463
+ iconStyle: { filter: icons.muteOffFilter },
2464
+ iconStyleActive: { filter: icons.muteOnFilter },
2465
+ className: cls.mute,
2466
+ classActive: cls.muteActive
2467
+ }
2468
+ ) })
2469
+ ] });
2470
+ }
2471
+
2472
+ // src/components/dsp/useHorizontalWheel.ts
2473
+ import { useEffect as useEffect5 } from "react";
2474
+ function useHorizontalWheel(ref) {
2475
+ useEffect5(() => {
2476
+ const el = ref.current;
2477
+ if (!el) return;
2478
+ const onWheel = (e) => {
2479
+ if (e.deltaY === 0) return;
2480
+ if (el.scrollWidth <= el.clientWidth) return;
2481
+ e.preventDefault();
2482
+ el.scrollLeft += e.deltaY;
2483
+ };
2484
+ el.addEventListener("wheel", onWheel, { passive: false });
2485
+ return () => el.removeEventListener("wheel", onWheel);
2486
+ }, [ref]);
2487
+ }
2488
+
2489
+ // src/components/dsp/DspIoPage.tsx
2490
+ import { jsx as jsx13 } from "react/jsx-runtime";
2491
+ function DspIoPage({ status, io, oid, send, skip, cls, icons, emptyLabel }) {
2492
+ const scrollRef = useRef4(null);
2493
+ useHorizontalWheel(scrollRef);
2494
+ const skipSet = new Set(skip ?? []);
2495
+ const raw = (io === "in" ? status?.ip : status?.op) ?? [];
2496
+ const channels = skipSet.size ? raw.filter((c) => !skipSet.has(c.id)) : raw;
2497
+ const allLinks = linksFor(status?.ln, io);
2498
+ const links = skipSet.size ? allLinks?.filter((g) => (g.ch ?? []).some((id) => !skipSet.has(id))) : allLinks;
2499
+ const strips = collapseStrips(channels, links);
2500
+ if (strips.length === 0) {
2501
+ return /* @__PURE__ */ jsx13("div", { className: cls.message, children: emptyLabel });
2502
+ }
2503
+ return /* @__PURE__ */ jsx13("div", { ref: scrollRef, className: "w-full h-full overflow-x-auto no-scrollbar", children: /* @__PURE__ */ jsx13(
2504
+ "div",
2505
+ {
2506
+ className: "flex h-full items-stretch gap-[0.3em] px-[0.5em] py-[0.5em]",
2507
+ style: { minWidth: "min-content" },
2508
+ children: strips.map((strip) => /* @__PURE__ */ jsx13(
2509
+ DspChannelStrip,
2510
+ {
2511
+ strip,
2512
+ io,
2513
+ oid,
2514
+ send,
2515
+ cls,
2516
+ icons
2517
+ },
2518
+ strip.key
2519
+ ))
2520
+ }
2521
+ ) });
2522
+ }
2523
+
2524
+ // src/components/dsp/DspMixer.tsx
2525
+ import { useRef as useRef5 } from "react";
2526
+ import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
2527
+ var COMMON_W = "9em";
2528
+ var CHAN_W = "3em";
2529
+ var COMMON_H = "2.4em";
2530
+ var CHAN_H = "2.1em";
2531
+ var CELL_W = "7.5em";
2532
+ var ROW_H = "4.5em";
2533
+ var DRAG_FACTOR = 0.6;
2534
+ function CrosspointOnIcon({ icon }) {
2535
+ if (typeof icon !== "string") return /* @__PURE__ */ jsx14(Fragment3, { children: icon });
2536
+ return /* @__PURE__ */ jsx14(
2537
+ "img",
2538
+ {
2539
+ src: getIconUrl(icon),
2540
+ alt: "",
2541
+ draggable: false,
2542
+ className: "pointer-events-none",
2543
+ style: { width: "4.4em", height: "4.4em", filter: "brightness(0) invert(1)" }
2544
+ }
2545
+ );
2546
+ }
2547
+ function DspMixer({ status, oid, send, cls, icons }) {
2548
+ const scrollRef = useRef5(null);
2549
+ const startRef = useRef5(null);
2550
+ const movedRef = useRef5(false);
2551
+ const thresholdRef = useRef5(8);
2552
+ const pendingRef = useRef5(null);
2553
+ const inputs = status?.ip ?? [];
2554
+ const outputs = status?.op ?? [];
2555
+ const isOn = (output, input) => outputs.find((o) => o.id === output)?.xp?.[input] === true;
2556
+ const onPointerDown = (e) => {
2557
+ const el = scrollRef.current;
2558
+ if (!el) return;
2559
+ movedRef.current = false;
2560
+ thresholdRef.current = parseFloat(getComputedStyle(el).fontSize || "16") * DRAG_FACTOR;
2561
+ startRef.current = { x: e.clientX, y: e.clientY, sl: el.scrollLeft, st: el.scrollTop };
2562
+ const cell = e.target.closest("[data-cell]");
2563
+ pendingRef.current = cell ? { input: Number(cell.dataset.in), output: Number(cell.dataset.out) } : null;
2564
+ el.setPointerCapture(e.pointerId);
2565
+ };
2566
+ const onPointerMove = (e) => {
2567
+ const el = scrollRef.current;
2568
+ const s = startRef.current;
2569
+ if (!el || !s) return;
2570
+ const dx = e.clientX - s.x;
2571
+ const dy = e.clientY - s.y;
2572
+ if (!movedRef.current && Math.hypot(dx, dy) > thresholdRef.current) movedRef.current = true;
2573
+ if (movedRef.current) {
2574
+ el.scrollLeft = s.sl - dx;
2575
+ el.scrollTop = s.st - dy;
2576
+ }
2577
+ };
2578
+ const onPointerUp = (e) => {
2579
+ const el = scrollRef.current;
2580
+ el?.releasePointerCapture?.(e.pointerId);
2581
+ if (!movedRef.current && pendingRef.current) {
2582
+ const { input, output } = pendingRef.current;
2583
+ send(oid, {
2584
+ action: "crosspoint.set",
2585
+ input,
2586
+ output,
2587
+ value: !isOn(output, input),
2588
+ queued: true
2589
+ });
2590
+ }
2591
+ startRef.current = null;
2592
+ pendingRef.current = null;
2593
+ };
2594
+ if (inputs.length === 0 || outputs.length === 0) {
2595
+ return /* @__PURE__ */ jsx14("div", { className: cls.message, children: "Sin crosspoints" });
2596
+ }
2597
+ const inAxis = buildMixerAxis(inputs, linksFor(status?.ln, "in"));
2598
+ const outAxis = buildMixerAxis(outputs, linksFor(status?.ln, "out"));
2599
+ return /* @__PURE__ */ jsx14(
2600
+ "div",
2601
+ {
2602
+ ref: scrollRef,
2603
+ onPointerDown,
2604
+ onPointerMove,
2605
+ onPointerUp,
2606
+ onPointerCancel: onPointerUp,
2607
+ className: "w-full h-full overflow-auto no-scrollbar relative touch-none select-none cursor-grab",
2608
+ children: /* @__PURE__ */ jsxs11(
2609
+ "div",
2610
+ {
2611
+ className: "grid",
2612
+ style: {
2613
+ // cols: [output common][output channel][inputs…] rows: [in common][in channel][outputs…]
2614
+ gridTemplateColumns: `${COMMON_W} ${CHAN_W} repeat(${inputs.length}, ${CELL_W})`,
2615
+ gridTemplateRows: `${COMMON_H} ${CHAN_H} repeat(${outputs.length}, ${ROW_H})`,
2616
+ minWidth: "min-content"
2617
+ },
2618
+ children: [
2619
+ /* @__PURE__ */ jsx14(
2620
+ "div",
2621
+ {
2622
+ className: cls.mixerCorner,
2623
+ style: { top: 0, left: 0, gridColumn: "1 / span 2", gridRow: "1 / span 2" },
2624
+ children: "OUT \\ IN"
2625
+ }
2626
+ ),
2627
+ inAxis.flatMap((it, ii) => {
2628
+ const col = 3 + ii;
2629
+ if (it.groupCommon !== void 0) {
2630
+ const nodes = [
2631
+ /* @__PURE__ */ jsx14(
2632
+ "div",
2633
+ {
2634
+ className: `${cls.mixerChrome} z-20 px-[0.2em]`,
2635
+ style: { top: 0, gridRow: "1 / span 2", gridColumn: col, alignItems: "flex-end", paddingBottom: "0.3em" },
2636
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-1", children: it.channelLabel })
2637
+ },
2638
+ `ich:${it.id}`
2639
+ )
2640
+ ];
2641
+ if (it.groupStart) {
2642
+ nodes.unshift(
2643
+ /* @__PURE__ */ jsx14(
2644
+ "div",
2645
+ {
2646
+ className: `${cls.mixerChrome} z-25 px-[0.2em] font-semibold`,
2647
+ style: { top: 0, gridRow: 1, gridColumn: `${col} / span ${it.groupSpan}` },
2648
+ title: it.groupCommon,
2649
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-2", children: it.groupCommon })
2650
+ },
2651
+ `icc:${it.id}`
2652
+ )
2653
+ );
2654
+ }
2655
+ return nodes;
2656
+ }
2657
+ return [
2658
+ /* @__PURE__ */ jsx14(
2659
+ "div",
2660
+ {
2661
+ className: `${cls.mixerChrome} z-20 px-[0.2em]`,
2662
+ style: { top: 0, gridRow: "1 / span 2", gridColumn: col, alignItems: "center" },
2663
+ title: it.channelLabel,
2664
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-3", children: it.channelLabel })
2665
+ },
2666
+ `is:${it.id}`
2667
+ )
2668
+ ];
2669
+ }),
2670
+ outAxis.flatMap((it, oo) => {
2671
+ const row = 3 + oo;
2672
+ if (it.groupCommon !== void 0) {
2673
+ const nodes = [
2674
+ /* @__PURE__ */ jsx14(
2675
+ "div",
2676
+ {
2677
+ className: `${cls.mixerChrome} z-20`,
2678
+ style: { left: 0, gridColumn: "1 / span 2", gridRow: row, justifyContent: "flex-end", paddingRight: "0.6em" },
2679
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-1", children: it.channelLabel })
2680
+ },
2681
+ `och:${it.id}`
2682
+ )
2683
+ ];
2684
+ if (it.groupStart) {
2685
+ nodes.unshift(
2686
+ /* @__PURE__ */ jsx14(
2687
+ "div",
2688
+ {
2689
+ className: `${cls.mixerChrome} z-25 justify-start px-[0.4em] font-semibold`,
2690
+ style: { left: 0, gridColumn: 1, gridRow: `${row} / span ${it.groupSpan}` },
2691
+ title: it.groupCommon,
2692
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-2 text-left", children: it.groupCommon })
2693
+ },
2694
+ `occ:${it.id}`
2695
+ )
2696
+ );
2697
+ }
2698
+ return nodes;
2699
+ }
2700
+ return [
2701
+ /* @__PURE__ */ jsx14(
2702
+ "div",
2703
+ {
2704
+ className: `${cls.mixerChrome} z-20 justify-start px-[0.4em]`,
2705
+ style: { left: 0, gridColumn: "1 / span 2", gridRow: row },
2706
+ title: it.channelLabel,
2707
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-2 text-left", children: it.channelLabel })
2708
+ },
2709
+ `os:${it.id}`
2710
+ )
2711
+ ];
2712
+ }),
2713
+ outputs.map(
2714
+ (o, oo) => inputs.map((i, ii) => {
2715
+ const on = isOn(o.id, i.id);
2716
+ return /* @__PURE__ */ jsx14(
2717
+ "div",
2718
+ {
2719
+ "data-cell": true,
2720
+ "data-in": i.id,
2721
+ "data-out": o.id,
2722
+ className: `${cls.cell} ${on ? cls.cellOn : cls.cellOff}`,
2723
+ style: { gridColumn: 3 + ii, gridRow: 3 + oo },
2724
+ children: on && /* @__PURE__ */ jsx14(CrosspointOnIcon, { icon: icons.crosspointOn })
2725
+ },
2726
+ `x:${i.id}:${o.id}`
2727
+ );
2728
+ })
2729
+ )
2730
+ ]
2731
+ }
2732
+ )
2733
+ }
2734
+ );
2735
+ }
2736
+
2737
+ // src/components/dsp/DspPresets.tsx
2738
+ import { useRef as useRef6, useState as useState5 } from "react";
2739
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
2740
+ var HOLD_MS = 2e3;
2741
+ var SAVED_MS = 1e3;
2742
+ function PresetButton({ num, name, active, onCall, onSave, cls }) {
2743
+ return /* @__PURE__ */ jsx15(
2744
+ CrisButton,
2745
+ {
2746
+ onTap: onCall,
2747
+ onLongPress: onSave,
2748
+ holdMs: HOLD_MS,
2749
+ selected: active,
2750
+ className: cls.preset,
2751
+ classActive: cls.presetActive,
2752
+ classPressed: cls.presetPressed,
2753
+ children: /* @__PURE__ */ jsxs12("span", { className: "flex items-center justify-center gap-[0.5em]", children: [
2754
+ /* @__PURE__ */ jsx15("span", { className: "text-[1.6em] font-bold leading-none", children: num }),
2755
+ /* @__PURE__ */ jsx15("span", { className: "text-[1.2em] leading-none", children: name })
2756
+ ] })
2757
+ }
2758
+ );
2759
+ }
2760
+ function DspPresets({
2761
+ oid,
2762
+ send,
2763
+ presets,
2764
+ activeDevice,
2765
+ activeLocal,
2766
+ sustainedFeedback = true,
2767
+ cls
2768
+ }) {
2769
+ const [saved, setSaved] = useState5(false);
2770
+ const savedTimer = useRef6(null);
2771
+ const call = (id) => send(oid, { action: "preset.call", value: id });
2772
+ const save = (id) => {
2773
+ send(oid, { action: "preset.save", value: id });
2774
+ setSaved(true);
2775
+ if (savedTimer.current !== null) clearTimeout(savedTimer.current);
2776
+ savedTimer.current = window.setTimeout(() => setSaved(false), SAVED_MS);
2777
+ };
2778
+ const isActive = (p) => {
2779
+ if (!sustainedFeedback) return false;
2780
+ return p.kind === "local" ? activeLocal === p.id : activeDevice === p.id;
2781
+ };
2782
+ return /* @__PURE__ */ jsxs12("div", { className: cls.presetWrap, children: [
2783
+ /* @__PURE__ */ jsx15("span", { className: cls.presetLabel, children: "Preset" }),
2784
+ /* @__PURE__ */ jsxs12("div", { className: "relative flex-1 flex items-stretch gap-[0.3em] mt-[0.25em]", children: [
2785
+ presets.map((p) => /* @__PURE__ */ jsx15(
2786
+ PresetButton,
2787
+ {
2788
+ num: p.id,
2789
+ name: p.name,
2790
+ active: isActive(p),
2791
+ onCall: () => call(p.id),
2792
+ onSave: p.saveAllowed === false ? void 0 : () => save(p.id),
2793
+ cls
2794
+ },
2795
+ p.id
2796
+ )),
2797
+ saved && /* @__PURE__ */ jsx15("div", { className: cls.savedFlash, children: "Saved" })
2798
+ ] })
2799
+ ] });
2800
+ }
2801
+
2802
+ // src/components/dsp/dspClasses.ts
2803
+ var DSP_CLASS_DEFAULTS = {
2804
+ root: "w-full h-full flex flex-col",
2805
+ header: "relative w-full flex items-end justify-center gap-[0.3em] px-[1em] pt-[0.5em]",
2806
+ content: "w-full flex-1 min-h-0",
2807
+ tab: "w-[10.5em] h-[3.25em] rounded-b-none rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white text-[1.4em] font-bold",
2808
+ tabActive: "bg-[#007ca0]",
2809
+ message: "w-full h-full flex items-center justify-center text-gray-400 text-[2em]",
2810
+ presetWrap: "absolute left-[1em] bottom-[0.3em] h-[4.5em] flex flex-col items-center",
2811
+ presetLabel: "text-[#4f5152] text-[1.4em] leading-none",
2812
+ preset: "w-[8em] h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
2813
+ presetActive: "bg-[#007ca0]",
2814
+ presetPressed: "bg-[#006080]",
2815
+ savedFlash: "absolute inset-0 flex items-center justify-center rounded bg-[#22c55e] text-white text-[1.2em] font-bold pointer-events-none",
2816
+ strip: "flex flex-col items-center gap-[0.4em] h-full w-[9em] flex-none px-[0.3em]",
2817
+ stripLabel: "text-[#4f5152] text-center leading-tight text-[1.1em] line-clamp-3",
2818
+ levelReadout: "text-[1.1em] text-[#4f5152] leading-none opacity-70 mt-[0.5em]",
2819
+ faderBar: "bg-[#c0c0c0] rounded",
2820
+ faderFill: "bg-[#007ca0] rounded",
2821
+ faderThumb: "bg-[#4f5152] rounded",
2822
+ mute: "w-full h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
2823
+ muteActive: "bg-[#dc2626] text-white",
2824
+ mixerCorner: "sticky z-30 flex items-center justify-center bg-[#282C34] text-[#4f5152] text-[1.1em] font-bold border border-black/30",
2825
+ mixerChrome: "sticky flex items-center justify-center text-center bg-[#282C34] text-white text-[1.1em] leading-tight border border-black/30",
2826
+ cell: "z-10 flex items-center justify-center border border-black/30",
2827
+ cellOn: "bg-[#22c55e]",
2828
+ cellOff: "bg-[#4f5152]"
2829
+ };
2830
+ var DSP_ICON_DEFAULTS = {
2831
+ crosspointOn: "audio-volume-ok",
2832
+ muteOff: "audio-volume-high",
2833
+ muteOn: "audio-volume-mute",
2834
+ muteOffFilter: "invert(65%) sepia(70%) saturate(500%) hue-rotate(80deg) brightness(110%) contrast(95%)",
2835
+ muteOnFilter: "brightness(0) invert(1)"
2836
+ };
2837
+ function mergeDefined(defaults4, overrides) {
2838
+ if (!overrides) return { ...defaults4 };
2839
+ const out = { ...defaults4 };
2840
+ Object.keys(overrides).forEach((k) => {
2841
+ const v = overrides[k];
2842
+ if (v !== void 0) out[k] = v;
2843
+ });
2844
+ return out;
2845
+ }
2846
+ function resolveDspClasses(classes) {
2847
+ return mergeDefined(DSP_CLASS_DEFAULTS, classes);
2848
+ }
2849
+ function resolveDspIcons(icons) {
2850
+ return mergeDefined(DSP_ICON_DEFAULTS, icons);
2851
+ }
2852
+
2853
+ // src/components/dsp/CrisViewDspFull.tsx
2854
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
2855
+ function CrisViewDspFull({
2856
+ oid,
2857
+ presets,
2858
+ skipShowInput,
2859
+ skipShowOutput,
2860
+ skipCrosspoints = false,
2861
+ sustainedPresetFeedback = true,
2862
+ classes,
2863
+ icons,
2864
+ className,
2865
+ initialTab = "in"
2866
+ }) {
2867
+ const safeInitial = initialTab === "mix" && skipCrosspoints ? "in" : initialTab;
2868
+ const [tab, setTab] = useState6(safeInitial);
2869
+ const status = useCustomObject4(oid, { subscribe: true });
2870
+ const send = useCustomObjectSend4();
2871
+ const cls = resolveDspClasses(classes);
2872
+ const ic = resolveDspIcons(icons);
2873
+ const tabs = [
2874
+ { id: "in", label: "Inputs" },
2875
+ ...skipCrosspoints ? [] : [{ id: "mix", label: "Mixer" }],
2876
+ { id: "out", label: "Outputs" }
2877
+ ];
2878
+ const hasPresets = !!presets && presets.length > 0;
2879
+ const activeDevice = status?.pr?.dv ?? status?.ps?.dv;
2880
+ const activeLocal = status?.pr?.lc ?? status?.ps?.lc;
2881
+ return /* @__PURE__ */ jsxs13("div", { className: `${cls.root} ${className ?? ""}`, children: [
2882
+ /* @__PURE__ */ jsxs13("div", { className: cls.header, children: [
2883
+ hasPresets && /* @__PURE__ */ jsx16(
2884
+ DspPresets,
2885
+ {
2886
+ oid,
2887
+ send,
2888
+ presets,
2889
+ activeDevice,
2890
+ activeLocal,
2891
+ sustainedFeedback: sustainedPresetFeedback,
2892
+ cls
2893
+ }
2894
+ ),
2895
+ tabs.map((t) => /* @__PURE__ */ jsx16(
2896
+ CrisButton,
2897
+ {
2898
+ text: t.label,
2899
+ selected: tab === t.id,
2900
+ onPress: () => setTab(t.id),
2901
+ className: cls.tab,
2902
+ classActive: cls.tabActive
2903
+ },
2904
+ t.id
2905
+ )),
2906
+ /* @__PURE__ */ jsx16(CrisViewComm, { comm: status?.cm, className: "absolute right-[1em] bottom-[0.3em] text-[1.25em]" })
2907
+ ] }),
2908
+ /* @__PURE__ */ jsx16("div", { className: cls.content, children: status === void 0 ? /* @__PURE__ */ jsx16("div", { className: cls.message, children: "Conectando\u2026" }) : /* @__PURE__ */ jsxs13(Fragment4, { children: [
2909
+ tab === "in" && /* @__PURE__ */ jsx16(
2910
+ DspIoPage,
2911
+ {
2912
+ status,
2913
+ io: "in",
2914
+ oid,
2915
+ send,
2916
+ skip: skipShowInput,
2917
+ cls,
2918
+ icons: ic,
2919
+ emptyLabel: "Sin entradas"
2920
+ }
2921
+ ),
2922
+ tab === "mix" && !skipCrosspoints && /* @__PURE__ */ jsx16(DspMixer, { status, oid, send, cls, icons: ic }),
2923
+ tab === "out" && /* @__PURE__ */ jsx16(
2924
+ DspIoPage,
2925
+ {
2926
+ status,
2927
+ io: "out",
2928
+ oid,
2929
+ send,
2930
+ skip: skipShowOutput,
2931
+ cls,
2932
+ icons: ic,
2933
+ emptyLabel: "Sin salidas"
2934
+ }
2935
+ )
2936
+ ] }) })
2937
+ ] });
2938
+ }
2123
2939
  export {
2124
2940
  CrisButton,
2125
2941
  CrisCoDebug,
@@ -2131,9 +2947,18 @@ export {
2131
2947
  CrisSpinner,
2132
2948
  CrisText,
2133
2949
  CrisTextInput,
2950
+ CrisViewComm,
2951
+ CrisViewDspFull,
2952
+ buildMixerAxis,
2953
+ clampLevel,
2954
+ collapseStrips,
2955
+ commIndicators,
2134
2956
  configureIcons,
2135
2957
  getIconConfig,
2136
2958
  getIconFilter,
2137
- getIconUrl
2959
+ getIconUrl,
2960
+ levelToPercent,
2961
+ linksFor,
2962
+ normalizeLinked
2138
2963
  };
2139
2964
  //# sourceMappingURL=index.mjs.map