@imperosoft/cris-webui-components 1.1.4 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,60 @@ 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;
773
+ touchingRef.current = false;
606
774
  };
607
- const handleTouchCancel = (event) => {
775
+ const handleTouchCancel = () => {
608
776
  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();
777
+ touchDecidedRef.current = false;
778
+ if (draggingRef.current) handleDragEnd();
779
+ touchingRef.current = false;
615
780
  };
616
781
  if (!isVisible) return null;
617
782
  const containerClasses = [
@@ -639,8 +804,10 @@ function CrisSlider({
639
804
  computedBarStyle.height = `${height}%`;
640
805
  computedBarStyle.top = `${top}%`;
641
806
  }
807
+ const dragLayerStyle = isDragging ? { willChange: "transform" } : {};
642
808
  const computedFillStyle = {
643
809
  ...fillStyle,
810
+ ...dragLayerStyle,
644
811
  position: "absolute"
645
812
  };
646
813
  if (horizontal) {
@@ -662,6 +829,7 @@ function CrisSlider({
662
829
  }
663
830
  const computedThumbStyle = {
664
831
  ...thumbStyle,
832
+ ...dragLayerStyle,
665
833
  position: "absolute"
666
834
  };
667
835
  if (horizontal) {
@@ -685,7 +853,9 @@ function CrisSlider({
685
853
  ...style,
686
854
  position: "relative",
687
855
  cursor: isEnabled ? "pointer" : "default",
688
- touchAction: "none",
856
+ // Let the browser scroll the cross-axis (so a horizontal swipe over a vertical
857
+ // fader scrolls the row); the slider handles drags along its own axis.
858
+ touchAction: horizontal ? "pan-y" : "pan-x",
689
859
  userSelect: "none"
690
860
  },
691
861
  onMouseDown: handleMouseDown,
@@ -2120,6 +2290,654 @@ function CrisCoMatrixListsTie({
2120
2290
  }
2121
2291
  );
2122
2292
  }
2293
+
2294
+ // src/components/CrisViewComm.tsx
2295
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2296
+ var ETH = /* @__PURE__ */ new Set(["tcp", "udp", "ssh", "ws"]);
2297
+ function commIndicators(c) {
2298
+ const isEth = !!c?.kd && ETH.has(c.kd);
2299
+ return {
2300
+ eth: { visible: isEth || c?.kd === "native", on: !!c?.co },
2301
+ serial: { visible: c?.kd === "serial" || isEth && !!c?.so, on: !!c?.al }
2302
+ };
2303
+ }
2304
+ var COMM_DEFAULTS = {
2305
+ root: "flex items-center gap-[0.4em]",
2306
+ dot: "w-[2.2em] h-[2.2em] rounded-full flex items-center justify-center",
2307
+ dotOn: "bg-[#22c55e]",
2308
+ dotOff: "bg-[#dc2626]",
2309
+ icon: ""
2310
+ };
2311
+ function isNode(v) {
2312
+ return v !== void 0 && typeof v !== "string";
2313
+ }
2314
+ function IndicatorDot({ icon, on, iconScale, cls }) {
2315
+ const iconStyle = {
2316
+ width: iconScale,
2317
+ height: iconScale,
2318
+ filter: "brightness(0) invert(1)",
2319
+ opacity: on ? 1 : 0.5
2320
+ };
2321
+ 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 }) });
2322
+ }
2323
+ function CrisViewComm({ comm, classes, icons, className }) {
2324
+ const cls = {
2325
+ root: classes?.root ?? COMM_DEFAULTS.root,
2326
+ dot: classes?.dot ?? COMM_DEFAULTS.dot,
2327
+ dotOn: classes?.dotOn ?? COMM_DEFAULTS.dotOn,
2328
+ dotOff: classes?.dotOff ?? COMM_DEFAULTS.dotOff,
2329
+ icon: classes?.icon ?? COMM_DEFAULTS.icon
2330
+ };
2331
+ const ethIcon = icons?.ethernet ?? "ind-ethernet";
2332
+ const serialIcon = icons?.serial ?? "rs232";
2333
+ const { eth, serial } = commIndicators(comm);
2334
+ if (!eth.visible && !serial.visible) return null;
2335
+ return /* @__PURE__ */ jsxs9("div", { className: `${cls.root} ${className ?? ""}`, children: [
2336
+ eth.visible && /* @__PURE__ */ jsx11(IndicatorDot, { icon: ethIcon, on: eth.on, iconScale: "85%", cls }),
2337
+ serial.visible && /* @__PURE__ */ jsx11(IndicatorDot, { icon: serialIcon, on: serial.on, iconScale: "95%", cls })
2338
+ ] });
2339
+ }
2340
+
2341
+ // src/components/dsp/CrisViewDspFull.tsx
2342
+ import { useState as useState6 } from "react";
2343
+ import { useCustomObject as useCustomObject4, useCustomObjectSend as useCustomObjectSend4 } from "@imperosoft/cris-webui-ch5-core";
2344
+
2345
+ // src/components/dsp/DspIoPage.tsx
2346
+ import { useRef as useRef4 } from "react";
2347
+
2348
+ // src/components/dsp/dspModel.ts
2349
+ function levelToPercent(lv) {
2350
+ return Math.round(clampLevel(lv) / 65535 * 100);
2351
+ }
2352
+ function clampLevel(lv) {
2353
+ if (!Number.isFinite(lv)) return 0;
2354
+ return Math.max(0, Math.min(65535, lv));
2355
+ }
2356
+ function normalizeLinked(raw) {
2357
+ return {
2358
+ label: raw.lb ?? "",
2359
+ channels: raw.ch ?? []
2360
+ };
2361
+ }
2362
+ function collapseStrips(channels, links) {
2363
+ const byId = new Map(channels.map((c) => [c.id, c]));
2364
+ const grouped = /* @__PURE__ */ new Set();
2365
+ const groups = [];
2366
+ for (const raw of links ?? []) {
2367
+ const { label, channels: members } = normalizeLinked(raw);
2368
+ if (members.length === 0) continue;
2369
+ members.forEach((id) => grouped.add(id));
2370
+ const primary = members.find((id) => byId.has(id));
2371
+ const ref = primary !== void 0 ? byId.get(primary) : void 0;
2372
+ groups.push({
2373
+ kind: "group",
2374
+ key: `g:${members.join("-")}`,
2375
+ label: label || ref?.lb || `Grupo ${members[0]}`,
2376
+ channels: members,
2377
+ lv: clampLevel(ref?.lv),
2378
+ mt: ref?.mt ?? false
2379
+ });
2380
+ }
2381
+ const singles = channels.filter((c) => !grouped.has(c.id)).map((c) => ({
2382
+ kind: "single",
2383
+ key: `c:${c.id}`,
2384
+ label: c.lb ?? `Canal ${c.id}`,
2385
+ channels: [c.id],
2386
+ lv: clampLevel(c.lv),
2387
+ mt: c.mt
2388
+ }));
2389
+ return [...singles, ...groups];
2390
+ }
2391
+ function linksFor(ln, io) {
2392
+ return io === "in" ? ln?.ip : ln?.op;
2393
+ }
2394
+ function buildMixerAxis(channels, links) {
2395
+ const items = channels.map((c) => ({
2396
+ id: c.id,
2397
+ channelLabel: c.lb ?? `${c.id}`
2398
+ }));
2399
+ const indexById = new Map(channels.map((c, idx) => [c.id, idx]));
2400
+ for (const raw of links ?? []) {
2401
+ const { label, channels: members } = normalizeLinked(raw);
2402
+ if (members.length < 2) continue;
2403
+ const sorted = [...members].sort((a, b) => a - b);
2404
+ const consecutiveNumbers = sorted.every((n, k) => k === 0 || n === sorted[k - 1] + 1);
2405
+ if (!consecutiveNumbers) continue;
2406
+ const idxs = sorted.map((n) => indexById.get(n));
2407
+ if (idxs.some((x) => x === void 0)) continue;
2408
+ const indices = idxs;
2409
+ const consecutiveIdx = indices.every((x, k) => k === 0 || x === indices[k - 1] + 1);
2410
+ if (!consecutiveIdx) continue;
2411
+ const startIdx = indices[0];
2412
+ items[startIdx].groupStart = true;
2413
+ items[startIdx].groupSpan = indices.length;
2414
+ for (const idx of indices) items[idx].groupCommon = label;
2415
+ }
2416
+ return items;
2417
+ }
2418
+
2419
+ // src/components/dsp/DspChannelStrip.tsx
2420
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2421
+ function DspChannelStrip({ strip, io, oid, send, cls, icons }) {
2422
+ const primary = strip.channels[0];
2423
+ const sendLevel = (channel, value, queued) => send(oid, { action: "level.set", io, channel, value, queued });
2424
+ const streamLevel = (value) => {
2425
+ if (primary !== void 0) sendLevel(primary, value, false);
2426
+ };
2427
+ const commitLevel = (value) => {
2428
+ strip.channels.forEach((ch) => sendLevel(ch, value, true));
2429
+ };
2430
+ const toggleMute = () => {
2431
+ const value = !strip.mt;
2432
+ strip.channels.forEach(
2433
+ (channel) => send(oid, { action: "mute.set", io, channel, value, queued: false })
2434
+ );
2435
+ };
2436
+ return /* @__PURE__ */ jsxs10("div", { className: cls.strip, style: { minWidth: 0 }, children: [
2437
+ /* @__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 }) }),
2438
+ /* @__PURE__ */ jsxs10("span", { className: cls.levelReadout, children: [
2439
+ levelToPercent(strip.lv),
2440
+ "%"
2441
+ ] }),
2442
+ /* @__PURE__ */ jsx12(
2443
+ CrisSlider,
2444
+ {
2445
+ value: strip.lv,
2446
+ onChange: streamLevel,
2447
+ onCommit: commitLevel,
2448
+ minValue: 0,
2449
+ maxValue: 65535,
2450
+ thumbSizePercent: 7,
2451
+ className: "flex-1 w-full",
2452
+ barClassName: cls.faderBar,
2453
+ fillClassName: cls.faderFill,
2454
+ thumbClassName: cls.faderThumb
2455
+ }
2456
+ ),
2457
+ /* @__PURE__ */ jsx12("div", { className: "w-full h-[3.8em] shrink-0 mt-[0.6em]", children: /* @__PURE__ */ jsx12(
2458
+ CrisButton,
2459
+ {
2460
+ selected: strip.mt,
2461
+ onPress: toggleMute,
2462
+ iconName: icons.muteOff,
2463
+ iconNameActive: icons.muteOn,
2464
+ iconSize: "3.6em",
2465
+ iconStyle: { filter: icons.muteOffFilter },
2466
+ iconStyleActive: { filter: icons.muteOnFilter },
2467
+ className: cls.mute,
2468
+ classActive: cls.muteActive
2469
+ }
2470
+ ) })
2471
+ ] });
2472
+ }
2473
+
2474
+ // src/components/dsp/useHorizontalWheel.ts
2475
+ import { useEffect as useEffect5 } from "react";
2476
+ function useHorizontalWheel(ref) {
2477
+ useEffect5(() => {
2478
+ const el = ref.current;
2479
+ if (!el) return;
2480
+ const onWheel = (e) => {
2481
+ if (e.deltaY === 0) return;
2482
+ if (el.scrollWidth <= el.clientWidth) return;
2483
+ e.preventDefault();
2484
+ el.scrollLeft += e.deltaY;
2485
+ };
2486
+ el.addEventListener("wheel", onWheel, { passive: false });
2487
+ return () => el.removeEventListener("wheel", onWheel);
2488
+ }, [ref]);
2489
+ }
2490
+
2491
+ // src/components/dsp/DspIoPage.tsx
2492
+ import { jsx as jsx13 } from "react/jsx-runtime";
2493
+ function DspIoPage({ status, io, oid, send, skip, cls, icons, emptyLabel }) {
2494
+ const scrollRef = useRef4(null);
2495
+ useHorizontalWheel(scrollRef);
2496
+ const skipSet = new Set(skip ?? []);
2497
+ const raw = (io === "in" ? status?.ip : status?.op) ?? [];
2498
+ const channels = skipSet.size ? raw.filter((c) => !skipSet.has(c.id)) : raw;
2499
+ const allLinks = linksFor(status?.ln, io);
2500
+ const links = skipSet.size ? allLinks?.filter((g) => (g.ch ?? []).some((id) => !skipSet.has(id))) : allLinks;
2501
+ const strips = collapseStrips(channels, links);
2502
+ if (strips.length === 0) {
2503
+ return /* @__PURE__ */ jsx13("div", { className: cls.message, children: emptyLabel });
2504
+ }
2505
+ return /* @__PURE__ */ jsx13("div", { ref: scrollRef, className: "w-full h-full overflow-x-auto no-scrollbar", children: /* @__PURE__ */ jsx13(
2506
+ "div",
2507
+ {
2508
+ className: "flex h-full items-stretch gap-[0.3em] px-[0.5em] py-[0.5em]",
2509
+ style: { minWidth: "min-content" },
2510
+ children: strips.map((strip) => /* @__PURE__ */ jsx13(
2511
+ DspChannelStrip,
2512
+ {
2513
+ strip,
2514
+ io,
2515
+ oid,
2516
+ send,
2517
+ cls,
2518
+ icons
2519
+ },
2520
+ strip.key
2521
+ ))
2522
+ }
2523
+ ) });
2524
+ }
2525
+
2526
+ // src/components/dsp/DspMixer.tsx
2527
+ import { useRef as useRef5 } from "react";
2528
+ import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
2529
+ var COMMON_W = "9em";
2530
+ var CHAN_W = "3em";
2531
+ var COMMON_H = "2.4em";
2532
+ var CHAN_H = "2.1em";
2533
+ var CELL_W = "7.5em";
2534
+ var ROW_H = "4.5em";
2535
+ var DRAG_FACTOR = 0.6;
2536
+ function CrosspointOnIcon({ icon }) {
2537
+ if (typeof icon !== "string") return /* @__PURE__ */ jsx14(Fragment3, { children: icon });
2538
+ return /* @__PURE__ */ jsx14(
2539
+ "img",
2540
+ {
2541
+ src: getIconUrl(icon),
2542
+ alt: "",
2543
+ draggable: false,
2544
+ className: "pointer-events-none",
2545
+ style: { width: "4.4em", height: "4.4em", filter: "brightness(0) invert(1)" }
2546
+ }
2547
+ );
2548
+ }
2549
+ function DspMixer({ status, oid, send, cls, icons }) {
2550
+ const scrollRef = useRef5(null);
2551
+ const startRef = useRef5(null);
2552
+ const movedRef = useRef5(false);
2553
+ const thresholdRef = useRef5(8);
2554
+ const pendingRef = useRef5(null);
2555
+ const inputs = status?.ip ?? [];
2556
+ const outputs = status?.op ?? [];
2557
+ const isOn = (output, input) => outputs.find((o) => o.id === output)?.xp?.[input] === true;
2558
+ const onPointerDown = (e) => {
2559
+ const el = scrollRef.current;
2560
+ if (!el) return;
2561
+ movedRef.current = false;
2562
+ thresholdRef.current = parseFloat(getComputedStyle(el).fontSize || "16") * DRAG_FACTOR;
2563
+ startRef.current = { x: e.clientX, y: e.clientY, sl: el.scrollLeft, st: el.scrollTop };
2564
+ const cell = e.target.closest("[data-cell]");
2565
+ pendingRef.current = cell ? { input: Number(cell.dataset.in), output: Number(cell.dataset.out) } : null;
2566
+ el.setPointerCapture(e.pointerId);
2567
+ };
2568
+ const onPointerMove = (e) => {
2569
+ const el = scrollRef.current;
2570
+ const s = startRef.current;
2571
+ if (!el || !s) return;
2572
+ const dx = e.clientX - s.x;
2573
+ const dy = e.clientY - s.y;
2574
+ if (!movedRef.current && Math.hypot(dx, dy) > thresholdRef.current) movedRef.current = true;
2575
+ if (movedRef.current) {
2576
+ el.scrollLeft = s.sl - dx;
2577
+ el.scrollTop = s.st - dy;
2578
+ }
2579
+ };
2580
+ const onPointerUp = (e) => {
2581
+ const el = scrollRef.current;
2582
+ el?.releasePointerCapture?.(e.pointerId);
2583
+ if (!movedRef.current && pendingRef.current) {
2584
+ const { input, output } = pendingRef.current;
2585
+ send(oid, {
2586
+ action: "crosspoint.set",
2587
+ input,
2588
+ output,
2589
+ value: !isOn(output, input),
2590
+ queued: true
2591
+ });
2592
+ }
2593
+ startRef.current = null;
2594
+ pendingRef.current = null;
2595
+ };
2596
+ if (inputs.length === 0 || outputs.length === 0) {
2597
+ return /* @__PURE__ */ jsx14("div", { className: cls.message, children: "Sin crosspoints" });
2598
+ }
2599
+ const inAxis = buildMixerAxis(inputs, linksFor(status?.ln, "in"));
2600
+ const outAxis = buildMixerAxis(outputs, linksFor(status?.ln, "out"));
2601
+ return /* @__PURE__ */ jsx14(
2602
+ "div",
2603
+ {
2604
+ ref: scrollRef,
2605
+ onPointerDown,
2606
+ onPointerMove,
2607
+ onPointerUp,
2608
+ onPointerCancel: onPointerUp,
2609
+ className: "w-full h-full overflow-auto no-scrollbar relative touch-none select-none cursor-grab",
2610
+ children: /* @__PURE__ */ jsxs11(
2611
+ "div",
2612
+ {
2613
+ className: "grid",
2614
+ style: {
2615
+ // cols: [output common][output channel][inputs…] rows: [in common][in channel][outputs…]
2616
+ gridTemplateColumns: `${COMMON_W} ${CHAN_W} repeat(${inputs.length}, ${CELL_W})`,
2617
+ gridTemplateRows: `${COMMON_H} ${CHAN_H} repeat(${outputs.length}, ${ROW_H})`,
2618
+ minWidth: "min-content"
2619
+ },
2620
+ children: [
2621
+ /* @__PURE__ */ jsx14(
2622
+ "div",
2623
+ {
2624
+ className: cls.mixerCorner,
2625
+ style: { top: 0, left: 0, gridColumn: "1 / span 2", gridRow: "1 / span 2" },
2626
+ children: "OUT \\ IN"
2627
+ }
2628
+ ),
2629
+ inAxis.flatMap((it, ii) => {
2630
+ const col = 3 + ii;
2631
+ if (it.groupCommon !== void 0) {
2632
+ const nodes = [
2633
+ /* @__PURE__ */ jsx14(
2634
+ "div",
2635
+ {
2636
+ className: `${cls.mixerChrome} z-20 px-[0.2em]`,
2637
+ style: { top: 0, gridRow: "1 / span 2", gridColumn: col, alignItems: "flex-end", paddingBottom: "0.3em" },
2638
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-1", children: it.channelLabel })
2639
+ },
2640
+ `ich:${it.id}`
2641
+ )
2642
+ ];
2643
+ if (it.groupStart) {
2644
+ nodes.unshift(
2645
+ /* @__PURE__ */ jsx14(
2646
+ "div",
2647
+ {
2648
+ className: `${cls.mixerChrome} z-25 px-[0.2em] font-semibold`,
2649
+ style: { top: 0, gridRow: 1, gridColumn: `${col} / span ${it.groupSpan}` },
2650
+ title: it.groupCommon,
2651
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-2", children: it.groupCommon })
2652
+ },
2653
+ `icc:${it.id}`
2654
+ )
2655
+ );
2656
+ }
2657
+ return nodes;
2658
+ }
2659
+ return [
2660
+ /* @__PURE__ */ jsx14(
2661
+ "div",
2662
+ {
2663
+ className: `${cls.mixerChrome} z-20 px-[0.2em]`,
2664
+ style: { top: 0, gridRow: "1 / span 2", gridColumn: col, alignItems: "center" },
2665
+ title: it.channelLabel,
2666
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-3", children: it.channelLabel })
2667
+ },
2668
+ `is:${it.id}`
2669
+ )
2670
+ ];
2671
+ }),
2672
+ outAxis.flatMap((it, oo) => {
2673
+ const row = 3 + oo;
2674
+ if (it.groupCommon !== void 0) {
2675
+ const nodes = [
2676
+ /* @__PURE__ */ jsx14(
2677
+ "div",
2678
+ {
2679
+ className: `${cls.mixerChrome} z-20`,
2680
+ style: { left: 0, gridColumn: "1 / span 2", gridRow: row, justifyContent: "flex-end", paddingRight: "0.6em" },
2681
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-1", children: it.channelLabel })
2682
+ },
2683
+ `och:${it.id}`
2684
+ )
2685
+ ];
2686
+ if (it.groupStart) {
2687
+ nodes.unshift(
2688
+ /* @__PURE__ */ jsx14(
2689
+ "div",
2690
+ {
2691
+ className: `${cls.mixerChrome} z-25 justify-start px-[0.4em] font-semibold`,
2692
+ style: { left: 0, gridColumn: 1, gridRow: `${row} / span ${it.groupSpan}` },
2693
+ title: it.groupCommon,
2694
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-2 text-left", children: it.groupCommon })
2695
+ },
2696
+ `occ:${it.id}`
2697
+ )
2698
+ );
2699
+ }
2700
+ return nodes;
2701
+ }
2702
+ return [
2703
+ /* @__PURE__ */ jsx14(
2704
+ "div",
2705
+ {
2706
+ className: `${cls.mixerChrome} z-20 justify-start px-[0.4em]`,
2707
+ style: { left: 0, gridColumn: "1 / span 2", gridRow: row },
2708
+ title: it.channelLabel,
2709
+ children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-2 text-left", children: it.channelLabel })
2710
+ },
2711
+ `os:${it.id}`
2712
+ )
2713
+ ];
2714
+ }),
2715
+ outputs.map(
2716
+ (o, oo) => inputs.map((i, ii) => {
2717
+ const on = isOn(o.id, i.id);
2718
+ return /* @__PURE__ */ jsx14(
2719
+ "div",
2720
+ {
2721
+ "data-cell": true,
2722
+ "data-in": i.id,
2723
+ "data-out": o.id,
2724
+ className: `${cls.cell} ${on ? cls.cellOn : cls.cellOff}`,
2725
+ style: { gridColumn: 3 + ii, gridRow: 3 + oo },
2726
+ children: on && /* @__PURE__ */ jsx14(CrosspointOnIcon, { icon: icons.crosspointOn })
2727
+ },
2728
+ `x:${i.id}:${o.id}`
2729
+ );
2730
+ })
2731
+ )
2732
+ ]
2733
+ }
2734
+ )
2735
+ }
2736
+ );
2737
+ }
2738
+
2739
+ // src/components/dsp/DspPresets.tsx
2740
+ import { useRef as useRef6, useState as useState5 } from "react";
2741
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
2742
+ var HOLD_MS = 2e3;
2743
+ var SAVED_MS = 1e3;
2744
+ function PresetButton({ num, name, active, onCall, onSave, cls }) {
2745
+ return /* @__PURE__ */ jsx15(
2746
+ CrisButton,
2747
+ {
2748
+ onTap: onCall,
2749
+ onLongPress: onSave,
2750
+ holdMs: HOLD_MS,
2751
+ selected: active,
2752
+ className: cls.preset,
2753
+ classActive: cls.presetActive,
2754
+ classPressed: cls.presetPressed,
2755
+ children: /* @__PURE__ */ jsxs12("span", { className: "flex items-center justify-center gap-[0.5em]", children: [
2756
+ /* @__PURE__ */ jsx15("span", { className: "text-[1.6em] font-bold leading-none", children: num }),
2757
+ /* @__PURE__ */ jsx15("span", { className: "text-[1.2em] leading-none", children: name })
2758
+ ] })
2759
+ }
2760
+ );
2761
+ }
2762
+ function DspPresets({
2763
+ oid,
2764
+ send,
2765
+ presets,
2766
+ activeDevice,
2767
+ activeLocal,
2768
+ sustainedFeedback = true,
2769
+ cls
2770
+ }) {
2771
+ const [saved, setSaved] = useState5(false);
2772
+ const savedTimer = useRef6(null);
2773
+ const call = (id) => send(oid, { action: "preset.call", value: id });
2774
+ const save = (id) => {
2775
+ send(oid, { action: "preset.save", value: id });
2776
+ setSaved(true);
2777
+ if (savedTimer.current !== null) clearTimeout(savedTimer.current);
2778
+ savedTimer.current = window.setTimeout(() => setSaved(false), SAVED_MS);
2779
+ };
2780
+ const isActive = (p) => {
2781
+ if (!sustainedFeedback) return false;
2782
+ return p.kind === "local" ? activeLocal === p.id : activeDevice === p.id;
2783
+ };
2784
+ return /* @__PURE__ */ jsxs12("div", { className: cls.presetWrap, children: [
2785
+ /* @__PURE__ */ jsx15("span", { className: cls.presetLabel, children: "Preset" }),
2786
+ /* @__PURE__ */ jsxs12("div", { className: "relative flex-1 flex items-stretch gap-[0.3em] mt-[0.25em]", children: [
2787
+ presets.map((p) => /* @__PURE__ */ jsx15(
2788
+ PresetButton,
2789
+ {
2790
+ num: p.id,
2791
+ name: p.name,
2792
+ active: isActive(p),
2793
+ onCall: () => call(p.id),
2794
+ onSave: p.saveAllowed === false ? void 0 : () => save(p.id),
2795
+ cls
2796
+ },
2797
+ p.id
2798
+ )),
2799
+ saved && /* @__PURE__ */ jsx15("div", { className: cls.savedFlash, children: "Saved" })
2800
+ ] })
2801
+ ] });
2802
+ }
2803
+
2804
+ // src/components/dsp/dspClasses.ts
2805
+ var DSP_CLASS_DEFAULTS = {
2806
+ root: "w-full h-full flex flex-col",
2807
+ header: "relative w-full flex items-end justify-center gap-[0.3em] px-[1em] pt-[0.5em]",
2808
+ content: "w-full flex-1 min-h-0",
2809
+ 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",
2810
+ tabActive: "bg-[#007ca0]",
2811
+ message: "w-full h-full flex items-center justify-center text-gray-400 text-[2em]",
2812
+ presetWrap: "absolute left-[1em] bottom-[0.3em] h-[4.5em] flex flex-col items-center",
2813
+ presetLabel: "text-[#4f5152] text-[1.4em] leading-none",
2814
+ preset: "w-[8em] h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
2815
+ presetActive: "bg-[#007ca0]",
2816
+ presetPressed: "bg-[#006080]",
2817
+ savedFlash: "absolute inset-0 flex items-center justify-center rounded bg-[#22c55e] text-white text-[1.2em] font-bold pointer-events-none",
2818
+ strip: "flex flex-col items-center gap-[0.4em] h-full w-[9em] flex-none px-[0.3em]",
2819
+ stripLabel: "text-[#4f5152] text-center leading-tight text-[1.1em] line-clamp-3",
2820
+ levelReadout: "text-[1.1em] text-[#4f5152] leading-none opacity-70 mt-[0.5em]",
2821
+ faderBar: "bg-[#c0c0c0] rounded",
2822
+ faderFill: "bg-[#007ca0] rounded",
2823
+ faderThumb: "bg-[#4f5152] rounded",
2824
+ mute: "w-full h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
2825
+ muteActive: "bg-[#dc2626] text-white",
2826
+ mixerCorner: "sticky z-30 flex items-center justify-center bg-[#282C34] text-[#4f5152] text-[1.1em] font-bold border border-black/30",
2827
+ mixerChrome: "sticky flex items-center justify-center text-center bg-[#282C34] text-white text-[1.1em] leading-tight border border-black/30",
2828
+ cell: "z-10 flex items-center justify-center border border-black/30",
2829
+ cellOn: "bg-[#22c55e]",
2830
+ cellOff: "bg-[#4f5152]"
2831
+ };
2832
+ var DSP_ICON_DEFAULTS = {
2833
+ crosspointOn: "audio-volume-ok",
2834
+ muteOff: "audio-volume-high",
2835
+ muteOn: "audio-volume-mute",
2836
+ muteOffFilter: "invert(65%) sepia(70%) saturate(500%) hue-rotate(80deg) brightness(110%) contrast(95%)",
2837
+ muteOnFilter: "brightness(0) invert(1)"
2838
+ };
2839
+ function mergeDefined(defaults4, overrides) {
2840
+ if (!overrides) return { ...defaults4 };
2841
+ const out = { ...defaults4 };
2842
+ Object.keys(overrides).forEach((k) => {
2843
+ const v = overrides[k];
2844
+ if (v !== void 0) out[k] = v;
2845
+ });
2846
+ return out;
2847
+ }
2848
+ function resolveDspClasses(classes) {
2849
+ return mergeDefined(DSP_CLASS_DEFAULTS, classes);
2850
+ }
2851
+ function resolveDspIcons(icons) {
2852
+ return mergeDefined(DSP_ICON_DEFAULTS, icons);
2853
+ }
2854
+
2855
+ // src/components/dsp/CrisViewDspFull.tsx
2856
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
2857
+ function CrisViewDspFull({
2858
+ oid,
2859
+ presets,
2860
+ skipShowInput,
2861
+ skipShowOutput,
2862
+ skipCrosspoints = false,
2863
+ sustainedPresetFeedback = true,
2864
+ classes,
2865
+ icons,
2866
+ className,
2867
+ initialTab = "in"
2868
+ }) {
2869
+ const safeInitial = initialTab === "mix" && skipCrosspoints ? "in" : initialTab;
2870
+ const [tab, setTab] = useState6(safeInitial);
2871
+ const status = useCustomObject4(oid, { subscribe: true });
2872
+ const send = useCustomObjectSend4();
2873
+ const cls = resolveDspClasses(classes);
2874
+ const ic = resolveDspIcons(icons);
2875
+ const tabs = [
2876
+ { id: "in", label: "Inputs" },
2877
+ ...skipCrosspoints ? [] : [{ id: "mix", label: "Mixer" }],
2878
+ { id: "out", label: "Outputs" }
2879
+ ];
2880
+ const hasPresets = !!presets && presets.length > 0;
2881
+ const activeDevice = status?.pr?.dv ?? status?.ps?.dv;
2882
+ const activeLocal = status?.pr?.lc ?? status?.ps?.lc;
2883
+ return /* @__PURE__ */ jsxs13("div", { className: `${cls.root} ${className ?? ""}`, children: [
2884
+ /* @__PURE__ */ jsxs13("div", { className: cls.header, children: [
2885
+ hasPresets && /* @__PURE__ */ jsx16(
2886
+ DspPresets,
2887
+ {
2888
+ oid,
2889
+ send,
2890
+ presets,
2891
+ activeDevice,
2892
+ activeLocal,
2893
+ sustainedFeedback: sustainedPresetFeedback,
2894
+ cls
2895
+ }
2896
+ ),
2897
+ tabs.map((t) => /* @__PURE__ */ jsx16(
2898
+ CrisButton,
2899
+ {
2900
+ text: t.label,
2901
+ selected: tab === t.id,
2902
+ onPress: () => setTab(t.id),
2903
+ className: cls.tab,
2904
+ classActive: cls.tabActive
2905
+ },
2906
+ t.id
2907
+ )),
2908
+ /* @__PURE__ */ jsx16(CrisViewComm, { comm: status?.cm, className: "absolute right-[1em] bottom-[0.3em] text-[1.25em]" })
2909
+ ] }),
2910
+ /* @__PURE__ */ jsx16("div", { className: cls.content, children: status === void 0 ? /* @__PURE__ */ jsx16("div", { className: cls.message, children: "Conectando\u2026" }) : /* @__PURE__ */ jsxs13(Fragment4, { children: [
2911
+ tab === "in" && /* @__PURE__ */ jsx16(
2912
+ DspIoPage,
2913
+ {
2914
+ status,
2915
+ io: "in",
2916
+ oid,
2917
+ send,
2918
+ skip: skipShowInput,
2919
+ cls,
2920
+ icons: ic,
2921
+ emptyLabel: "Sin entradas"
2922
+ }
2923
+ ),
2924
+ tab === "mix" && !skipCrosspoints && /* @__PURE__ */ jsx16(DspMixer, { status, oid, send, cls, icons: ic }),
2925
+ tab === "out" && /* @__PURE__ */ jsx16(
2926
+ DspIoPage,
2927
+ {
2928
+ status,
2929
+ io: "out",
2930
+ oid,
2931
+ send,
2932
+ skip: skipShowOutput,
2933
+ cls,
2934
+ icons: ic,
2935
+ emptyLabel: "Sin salidas"
2936
+ }
2937
+ )
2938
+ ] }) })
2939
+ ] });
2940
+ }
2123
2941
  export {
2124
2942
  CrisButton,
2125
2943
  CrisCoDebug,
@@ -2131,9 +2949,18 @@ export {
2131
2949
  CrisSpinner,
2132
2950
  CrisText,
2133
2951
  CrisTextInput,
2952
+ CrisViewComm,
2953
+ CrisViewDspFull,
2954
+ buildMixerAxis,
2955
+ clampLevel,
2956
+ collapseStrips,
2957
+ commIndicators,
2134
2958
  configureIcons,
2135
2959
  getIconConfig,
2136
2960
  getIconFilter,
2137
- getIconUrl
2961
+ getIconUrl,
2962
+ levelToPercent,
2963
+ linksFor,
2964
+ normalizeLinked
2138
2965
  };
2139
2966
  //# sourceMappingURL=index.mjs.map