@imperosoft/cris-webui-components 0.1.1-beta.9 → 1.0.0-beta.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.js +26 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
34
34
|
|
|
35
35
|
// src/components/CrisButton.tsx
|
|
36
36
|
var import_react = require("react");
|
|
37
|
-
var
|
|
37
|
+
var import_cris_webui_ch5_core = require("@imperosoft/cris-webui-ch5-core");
|
|
38
38
|
|
|
39
39
|
// src/utils/icons.ts
|
|
40
40
|
var iconConfig = {
|
|
@@ -120,20 +120,18 @@ function CrisButton({
|
|
|
120
120
|
onRelease
|
|
121
121
|
}) {
|
|
122
122
|
const [pressed, setPressed] = (0, import_react.useState)(false);
|
|
123
|
-
const [hovering, setHovering] = (0, import_react.useState)(false);
|
|
124
123
|
const pressedRef = (0, import_react.useRef)(false);
|
|
125
124
|
const touchingRef = (0, import_react.useRef)(false);
|
|
126
125
|
const touchStartedHereRef = (0, import_react.useRef)(false);
|
|
127
126
|
const feedbackJoin = joinFeedback ?? join;
|
|
128
|
-
const feedback = (0,
|
|
129
|
-
const enabled = (0,
|
|
130
|
-
const visible = (0,
|
|
131
|
-
const dSet = (0,
|
|
127
|
+
const feedback = (0, import_cris_webui_ch5_core.useDigital)(feedbackJoin ?? 0);
|
|
128
|
+
const enabled = (0, import_cris_webui_ch5_core.useDigital)(joinEnable ?? 0);
|
|
129
|
+
const visible = (0, import_cris_webui_ch5_core.useDigital)(joinVisible ?? 0);
|
|
130
|
+
const dSet = (0, import_cris_webui_ch5_core.useJoinsStore)((state) => state.dSet);
|
|
132
131
|
const isEnabled = joinEnable == null ? true : enabled;
|
|
133
132
|
const isVisible = joinVisible == null ? true : visible;
|
|
134
133
|
(0, import_react.useEffect)(() => {
|
|
135
134
|
if (!isVisible && pressedRef.current) {
|
|
136
|
-
console.log(`[CrisButton ${join}] Button hidden while pressed - sending release`);
|
|
137
135
|
pressedRef.current = false;
|
|
138
136
|
setPressed(false);
|
|
139
137
|
touchingRef.current = false;
|
|
@@ -153,7 +151,6 @@ function CrisButton({
|
|
|
153
151
|
currentText = textSelected;
|
|
154
152
|
}
|
|
155
153
|
const handlePress = () => {
|
|
156
|
-
console.log(`[CrisButton ${join}] handlePress - suppressKeyClicks:${suppressKeyClicks} pressedRef:${pressedRef.current}`);
|
|
157
154
|
if (suppressKeyClicks) return;
|
|
158
155
|
if (pressedRef.current) return;
|
|
159
156
|
pressedRef.current = true;
|
|
@@ -161,12 +158,10 @@ function CrisButton({
|
|
|
161
158
|
if (!isEnabled) return;
|
|
162
159
|
onPress?.();
|
|
163
160
|
if (join != null && smartId == null) {
|
|
164
|
-
console.log(`[CrisButton ${join}] SENDING PRESS (true)`);
|
|
165
161
|
dSet(join, true);
|
|
166
162
|
}
|
|
167
163
|
};
|
|
168
164
|
const handleRelease = () => {
|
|
169
|
-
console.log(`[CrisButton ${join}] handleRelease - suppressKeyClicks:${suppressKeyClicks} pressedRef:${pressedRef.current}`);
|
|
170
165
|
if (suppressKeyClicks) return;
|
|
171
166
|
if (!pressedRef.current) return;
|
|
172
167
|
pressedRef.current = false;
|
|
@@ -174,19 +169,16 @@ function CrisButton({
|
|
|
174
169
|
if (!isEnabled) return;
|
|
175
170
|
onRelease?.();
|
|
176
171
|
if (join != null && smartId == null) {
|
|
177
|
-
console.log(`[CrisButton ${join}] SENDING RELEASE (false)`);
|
|
178
172
|
dSet(join, false);
|
|
179
173
|
}
|
|
180
174
|
};
|
|
181
175
|
const handleTouchStart = () => {
|
|
182
|
-
console.log(`[CrisButton ${join}] handleTouchStart - touchStartedHereRef:${touchStartedHereRef.current}`);
|
|
183
176
|
touchStart();
|
|
184
177
|
touchingRef.current = true;
|
|
185
178
|
touchStartedHereRef.current = true;
|
|
186
179
|
handlePress();
|
|
187
180
|
};
|
|
188
181
|
const handleTouchEnd = () => {
|
|
189
|
-
console.log(`[CrisButton ${join}] handleTouchEnd - touchStartedHereRef:${touchStartedHereRef.current}`);
|
|
190
182
|
touchEnd();
|
|
191
183
|
touchingRef.current = true;
|
|
192
184
|
if (touchStartedHereRef.current) {
|
|
@@ -195,30 +187,21 @@ function CrisButton({
|
|
|
195
187
|
}
|
|
196
188
|
};
|
|
197
189
|
const handleTouchCancel = () => {
|
|
198
|
-
console.log(`[CrisButton ${join}] handleTouchCancel - touchStartedHereRef:${touchStartedHereRef.current}`);
|
|
199
190
|
touchEnd();
|
|
200
191
|
touchingRef.current = true;
|
|
201
192
|
touchStartedHereRef.current = false;
|
|
202
193
|
handleRelease();
|
|
203
194
|
};
|
|
204
195
|
const handleMouseDown = () => {
|
|
205
|
-
console.log(`[CrisButton ${join}] handleMouseDown - isTouchActive:${isTouchActive()} touchingRef:${touchingRef.current}`);
|
|
206
196
|
if (isTouchActive() || touchingRef.current) return;
|
|
207
197
|
handlePress();
|
|
208
198
|
};
|
|
209
199
|
const handleMouseUp = () => {
|
|
210
|
-
console.log(`[CrisButton ${join}] handleMouseUp - isTouchActive:${isTouchActive()} touchingRef:${touchingRef.current}`);
|
|
211
200
|
if (isTouchActive() || touchingRef.current) return;
|
|
212
201
|
handleRelease();
|
|
213
202
|
};
|
|
214
|
-
const handleMouseEnter = () => {
|
|
215
|
-
if (isTouchActive() || touchingRef.current) return;
|
|
216
|
-
if (suppressKeyClicks) return;
|
|
217
|
-
setHovering(true);
|
|
218
|
-
};
|
|
219
203
|
const handleMouseLeave = () => {
|
|
220
204
|
if (isTouchActive() || touchingRef.current) return;
|
|
221
|
-
setHovering(false);
|
|
222
205
|
handleRelease();
|
|
223
206
|
};
|
|
224
207
|
if (!isVisible) return null;
|
|
@@ -310,7 +293,6 @@ function CrisButton({
|
|
|
310
293
|
},
|
|
311
294
|
onMouseDown: handleMouseDown,
|
|
312
295
|
onMouseUp: handleMouseUp,
|
|
313
|
-
onMouseEnter: handleMouseEnter,
|
|
314
296
|
onMouseLeave: handleMouseLeave,
|
|
315
297
|
onTouchStart: handleTouchStart,
|
|
316
298
|
onTouchEnd: handleTouchEnd,
|
|
@@ -319,13 +301,13 @@ function CrisButton({
|
|
|
319
301
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
320
302
|
"div",
|
|
321
303
|
{
|
|
322
|
-
className: "cris-button-
|
|
304
|
+
className: "cris-button-press-overlay",
|
|
323
305
|
style: {
|
|
324
306
|
position: "absolute",
|
|
325
307
|
inset: 0,
|
|
326
308
|
backgroundColor: "black",
|
|
327
|
-
opacity:
|
|
328
|
-
transition: "opacity 0.
|
|
309
|
+
opacity: pressed ? 0.1 : 0,
|
|
310
|
+
transition: "opacity 0.1s",
|
|
329
311
|
pointerEvents: "none",
|
|
330
312
|
borderRadius: "inherit"
|
|
331
313
|
}
|
|
@@ -340,7 +322,7 @@ function CrisButton({
|
|
|
340
322
|
}
|
|
341
323
|
|
|
342
324
|
// src/components/CrisText.tsx
|
|
343
|
-
var
|
|
325
|
+
var import_cris_webui_ch5_core2 = require("@imperosoft/cris-webui-ch5-core");
|
|
344
326
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
345
327
|
function CrisText({
|
|
346
328
|
joinIndirectText,
|
|
@@ -354,13 +336,13 @@ function CrisText({
|
|
|
354
336
|
classDisabled = "",
|
|
355
337
|
children
|
|
356
338
|
}) {
|
|
357
|
-
const indirectText = (0,
|
|
358
|
-
const enabled = (0,
|
|
359
|
-
const visible = (0,
|
|
339
|
+
const indirectText = (0, import_cris_webui_ch5_core2.useSerial)(joinIndirectText ?? 0);
|
|
340
|
+
const enabled = (0, import_cris_webui_ch5_core2.useDigital)(joinEnable ?? 0);
|
|
341
|
+
const visible = (0, import_cris_webui_ch5_core2.useDigital)(joinVisible ?? 0);
|
|
360
342
|
const isEnabled = joinEnable == null ? true : enabled;
|
|
361
343
|
const isVisible = joinVisible == null ? true : visible;
|
|
362
344
|
const rawText = joinIndirectText != null ? indirectText : text ?? "";
|
|
363
|
-
const currentText = (0,
|
|
345
|
+
const currentText = (0, import_cris_webui_ch5_core2.useCipDecode)(rawText);
|
|
364
346
|
if (!isVisible) return null;
|
|
365
347
|
const containerClasses = [
|
|
366
348
|
"cris-text",
|
|
@@ -376,7 +358,7 @@ function CrisText({
|
|
|
376
358
|
|
|
377
359
|
// src/components/CrisSlider.tsx
|
|
378
360
|
var import_react2 = require("react");
|
|
379
|
-
var
|
|
361
|
+
var import_cris_webui_ch5_core3 = require("@imperosoft/cris-webui-ch5-core");
|
|
380
362
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
381
363
|
function CrisSlider({
|
|
382
364
|
join,
|
|
@@ -403,11 +385,11 @@ function CrisSlider({
|
|
|
403
385
|
}) {
|
|
404
386
|
const effectiveAnalogJoin = joinAnalog ?? join;
|
|
405
387
|
const effectiveDigitalJoin = joinDigital ?? join;
|
|
406
|
-
const analogValue = (0,
|
|
407
|
-
const enabled = (0,
|
|
408
|
-
const visible = (0,
|
|
409
|
-
const dSet = (0,
|
|
410
|
-
const aSet = (0,
|
|
388
|
+
const analogValue = (0, import_cris_webui_ch5_core3.useAnalog)(effectiveAnalogJoin ?? 0);
|
|
389
|
+
const enabled = (0, import_cris_webui_ch5_core3.useDigital)(joinEnable ?? 0);
|
|
390
|
+
const visible = (0, import_cris_webui_ch5_core3.useDigital)(joinVisible ?? 0);
|
|
391
|
+
const dSet = (0, import_cris_webui_ch5_core3.useJoinsStore)((state) => state.dSet);
|
|
392
|
+
const aSet = (0, import_cris_webui_ch5_core3.useJoinsStore)((state) => state.aSet);
|
|
411
393
|
const [ratioCurrent, setRatioCurrent] = (0, import_react2.useState)(0);
|
|
412
394
|
const [isDragging, setIsDragging] = (0, import_react2.useState)(false);
|
|
413
395
|
const isDraggingOrJustAfterRef = (0, import_react2.useRef)(false);
|
|
@@ -418,7 +400,6 @@ function CrisSlider({
|
|
|
418
400
|
const isVisible = joinVisible == null ? true : visible;
|
|
419
401
|
(0, import_react2.useEffect)(() => {
|
|
420
402
|
if (!isVisible && isDraggingOrJustAfterRef.current) {
|
|
421
|
-
console.log(`[CrisSlider ${effectiveDigitalJoin}] Slider hidden while dragging - sending release`);
|
|
422
403
|
setIsDragging(false);
|
|
423
404
|
isDraggingOrJustAfterRef.current = false;
|
|
424
405
|
touchingRef.current = false;
|
|
@@ -641,7 +622,7 @@ function CrisSlider({
|
|
|
641
622
|
}
|
|
642
623
|
|
|
643
624
|
// src/components/CrisGauge.tsx
|
|
644
|
-
var
|
|
625
|
+
var import_cris_webui_ch5_core4 = require("@imperosoft/cris-webui-ch5-core");
|
|
645
626
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
646
627
|
function CrisGauge({
|
|
647
628
|
value,
|
|
@@ -667,9 +648,9 @@ function CrisGauge({
|
|
|
667
648
|
highSegmentStyle,
|
|
668
649
|
classDisabled = ""
|
|
669
650
|
}) {
|
|
670
|
-
const joinValue = (0,
|
|
671
|
-
const enabled = (0,
|
|
672
|
-
const visible = (0,
|
|
651
|
+
const joinValue = (0, import_cris_webui_ch5_core4.useAnalog)(join ?? 0);
|
|
652
|
+
const enabled = (0, import_cris_webui_ch5_core4.useDigital)(joinEnable ?? 0);
|
|
653
|
+
const visible = (0, import_cris_webui_ch5_core4.useDigital)(joinVisible ?? 0);
|
|
673
654
|
const isEnabled = joinEnable == null ? true : enabled;
|
|
674
655
|
const isVisible = joinVisible == null ? true : visible;
|
|
675
656
|
const currentValue = join != null ? joinValue : value ?? 0;
|
|
@@ -751,7 +732,7 @@ function CrisGauge({
|
|
|
751
732
|
}
|
|
752
733
|
|
|
753
734
|
// src/components/CrisSpinner.tsx
|
|
754
|
-
var
|
|
735
|
+
var import_cris_webui_ch5_core5 = require("@imperosoft/cris-webui-ch5-core");
|
|
755
736
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
756
737
|
var SPEED_DURATIONS = {
|
|
757
738
|
slow: "1.5s",
|
|
@@ -770,8 +751,8 @@ function CrisSpinner({
|
|
|
770
751
|
className = "",
|
|
771
752
|
style
|
|
772
753
|
}) {
|
|
773
|
-
const joinPositionValue = (0,
|
|
774
|
-
const visible = (0,
|
|
754
|
+
const joinPositionValue = (0, import_cris_webui_ch5_core5.useAnalog)(joinPosition ?? 0);
|
|
755
|
+
const visible = (0, import_cris_webui_ch5_core5.useDigital)(joinVisible ?? 0);
|
|
775
756
|
const isVisible = joinVisible == null ? true : visible;
|
|
776
757
|
if (!isVisible) return null;
|
|
777
758
|
const currentPosition = joinPosition != null ? joinPositionValue : position ?? 0;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/utils/touchGuard.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx"],"sourcesContent":["/**\r\n * @imperosoft/cris-webui-components\r\n *\r\n * CRIS - Crestron React Impero Soft WebUI components library\r\n *\r\n * Provides reusable UI components for Crestron touch panel interfaces.\r\n */\r\n\r\n// Components\r\nexport { CrisButton } from './components/CrisButton';\r\nexport type { CrisButtonProps } from './components/CrisButton';\r\n\r\nexport { CrisText } from './components/CrisText';\r\nexport type { CrisTextProps } from './components/CrisText';\r\n\r\nexport { CrisSlider } from './components/CrisSlider';\r\nexport type { CrisSliderProps } from './components/CrisSlider';\r\n\r\nexport { CrisGauge } from './components/CrisGauge';\r\nexport type { CrisGaugeProps } from './components/CrisGauge';\r\n\r\nexport { CrisSpinner } from './components/CrisSpinner';\r\nexport type { CrisSpinnerProps, SpinnerSpeed } from './components/CrisSpinner';\r\n\r\n// Utilities\r\nexport { configureIcons, getIconUrl, getIconFilter, getIconConfig } from './utils/icons';\r\nexport type { IconConfig } from './utils/icons';\r\n","import { useState, useRef, useEffect, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n}: CrisButtonProps) {\r\n const [pressed, setPressed] = useState(false);\r\n const [hovering, setHovering] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if button becomes invisible while pressed\r\n useEffect(() => {\r\n if (!isVisible && pressedRef.current) {\r\n console.log(`[CrisButton ${join}] Button hidden while pressed - sending release`);\r\n pressedRef.current = false;\r\n setPressed(false);\r\n touchingRef.current = false;\r\n touchStartedHereRef.current = false;\r\n if (join != null && smartId == null) {\r\n dSet(join, false);\r\n }\r\n }\r\n }, [isVisible, join, smartId, dSet]);\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n console.log(`[CrisButton ${join}] handlePress - suppressKeyClicks:${suppressKeyClicks} pressedRef:${pressedRef.current}`);\r\n if (suppressKeyClicks) return;\r\n if (pressedRef.current) return;\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n console.log(`[CrisButton ${join}] SENDING PRESS (true)`);\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n console.log(`[CrisButton ${join}] handleRelease - suppressKeyClicks:${suppressKeyClicks} pressedRef:${pressedRef.current}`);\r\n if (suppressKeyClicks) return;\r\n if (!pressedRef.current) return;\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n console.log(`[CrisButton ${join}] SENDING RELEASE (false)`);\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - use global touch guard to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n console.log(`[CrisButton ${join}] handleTouchStart - touchStartedHereRef:${touchStartedHereRef.current}`);\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n console.log(`[CrisButton ${join}] handleTouchEnd - touchStartedHereRef:${touchStartedHereRef.current}`);\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n }\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n console.log(`[CrisButton ${join}] handleTouchCancel - touchStartedHereRef:${touchStartedHereRef.current}`);\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n };\r\n\r\n // Mouse event handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = () => {\r\n console.log(`[CrisButton ${join}] handleMouseDown - isTouchActive:${isTouchActive()} touchingRef:${touchingRef.current}`);\r\n if (isTouchActive() || touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n console.log(`[CrisButton ${join}] handleMouseUp - isTouchActive:${isTouchActive()} touchingRef:${touchingRef.current}`);\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseEnter = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n if (suppressKeyClicks) return;\r\n setHovering(true);\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n setHovering(false);\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={computedIconStyle}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={computedIconStyle} alt=\"\" />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseEnter={handleMouseEnter}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Hover overlay - inline styles for touch-safe behavior */}\r\n <div\r\n className=\"cris-button-hover-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: hovering ? 0.1 : 0,\r\n transition: 'opacity 0.2s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","/**\r\n * Global touch state guard - shared across all CRIS components\r\n *\r\n * This prevents phantom clicks when elements swap visibility during touch interactions.\r\n * When a button is touched and triggers a visibility change (e.g., back button hides,\r\n * power button appears), the newly visible element would receive the synthetic click\r\n * event from the browser. The global touch flag blocks this.\r\n */\r\n\r\nlet globalTouchActive = false;\r\nlet touchResetTimeout: number | null = null;\r\n\r\n/** Duration to keep touch guard active after touch ends (blocks synthetic click) */\r\nconst TOUCH_GUARD_DURATION_MS = 300;\r\n\r\n/**\r\n * Mark that a touch interaction has started.\r\n * Call this in touchStart handlers.\r\n */\r\nexport function touchStart(): void {\r\n globalTouchActive = true;\r\n // Clear any pending reset\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n touchResetTimeout = null;\r\n }\r\n}\r\n\r\n/**\r\n * Mark that a touch interaction has ended.\r\n * The guard remains active for a short duration to block synthetic click events.\r\n * Call this in touchEnd/touchCancel handlers.\r\n */\r\nexport function touchEnd(): void {\r\n globalTouchActive = true;\r\n // Reset after delay to block synthetic click\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n }\r\n touchResetTimeout = window.setTimeout(() => {\r\n globalTouchActive = false;\r\n touchResetTimeout = null;\r\n }, TOUCH_GUARD_DURATION_MS);\r\n}\r\n\r\n/**\r\n * Check if a touch interaction is currently active.\r\n * Use this in mouse handlers to skip if touch is active.\r\n */\r\nexport function isTouchActive(): boolean {\r\n return globalTouchActive;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-core';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if slider becomes invisible while dragging\r\n useEffect(() => {\r\n if (!isVisible && isDraggingOrJustAfterRef.current) {\r\n console.log(`[CrisSlider ${effectiveDigitalJoin}] Slider hidden while dragging - sending release`);\r\n setIsDragging(false);\r\n isDraggingOrJustAfterRef.current = false;\r\n touchingRef.current = false;\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n }\r\n }, [isVisible, effectiveDigitalJoin, dSet]);\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers - use global touch guard\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchEnd(); // Global guard with delayed reset\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["import_cris_webui_core","import_jsx_runtime","import_react","import_cris_webui_core","import_jsx_runtime","import_cris_webui_core","import_jsx_runtime","import_cris_webui_core","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/utils/touchGuard.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx"],"sourcesContent":["/**\r\n * @imperosoft/cris-webui-components\r\n *\r\n * CRIS - Crestron React Impero Soft WebUI components library\r\n *\r\n * Provides reusable UI components for Crestron touch panel interfaces.\r\n */\r\n\r\n// Components\r\nexport { CrisButton } from './components/CrisButton';\r\nexport type { CrisButtonProps } from './components/CrisButton';\r\n\r\nexport { CrisText } from './components/CrisText';\r\nexport type { CrisTextProps } from './components/CrisText';\r\n\r\nexport { CrisSlider } from './components/CrisSlider';\r\nexport type { CrisSliderProps } from './components/CrisSlider';\r\n\r\nexport { CrisGauge } from './components/CrisGauge';\r\nexport type { CrisGaugeProps } from './components/CrisGauge';\r\n\r\nexport { CrisSpinner } from './components/CrisSpinner';\r\nexport type { CrisSpinnerProps, SpinnerSpeed } from './components/CrisSpinner';\r\n\r\n// Utilities\r\nexport { configureIcons, getIconUrl, getIconFilter, getIconConfig } from './utils/icons';\r\nexport type { IconConfig } from './utils/icons';\r\n","import { useState, useRef, useEffect, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n}: CrisButtonProps) {\r\n const [pressed, setPressed] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if button becomes invisible while pressed\r\n useEffect(() => {\r\n if (!isVisible && pressedRef.current) {\r\n pressedRef.current = false;\r\n setPressed(false);\r\n touchingRef.current = false;\r\n touchStartedHereRef.current = false;\r\n if (join != null && smartId == null) {\r\n dSet(join, false);\r\n }\r\n }\r\n }, [isVisible, join, smartId, dSet]);\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n if (suppressKeyClicks) return;\r\n if (pressedRef.current) return;\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n if (suppressKeyClicks) return;\r\n if (!pressedRef.current) return;\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - use global touch guard to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n }\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n };\r\n\r\n // Mouse event handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={computedIconStyle}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={computedIconStyle} alt=\"\" />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Press overlay - shows on press for both desktop and touch devices */}\r\n <div\r\n className=\"cris-button-press-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: pressed ? 0.1 : 0,\r\n transition: 'opacity 0.1s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","/**\r\n * Global touch state guard - shared across all CRIS components\r\n *\r\n * This prevents phantom clicks when elements swap visibility during touch interactions.\r\n * When a button is touched and triggers a visibility change (e.g., back button hides,\r\n * power button appears), the newly visible element would receive the synthetic click\r\n * event from the browser. The global touch flag blocks this.\r\n */\r\n\r\nlet globalTouchActive = false;\r\nlet touchResetTimeout: number | null = null;\r\n\r\n/** Duration to keep touch guard active after touch ends (blocks synthetic click) */\r\nconst TOUCH_GUARD_DURATION_MS = 300;\r\n\r\n/**\r\n * Mark that a touch interaction has started.\r\n * Call this in touchStart handlers.\r\n */\r\nexport function touchStart(): void {\r\n globalTouchActive = true;\r\n // Clear any pending reset\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n touchResetTimeout = null;\r\n }\r\n}\r\n\r\n/**\r\n * Mark that a touch interaction has ended.\r\n * The guard remains active for a short duration to block synthetic click events.\r\n * Call this in touchEnd/touchCancel handlers.\r\n */\r\nexport function touchEnd(): void {\r\n globalTouchActive = true;\r\n // Reset after delay to block synthetic click\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n }\r\n touchResetTimeout = window.setTimeout(() => {\r\n globalTouchActive = false;\r\n touchResetTimeout = null;\r\n }, TOUCH_GUARD_DURATION_MS);\r\n}\r\n\r\n/**\r\n * Check if a touch interaction is currently active.\r\n * Use this in mouse handlers to skip if touch is active.\r\n */\r\nexport function isTouchActive(): boolean {\r\n return globalTouchActive;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if slider becomes invisible while dragging\r\n useEffect(() => {\r\n if (!isVisible && isDraggingOrJustAfterRef.current) {\r\n setIsDragging(false);\r\n isDraggingOrJustAfterRef.current = false;\r\n touchingRef.current = false;\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n }\r\n }, [isVisible, effectiveDigitalJoin, dSet]);\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers - use global touch guard\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchEnd(); // Global guard with delayed reset\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["import_cris_webui_ch5_core","import_jsx_runtime","import_react","import_cris_webui_ch5_core","import_jsx_runtime","import_cris_webui_ch5_core","import_jsx_runtime","import_cris_webui_ch5_core","import_jsx_runtime"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/components/CrisButton.tsx
|
|
2
2
|
import { useState, useRef, useEffect } from "react";
|
|
3
|
-
import { useDigital, useJoinsStore } from "@imperosoft/cris-webui-core";
|
|
3
|
+
import { useDigital, useJoinsStore } from "@imperosoft/cris-webui-ch5-core";
|
|
4
4
|
|
|
5
5
|
// src/utils/icons.ts
|
|
6
6
|
var iconConfig = {
|
|
@@ -86,7 +86,6 @@ function CrisButton({
|
|
|
86
86
|
onRelease
|
|
87
87
|
}) {
|
|
88
88
|
const [pressed, setPressed] = useState(false);
|
|
89
|
-
const [hovering, setHovering] = useState(false);
|
|
90
89
|
const pressedRef = useRef(false);
|
|
91
90
|
const touchingRef = useRef(false);
|
|
92
91
|
const touchStartedHereRef = useRef(false);
|
|
@@ -99,7 +98,6 @@ function CrisButton({
|
|
|
99
98
|
const isVisible = joinVisible == null ? true : visible;
|
|
100
99
|
useEffect(() => {
|
|
101
100
|
if (!isVisible && pressedRef.current) {
|
|
102
|
-
console.log(`[CrisButton ${join}] Button hidden while pressed - sending release`);
|
|
103
101
|
pressedRef.current = false;
|
|
104
102
|
setPressed(false);
|
|
105
103
|
touchingRef.current = false;
|
|
@@ -119,7 +117,6 @@ function CrisButton({
|
|
|
119
117
|
currentText = textSelected;
|
|
120
118
|
}
|
|
121
119
|
const handlePress = () => {
|
|
122
|
-
console.log(`[CrisButton ${join}] handlePress - suppressKeyClicks:${suppressKeyClicks} pressedRef:${pressedRef.current}`);
|
|
123
120
|
if (suppressKeyClicks) return;
|
|
124
121
|
if (pressedRef.current) return;
|
|
125
122
|
pressedRef.current = true;
|
|
@@ -127,12 +124,10 @@ function CrisButton({
|
|
|
127
124
|
if (!isEnabled) return;
|
|
128
125
|
onPress?.();
|
|
129
126
|
if (join != null && smartId == null) {
|
|
130
|
-
console.log(`[CrisButton ${join}] SENDING PRESS (true)`);
|
|
131
127
|
dSet(join, true);
|
|
132
128
|
}
|
|
133
129
|
};
|
|
134
130
|
const handleRelease = () => {
|
|
135
|
-
console.log(`[CrisButton ${join}] handleRelease - suppressKeyClicks:${suppressKeyClicks} pressedRef:${pressedRef.current}`);
|
|
136
131
|
if (suppressKeyClicks) return;
|
|
137
132
|
if (!pressedRef.current) return;
|
|
138
133
|
pressedRef.current = false;
|
|
@@ -140,19 +135,16 @@ function CrisButton({
|
|
|
140
135
|
if (!isEnabled) return;
|
|
141
136
|
onRelease?.();
|
|
142
137
|
if (join != null && smartId == null) {
|
|
143
|
-
console.log(`[CrisButton ${join}] SENDING RELEASE (false)`);
|
|
144
138
|
dSet(join, false);
|
|
145
139
|
}
|
|
146
140
|
};
|
|
147
141
|
const handleTouchStart = () => {
|
|
148
|
-
console.log(`[CrisButton ${join}] handleTouchStart - touchStartedHereRef:${touchStartedHereRef.current}`);
|
|
149
142
|
touchStart();
|
|
150
143
|
touchingRef.current = true;
|
|
151
144
|
touchStartedHereRef.current = true;
|
|
152
145
|
handlePress();
|
|
153
146
|
};
|
|
154
147
|
const handleTouchEnd = () => {
|
|
155
|
-
console.log(`[CrisButton ${join}] handleTouchEnd - touchStartedHereRef:${touchStartedHereRef.current}`);
|
|
156
148
|
touchEnd();
|
|
157
149
|
touchingRef.current = true;
|
|
158
150
|
if (touchStartedHereRef.current) {
|
|
@@ -161,30 +153,21 @@ function CrisButton({
|
|
|
161
153
|
}
|
|
162
154
|
};
|
|
163
155
|
const handleTouchCancel = () => {
|
|
164
|
-
console.log(`[CrisButton ${join}] handleTouchCancel - touchStartedHereRef:${touchStartedHereRef.current}`);
|
|
165
156
|
touchEnd();
|
|
166
157
|
touchingRef.current = true;
|
|
167
158
|
touchStartedHereRef.current = false;
|
|
168
159
|
handleRelease();
|
|
169
160
|
};
|
|
170
161
|
const handleMouseDown = () => {
|
|
171
|
-
console.log(`[CrisButton ${join}] handleMouseDown - isTouchActive:${isTouchActive()} touchingRef:${touchingRef.current}`);
|
|
172
162
|
if (isTouchActive() || touchingRef.current) return;
|
|
173
163
|
handlePress();
|
|
174
164
|
};
|
|
175
165
|
const handleMouseUp = () => {
|
|
176
|
-
console.log(`[CrisButton ${join}] handleMouseUp - isTouchActive:${isTouchActive()} touchingRef:${touchingRef.current}`);
|
|
177
166
|
if (isTouchActive() || touchingRef.current) return;
|
|
178
167
|
handleRelease();
|
|
179
168
|
};
|
|
180
|
-
const handleMouseEnter = () => {
|
|
181
|
-
if (isTouchActive() || touchingRef.current) return;
|
|
182
|
-
if (suppressKeyClicks) return;
|
|
183
|
-
setHovering(true);
|
|
184
|
-
};
|
|
185
169
|
const handleMouseLeave = () => {
|
|
186
170
|
if (isTouchActive() || touchingRef.current) return;
|
|
187
|
-
setHovering(false);
|
|
188
171
|
handleRelease();
|
|
189
172
|
};
|
|
190
173
|
if (!isVisible) return null;
|
|
@@ -276,7 +259,6 @@ function CrisButton({
|
|
|
276
259
|
},
|
|
277
260
|
onMouseDown: handleMouseDown,
|
|
278
261
|
onMouseUp: handleMouseUp,
|
|
279
|
-
onMouseEnter: handleMouseEnter,
|
|
280
262
|
onMouseLeave: handleMouseLeave,
|
|
281
263
|
onTouchStart: handleTouchStart,
|
|
282
264
|
onTouchEnd: handleTouchEnd,
|
|
@@ -285,13 +267,13 @@ function CrisButton({
|
|
|
285
267
|
/* @__PURE__ */ jsx(
|
|
286
268
|
"div",
|
|
287
269
|
{
|
|
288
|
-
className: "cris-button-
|
|
270
|
+
className: "cris-button-press-overlay",
|
|
289
271
|
style: {
|
|
290
272
|
position: "absolute",
|
|
291
273
|
inset: 0,
|
|
292
274
|
backgroundColor: "black",
|
|
293
|
-
opacity:
|
|
294
|
-
transition: "opacity 0.
|
|
275
|
+
opacity: pressed ? 0.1 : 0,
|
|
276
|
+
transition: "opacity 0.1s",
|
|
295
277
|
pointerEvents: "none",
|
|
296
278
|
borderRadius: "inherit"
|
|
297
279
|
}
|
|
@@ -306,7 +288,7 @@ function CrisButton({
|
|
|
306
288
|
}
|
|
307
289
|
|
|
308
290
|
// src/components/CrisText.tsx
|
|
309
|
-
import { useDigital as useDigital2, useSerial, useCipDecode } from "@imperosoft/cris-webui-core";
|
|
291
|
+
import { useDigital as useDigital2, useSerial, useCipDecode } from "@imperosoft/cris-webui-ch5-core";
|
|
310
292
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
311
293
|
function CrisText({
|
|
312
294
|
joinIndirectText,
|
|
@@ -342,7 +324,7 @@ function CrisText({
|
|
|
342
324
|
|
|
343
325
|
// src/components/CrisSlider.tsx
|
|
344
326
|
import { useState as useState2, useRef as useRef2, useEffect as useEffect2, useCallback } from "react";
|
|
345
|
-
import { useDigital as useDigital3, useAnalog, useJoinsStore as useJoinsStore2 } from "@imperosoft/cris-webui-core";
|
|
327
|
+
import { useDigital as useDigital3, useAnalog, useJoinsStore as useJoinsStore2 } from "@imperosoft/cris-webui-ch5-core";
|
|
346
328
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
347
329
|
function CrisSlider({
|
|
348
330
|
join,
|
|
@@ -384,7 +366,6 @@ function CrisSlider({
|
|
|
384
366
|
const isVisible = joinVisible == null ? true : visible;
|
|
385
367
|
useEffect2(() => {
|
|
386
368
|
if (!isVisible && isDraggingOrJustAfterRef.current) {
|
|
387
|
-
console.log(`[CrisSlider ${effectiveDigitalJoin}] Slider hidden while dragging - sending release`);
|
|
388
369
|
setIsDragging(false);
|
|
389
370
|
isDraggingOrJustAfterRef.current = false;
|
|
390
371
|
touchingRef.current = false;
|
|
@@ -607,7 +588,7 @@ function CrisSlider({
|
|
|
607
588
|
}
|
|
608
589
|
|
|
609
590
|
// src/components/CrisGauge.tsx
|
|
610
|
-
import { useAnalog as useAnalog2, useDigital as useDigital4 } from "@imperosoft/cris-webui-core";
|
|
591
|
+
import { useAnalog as useAnalog2, useDigital as useDigital4 } from "@imperosoft/cris-webui-ch5-core";
|
|
611
592
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
612
593
|
function CrisGauge({
|
|
613
594
|
value,
|
|
@@ -717,7 +698,7 @@ function CrisGauge({
|
|
|
717
698
|
}
|
|
718
699
|
|
|
719
700
|
// src/components/CrisSpinner.tsx
|
|
720
|
-
import { useAnalog as useAnalog3, useDigital as useDigital5 } from "@imperosoft/cris-webui-core";
|
|
701
|
+
import { useAnalog as useAnalog3, useDigital as useDigital5 } from "@imperosoft/cris-webui-ch5-core";
|
|
721
702
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
722
703
|
var SPEED_DURATIONS = {
|
|
723
704
|
slow: "1.5s",
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/utils/touchGuard.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx"],"sourcesContent":["import { useState, useRef, useEffect, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n}: CrisButtonProps) {\r\n const [pressed, setPressed] = useState(false);\r\n const [hovering, setHovering] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if button becomes invisible while pressed\r\n useEffect(() => {\r\n if (!isVisible && pressedRef.current) {\r\n console.log(`[CrisButton ${join}] Button hidden while pressed - sending release`);\r\n pressedRef.current = false;\r\n setPressed(false);\r\n touchingRef.current = false;\r\n touchStartedHereRef.current = false;\r\n if (join != null && smartId == null) {\r\n dSet(join, false);\r\n }\r\n }\r\n }, [isVisible, join, smartId, dSet]);\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n console.log(`[CrisButton ${join}] handlePress - suppressKeyClicks:${suppressKeyClicks} pressedRef:${pressedRef.current}`);\r\n if (suppressKeyClicks) return;\r\n if (pressedRef.current) return;\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n console.log(`[CrisButton ${join}] SENDING PRESS (true)`);\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n console.log(`[CrisButton ${join}] handleRelease - suppressKeyClicks:${suppressKeyClicks} pressedRef:${pressedRef.current}`);\r\n if (suppressKeyClicks) return;\r\n if (!pressedRef.current) return;\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n console.log(`[CrisButton ${join}] SENDING RELEASE (false)`);\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - use global touch guard to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n console.log(`[CrisButton ${join}] handleTouchStart - touchStartedHereRef:${touchStartedHereRef.current}`);\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n console.log(`[CrisButton ${join}] handleTouchEnd - touchStartedHereRef:${touchStartedHereRef.current}`);\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n }\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n console.log(`[CrisButton ${join}] handleTouchCancel - touchStartedHereRef:${touchStartedHereRef.current}`);\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n };\r\n\r\n // Mouse event handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = () => {\r\n console.log(`[CrisButton ${join}] handleMouseDown - isTouchActive:${isTouchActive()} touchingRef:${touchingRef.current}`);\r\n if (isTouchActive() || touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n console.log(`[CrisButton ${join}] handleMouseUp - isTouchActive:${isTouchActive()} touchingRef:${touchingRef.current}`);\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseEnter = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n if (suppressKeyClicks) return;\r\n setHovering(true);\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n setHovering(false);\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={computedIconStyle}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={computedIconStyle} alt=\"\" />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseEnter={handleMouseEnter}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Hover overlay - inline styles for touch-safe behavior */}\r\n <div\r\n className=\"cris-button-hover-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: hovering ? 0.1 : 0,\r\n transition: 'opacity 0.2s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","/**\r\n * Global touch state guard - shared across all CRIS components\r\n *\r\n * This prevents phantom clicks when elements swap visibility during touch interactions.\r\n * When a button is touched and triggers a visibility change (e.g., back button hides,\r\n * power button appears), the newly visible element would receive the synthetic click\r\n * event from the browser. The global touch flag blocks this.\r\n */\r\n\r\nlet globalTouchActive = false;\r\nlet touchResetTimeout: number | null = null;\r\n\r\n/** Duration to keep touch guard active after touch ends (blocks synthetic click) */\r\nconst TOUCH_GUARD_DURATION_MS = 300;\r\n\r\n/**\r\n * Mark that a touch interaction has started.\r\n * Call this in touchStart handlers.\r\n */\r\nexport function touchStart(): void {\r\n globalTouchActive = true;\r\n // Clear any pending reset\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n touchResetTimeout = null;\r\n }\r\n}\r\n\r\n/**\r\n * Mark that a touch interaction has ended.\r\n * The guard remains active for a short duration to block synthetic click events.\r\n * Call this in touchEnd/touchCancel handlers.\r\n */\r\nexport function touchEnd(): void {\r\n globalTouchActive = true;\r\n // Reset after delay to block synthetic click\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n }\r\n touchResetTimeout = window.setTimeout(() => {\r\n globalTouchActive = false;\r\n touchResetTimeout = null;\r\n }, TOUCH_GUARD_DURATION_MS);\r\n}\r\n\r\n/**\r\n * Check if a touch interaction is currently active.\r\n * Use this in mouse handlers to skip if touch is active.\r\n */\r\nexport function isTouchActive(): boolean {\r\n return globalTouchActive;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-core';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if slider becomes invisible while dragging\r\n useEffect(() => {\r\n if (!isVisible && isDraggingOrJustAfterRef.current) {\r\n console.log(`[CrisSlider ${effectiveDigitalJoin}] Slider hidden while dragging - sending release`);\r\n setIsDragging(false);\r\n isDraggingOrJustAfterRef.current = false;\r\n touchingRef.current = false;\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n }\r\n }, [isVisible, effectiveDigitalJoin, dSet]);\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers - use global touch guard\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchEnd(); // Global guard with delayed reset\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["useDigital","jsx","jsxs","useState","useRef","useEffect","useDigital","useJoinsStore","jsx","jsxs","useDigital","useJoinsStore","useState","useRef","useEffect","useAnalog","useDigital","jsx","useAnalog","useDigital","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/utils/touchGuard.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx"],"sourcesContent":["import { useState, useRef, useEffect, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n}: CrisButtonProps) {\r\n const [pressed, setPressed] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if button becomes invisible while pressed\r\n useEffect(() => {\r\n if (!isVisible && pressedRef.current) {\r\n pressedRef.current = false;\r\n setPressed(false);\r\n touchingRef.current = false;\r\n touchStartedHereRef.current = false;\r\n if (join != null && smartId == null) {\r\n dSet(join, false);\r\n }\r\n }\r\n }, [isVisible, join, smartId, dSet]);\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n if (suppressKeyClicks) return;\r\n if (pressedRef.current) return;\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n if (suppressKeyClicks) return;\r\n if (!pressedRef.current) return;\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - use global touch guard to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n }\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n };\r\n\r\n // Mouse event handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={computedIconStyle}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={computedIconStyle} alt=\"\" />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Press overlay - shows on press for both desktop and touch devices */}\r\n <div\r\n className=\"cris-button-press-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: pressed ? 0.1 : 0,\r\n transition: 'opacity 0.1s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","/**\r\n * Global touch state guard - shared across all CRIS components\r\n *\r\n * This prevents phantom clicks when elements swap visibility during touch interactions.\r\n * When a button is touched and triggers a visibility change (e.g., back button hides,\r\n * power button appears), the newly visible element would receive the synthetic click\r\n * event from the browser. The global touch flag blocks this.\r\n */\r\n\r\nlet globalTouchActive = false;\r\nlet touchResetTimeout: number | null = null;\r\n\r\n/** Duration to keep touch guard active after touch ends (blocks synthetic click) */\r\nconst TOUCH_GUARD_DURATION_MS = 300;\r\n\r\n/**\r\n * Mark that a touch interaction has started.\r\n * Call this in touchStart handlers.\r\n */\r\nexport function touchStart(): void {\r\n globalTouchActive = true;\r\n // Clear any pending reset\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n touchResetTimeout = null;\r\n }\r\n}\r\n\r\n/**\r\n * Mark that a touch interaction has ended.\r\n * The guard remains active for a short duration to block synthetic click events.\r\n * Call this in touchEnd/touchCancel handlers.\r\n */\r\nexport function touchEnd(): void {\r\n globalTouchActive = true;\r\n // Reset after delay to block synthetic click\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n }\r\n touchResetTimeout = window.setTimeout(() => {\r\n globalTouchActive = false;\r\n touchResetTimeout = null;\r\n }, TOUCH_GUARD_DURATION_MS);\r\n}\r\n\r\n/**\r\n * Check if a touch interaction is currently active.\r\n * Use this in mouse handlers to skip if touch is active.\r\n */\r\nexport function isTouchActive(): boolean {\r\n return globalTouchActive;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if slider becomes invisible while dragging\r\n useEffect(() => {\r\n if (!isVisible && isDraggingOrJustAfterRef.current) {\r\n setIsDragging(false);\r\n isDraggingOrJustAfterRef.current = false;\r\n touchingRef.current = false;\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n }\r\n }, [isVisible, effectiveDigitalJoin, dSet]);\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers - use global touch guard\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchEnd(); // Global guard with delayed reset\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n"],"mappings":";AAAA,SAAS,UAAU,QAAQ,iBAA4B;AACvD,SAAS,YAAY,qBAAqB;;;ACiB1C,IAAI,aAAyB;AAAA,EAC3B,UAAU;AAAA,EACV,WAAW;AAAA,EACX,eAAe;AAAA,EACf,cAAc;AAChB;AAoBO,SAAS,eAAe,QAAmC;AAChE,eAAa,EAAE,GAAG,YAAY,GAAG,OAAO;AAC1C;AAKO,SAAS,gBAA4B;AAC1C,SAAO;AACT;AAQO,SAAS,WAAW,MAAsB;AAC/C,MAAI,CAAC,KAAM,QAAO;AAGlB,MAAI,KAAK,WAAW,MAAM,KAAK,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,GAAG;AAC3E,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,UAAU,UAAU,IAAI;AAChC,QAAM,iBAAiB,SAAS,SAAS,GAAG,IAAI,WAAW,GAAG,QAAQ;AACtE,SAAO,GAAG,cAAc,GAAG,IAAI,IAAI,SAAS;AAC9C;AAKO,SAAS,cAAc,QAAqC;AACjE,SAAO,SAAS,WAAW,eAAe,WAAW;AACvD;;;ACrEA,IAAI,oBAAoB;AACxB,IAAI,oBAAmC;AAGvC,IAAM,0BAA0B;AAMzB,SAAS,aAAmB;AACjC,sBAAoB;AAEpB,MAAI,sBAAsB,MAAM;AAC9B,WAAO,aAAa,iBAAiB;AACrC,wBAAoB;AAAA,EACtB;AACF;AAOO,SAAS,WAAiB;AAC/B,sBAAoB;AAEpB,MAAI,sBAAsB,MAAM;AAC9B,WAAO,aAAa,iBAAiB;AAAA,EACvC;AACA,sBAAoB,OAAO,WAAW,MAAM;AAC1C,wBAAoB;AACpB,wBAAoB;AAAA,EACtB,GAAG,uBAAuB;AAC5B;AAMO,SAAS,gBAAyB;AACvC,SAAO;AACT;;;AF0Qa,cA8CT,YA9CS;AArPN,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB;AAAA,EACA,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AACF,GAAoB;AAClB,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,aAAa,OAAO,KAAK;AAC/B,QAAM,cAAc,OAAO,KAAK;AAChC,QAAM,sBAAsB,OAAO,KAAK;AAGxC,QAAM,eAAe,gBAAgB;AACrC,QAAM,WAAW,WAAW,gBAAgB,CAAC;AAC7C,QAAM,UAAU,WAAW,cAAc,CAAC;AAC1C,QAAM,UAAU,WAAW,eAAe,CAAC;AAG3C,QAAM,OAAO,cAAc,CAAC,UAAU,MAAM,IAAI;AAGhD,QAAM,YAAY,cAAc,OAAO,OAAO;AAG9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,YAAU,MAAM;AACd,QAAI,CAAC,aAAa,WAAW,SAAS;AACpC,iBAAW,UAAU;AACrB,iBAAW,KAAK;AAChB,kBAAY,UAAU;AACtB,0BAAoB,UAAU;AAC9B,UAAI,QAAQ,QAAQ,WAAW,MAAM;AACnC,aAAK,MAAM,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,WAAW,MAAM,SAAS,IAAI,CAAC;AAGnC,QAAM,qBAAqB,uBAAuB,gBAAgB,QAAQ;AAC1E,QAAM,qBAAqB,qBAAqB,WAAW;AAC3D,QAAM,WAAW,sBAAsB;AAGvC,MAAI,cAAc,QAAQ;AAC1B,MAAI,sBAAsB,eAAe,MAAM;AAC7C,kBAAc;AAAA,EAChB,WAAW,sBAAsB,gBAAgB,MAAM;AACrD,kBAAc;AAAA,EAChB;AAGA,QAAM,cAAc,MAAM;AACxB,QAAI,kBAAmB;AACvB,QAAI,WAAW,QAAS;AAExB,eAAW,UAAU;AACrB,eAAW,IAAI;AAEf,QAAI,CAAC,UAAW;AAGhB,cAAU;AAGV,QAAI,QAAQ,QAAQ,WAAW,MAAM;AACnC,WAAK,MAAM,IAAI;AAAA,IACjB;AAAA,EAEF;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,kBAAmB;AACvB,QAAI,CAAC,WAAW,QAAS;AAEzB,eAAW,UAAU;AACrB,eAAW,KAAK;AAEhB,QAAI,CAAC,UAAW;AAGhB,gBAAY;AAGZ,QAAI,QAAQ,QAAQ,WAAW,MAAM;AACnC,WAAK,MAAM,KAAK;AAAA,IAClB;AAAA,EAEF;AAIA,QAAM,mBAAmB,MAAM;AAC7B,eAAW;AACX,gBAAY,UAAU;AACtB,wBAAoB,UAAU;AAC9B,gBAAY;AAAA,EACd;AAEA,QAAM,iBAAiB,MAAM;AAC3B,aAAS;AACT,gBAAY,UAAU;AAEtB,QAAI,oBAAoB,SAAS;AAC/B,0BAAoB,UAAU;AAC9B,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,oBAAoB,MAAM;AAC9B,aAAS;AACT,gBAAY,UAAU;AACtB,wBAAoB,UAAU;AAC9B,kBAAc;AAAA,EAChB;AAGA,QAAM,kBAAkB,MAAM;AAC5B,QAAI,cAAc,KAAK,YAAY,QAAS;AAC5C,gBAAY;AAAA,EACd;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,cAAc,KAAK,YAAY,QAAS;AAC5C,kBAAc;AAAA,EAChB;AAEA,QAAM,mBAAmB,MAAM;AAC7B,QAAI,cAAc,KAAK,YAAY,QAAS;AAC5C,kBAAc;AAAA,EAChB;AAGA,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,IACtB,CAAC,aAAa;AAAA,IACd,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,IACtB,CAAC,aAAa;AAAA,IACd,qBAAqB;AAAA,EACvB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,gBACJ,iBAAiB,QACb,aACA,iBAAiB,WACf,qBACA,iBAAiB,SACf,aACA;AAGV,QAAM,UAAU,QAAQ,QAAQ,YAAY,QAAQ,aAAa,QAAQ,kBAAkB,QAAQ,mBAAmB;AAGtH,QAAM,kBAAkB,YAAY,kBAAkB,OAAO,iBAAiB;AAC9E,QAAM,mBAAmB,YAAY,mBAAmB,OAAO,kBAAkB;AACjF,QAAM,mBAAmB,YAAY,mBAAmB,OAAO,EAAE,GAAG,WAAW,GAAG,gBAAgB,IAAI;AAGtG,QAAM,cAAsC;AAAA,IAC1C,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,EACN;AAGA,QAAM,mBAAmB,MAA0B;AACjD,QAAI,YAAY,KAAM,QAAO;AAC7B,QAAI,OAAO,aAAa,SAAU,QAAO,GAAG,QAAQ;AACpD,QAAI,YAAY,YAAa,QAAO,YAAY,QAAQ;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,iBAAiB;AAGvC,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,EACxB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,oBAAyC;AAAA,IAC7C,GAAG;AAAA,EACL;AAGA,MAAI,eAAe;AACjB,sBAAkB,QAAQ;AAC1B,sBAAkB,SAAS;AAAA,EAC7B;AAGA,MAAI,mBAAmB,CAAC,kBAAkB,QAAQ;AAChD,UAAM,SAAS,cAAc,QAAQ;AACrC,QAAI,QAAQ;AACV,wBAAkB,SAAS;AAAA,IAC7B;AAAA,EACF;AAGA,QAAM,oBAAoB,MAAM;AAC9B,QAAI,MAAM;AAER,aAAO,oBAAC,UAAK,WAAW,aAAa,OAAO,mBAAoB,gBAAK;AAAA,IACvE;AAEA,QAAI,iBAAiB;AAEnB,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,UACX,OAAO;AAAA,UACP,KAAK,WAAW,eAAe;AAAA,UAC/B,KAAI;AAAA;AAAA,MACN;AAAA,IAEJ;AAEA,QAAI,kBAAkB;AAEpB,aAAO,oBAAC,SAAI,WAAW,aAAa,OAAO,mBAAmB,KAAI,IAAG;AAAA,IACvE;AAEA,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,iBAAiB,SAAS,iBAAiB;AAC9D,QAAM,UAAU,gBAAgB;AAChC,QAAM,uBAAuB,UAAW,aAAa,QAAQ,QAAS;AACtE,QAAM,gBAAgB,qBAAqB;AAG3C,QAAM,oBAAoB,eAAe,aAAa;AAGtD,QAAM,qBAA0C,aAC5C,EAAE,QAAQ,eAAe,OAAO,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS,IACxG,EAAE,OAAO,eAAe,QAAQ,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS;AAG5G,QAAM,qBAA0C,aAC5C,EAAE,QAAQ,mBAAmB,OAAO,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS,IAC5G,EAAE,OAAO,mBAAmB,QAAQ,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS;AAGhH,QAAM,mBAAmB,UAAU,SAAS,MAAM;AAElD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,OAAO,qCAAqC,aAAa;AAAA,MACvE,OAAO;AAAA,QACL,QAAQ,oBAAoB,YAAY;AAAA,QACxC,UAAU,mBAAmB,SAAY;AAAA,QACzC,UAAU,mBAAmB,SAAY;AAAA,MAC3C;AAAA,MACA,aAAa;AAAA,MACb,WAAW;AAAA,MACX,cAAc;AAAA,MACd,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,eAAe;AAAA,MAGf;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO;AAAA,cACL,UAAU;AAAA,cACV,OAAO;AAAA,cACP,iBAAiB;AAAA,cACjB,SAAS,UAAU,MAAM;AAAA,cACzB,YAAY;AAAA,cACZ,eAAe;AAAA,cACf,cAAc;AAAA,YAChB;AAAA;AAAA,QACF;AAAA,QACC;AAAA,QACA,WACC,oBAAC,SAAI,WAAU,8BAA6B,OAAO,oBAChD,4BAAkB,GACrB;AAAA,QAED,eACC,oBAAC,SAAI,WAAU,8BAA6B,OAAO,oBACjD,8BAAC,UAAK,WAAU,oBAAoB,uBAAY,GAClD;AAAA;AAAA;AAAA,EAEJ;AAEJ;;;AGlZA,SAAS,cAAAA,aAAY,WAAW,oBAAoB;AAoEhD,SAGI,OAAAC,MAHJ,QAAAC,aAAA;AA3CG,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,gBAAgB;AAAA,EAChB;AACF,GAAkB;AAEhB,QAAM,eAAe,UAAU,oBAAoB,CAAC;AACpD,QAAM,UAAUF,YAAW,cAAc,CAAC;AAC1C,QAAM,UAAUA,YAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,cAAc,OAAO,OAAO;AAG9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,UAAU,oBAAoB,OAAO,eAAgB,QAAQ;AAGnE,QAAM,cAAc,aAAa,OAAO;AAGxC,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,SACE,gBAAAE,MAAC,SAAI,WAAW,kBAAkB,OAC/B;AAAA;AAAA,IACA,eACC,gBAAAD,KAAC,UAAK,WAAW,kBAAkB,aAAa,IAAI,OAAO,WACxD,uBACH;AAAA,KAEJ;AAEJ;;;AC9EA,SAAS,YAAAE,WAAU,UAAAC,SAAQ,aAAAC,YAAW,mBAAmB;AACzD,SAAS,cAAAC,aAAY,WAAW,iBAAAC,sBAAqB;AAgWjD,SAmBE,OAAAC,MAnBF,QAAAC,aAAA;AA9SG,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,iCAAiC;AAAA,EACjC,YAAY;AAAA,EACZ;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA,gBAAgB;AAClB,GAAoB;AAElB,QAAM,sBAAsB,cAAc;AAC1C,QAAM,uBAAuB,eAAe;AAG5C,QAAM,cAAc,UAAU,uBAAuB,CAAC;AACtD,QAAM,UAAUC,YAAW,cAAc,CAAC;AAC1C,QAAM,UAAUA,YAAW,eAAe,CAAC;AAG3C,QAAM,OAAOC,eAAc,CAAC,UAAU,MAAM,IAAI;AAChD,QAAM,OAAOA,eAAc,CAAC,UAAU,MAAM,IAAI;AAGhD,QAAM,CAAC,cAAc,eAAe,IAAIC,UAAS,CAAC;AAClD,QAAM,CAAC,YAAY,aAAa,IAAIA,UAAS,KAAK;AAClD,QAAM,2BAA2BC,QAAO,KAAK;AAC7C,QAAM,qBAAqBA,QAAO,CAAC;AACnC,QAAM,sBAAsBA,QAAsB,IAAI;AACtD,QAAM,cAAcA,QAAO,KAAK;AAGhC,QAAM,YAAY,cAAc,OAAO,OAAO;AAC9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,aAAa,yBAAyB,SAAS;AAClD,oBAAc,KAAK;AACnB,+BAAyB,UAAU;AACnC,kBAAY,UAAU;AACtB,UAAI,wBAAwB,MAAM;AAChC,aAAK,sBAAsB,KAAK;AAAA,MAClC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,WAAW,sBAAsB,IAAI,CAAC;AAG1C,QAAM,gBAAgB;AAAA,IACpB,CAAC,UAAkB;AACjB,YAAM,QAAQ,WAAW;AACzB,UAAI,SAAS,EAAG,QAAO;AACvB,aAAO,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,QAAQ,YAAY,KAAK,CAAC;AAAA,IAC5D;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAGA,QAAM,gBAAgB;AAAA,IACpB,CAAC,UAAkB;AACjB,YAAM,QAAQ,WAAW;AACzB,aAAO,KAAK,MAAM,WAAW,QAAQ,KAAK;AAAA,IAC5C;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAGA,QAAM,qBAAqB,YAAY,MAAM;AAC3C,QAAI,CAAC,yBAAyB,WAAW,uBAAuB,MAAM;AACpE,sBAAgB,cAAc,WAAW,CAAC;AAAA,IAC5C;AAAA,EACF,GAAG,CAAC,aAAa,eAAe,mBAAmB,CAAC;AAGpD,EAAAA,WAAU,MAAM;AACd,uBAAmB;AAAA,EACrB,GAAG,CAAC,kBAAkB,CAAC;AAGvB,QAAM,kBAAkB,YAAY,MAAM;AACxC,QAAI,CAAC,UAAW;AAEhB,kBAAc,IAAI;AAGlB,QAAI,wBAAwB,MAAM;AAChC,WAAK,sBAAsB,IAAI;AAAA,IACjC;AAGA,QAAI,CAAC,yBAAyB,SAAS;AACrC,yBAAmB,UAAU;AAAA,IAC/B;AACA,6BAAyB,UAAU;AAGnC,QAAI,oBAAoB,YAAY,MAAM;AACxC,aAAO,aAAa,oBAAoB,OAAO;AAC/C,0BAAoB,UAAU;AAAA,IAChC;AAAA,EACF,GAAG,CAAC,WAAW,sBAAsB,MAAM,YAAY,CAAC;AAGxD,QAAM,gBAAgB,YAAY,MAAM;AACtC,QAAI,CAAC,WAAY;AAEjB,kBAAc,KAAK;AAGnB,QAAI,wBAAwB,MAAM;AAChC,WAAK,sBAAsB,KAAK;AAAA,IAClC;AAGA,QAAI,iCAAiC,GAAG;AACtC,0BAAoB,UAAU,OAAO,WAAW,MAAM;AACpD,iCAAyB,UAAU;AACnC,2BAAmB;AACnB,4BAAoB,UAAU;AAAA,MAChC,GAAG,8BAA8B;AAAA,IACnC,OAAO;AACL,+BAAyB,UAAU;AACnC,yBAAmB;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,YAAY,sBAAsB,MAAM,gCAAgC,kBAAkB,CAAC;AAG/F,QAAM,aAAa;AAAA,IACjB,CAAC,SAAiB,SAAiB,WAAoB;AACrD,UAAI,CAAC,WAAY;AAEjB,UAAI;AACJ,UAAI,YAAY;AACd,oBAAY,UAAU,OAAO,QAAQ,OAAO;AAAA,MAC9C,OAAO;AACL,mBAAW,KAAK,UAAU,OAAO,OAAO,OAAO;AAAA,MACjD;AACA,iBAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AAE5C,sBAAgB,QAAQ;AAGxB,UAAI,uBAAuB,MAAM;AAC/B,aAAK,qBAAqB,cAAc,QAAQ,CAAC;AAAA,MACnD;AAAA,IACF;AAAA,IACA,CAAC,YAAY,YAAY,qBAAqB,MAAM,aAAa;AAAA,EACnE;AAGA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,cAAc,KAAK,YAAY,QAAS;AAC5C,QAAI,MAAM,WAAW,EAAG;AAExB,oBAAgB;AAChB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,CAAC,WAAY;AACjB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,gBAAgB,MAAM;AAC1B,kBAAc;AAAA,EAChB;AAEA,QAAM,mBAAmB,CAAC,UAA4C;AACpE,QAAI,YAAY;AACd,YAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,iBAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,IACjD;AACA,kBAAc;AAAA,EAChB;AAGA,QAAM,mBAAmB,CAAC,UAA4C;AACpE,eAAW;AACX,gBAAY,UAAU;AACtB,oBAAgB;AAChB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,UAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,CAAC,WAAY;AACjB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,UAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,iBAAiB,MAAM;AAC3B,aAAS;AACT,kBAAc;AAAA,EAChB;AAEA,QAAM,oBAAoB,CAAC,UAA4C;AACrE,aAAS;AACT,QAAI,cAAc,MAAM,QAAQ,SAAS,GAAG;AAC1C,YAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,YAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,iBAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,IACjD;AACA,kBAAc;AAAA,EAChB;AAGA,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,mBAAwC;AAAA,IAC5C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,QAAQ,MAAM;AACpB,UAAM,QAAQ,MAAM,SAAS;AAC7B,qBAAiB,SAAS,GAAG,gBAAgB;AAC7C,qBAAiB,MAAM,IAAI,MAAM,oBAAoB,CAAC;AACtD,qBAAiB,QAAQ,GAAG,KAAK;AACjC,qBAAiB,OAAO,GAAG,IAAI;AAAA,EACjC,OAAO;AACL,UAAM,SAAS,MAAM;AACrB,UAAM,OAAO,MAAM,UAAU;AAC7B,qBAAiB,QAAQ,GAAG,gBAAgB;AAC5C,qBAAiB,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACvD,qBAAiB,SAAS,GAAG,MAAM;AACnC,qBAAiB,MAAM,GAAG,GAAG;AAAA,EAC/B;AAGA,QAAM,oBAAyC;AAAA,IAC7C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,QAAQ,MAAM;AACpB,UAAM,OAAO,mBAAmB;AAChC,UAAM,QAAQ,QAAQ;AACtB,sBAAkB,SAAS,GAAG,gBAAgB;AAC9C,sBAAkB,MAAM,IAAI,MAAM,oBAAoB,CAAC;AACvD,sBAAkB,QAAQ,GAAG,KAAK;AAClC,sBAAkB,OAAO,GAAG,IAAI;AAAA,EAClC,OAAO;AACL,UAAM,QAAQ,MAAM;AACpB,UAAM,MAAM,OAAO,QAAQ,eAAe,mBAAmB;AAC7D,UAAM,SAAS,QAAQ,mBAAmB,IAAI;AAC9C,sBAAkB,QAAQ,GAAG,gBAAgB;AAC7C,sBAAkB,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACxD,sBAAkB,SAAS,GAAG,MAAM;AACpC,sBAAkB,MAAM,GAAG,GAAG;AAAA,EAChC;AAGA,QAAM,qBAA0C;AAAA,IAC9C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,UAAU,MAAM;AACtB,UAAM,OAAO,UAAU;AACvB,uBAAmB,QAAQ,GAAG,gBAAgB;AAC9C,uBAAmB,SAAS;AAC5B,uBAAmB,OAAO,GAAG,IAAI;AAAA,EACnC,OAAO;AACL,UAAM,SAAS,MAAM;AACrB,UAAM,MAAM,SAAS,SAAS;AAC9B,uBAAmB,QAAQ;AAC3B,uBAAmB,SAAS,GAAG,gBAAgB;AAC/C,uBAAmB,MAAM,GAAG,GAAG;AAAA,EACjC;AAEA,SACE,gBAAAL;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,MACX,OAAO;AAAA,QACL,GAAG;AAAA,QACH,UAAU;AAAA,QACV,QAAQ,YAAY,YAAY;AAAA,QAChC,aAAa;AAAA,QACb,YAAY;AAAA,MACd;AAAA,MACA,aAAa;AAAA,MACb,aAAa;AAAA,MACb,WAAW;AAAA,MACX,cAAc;AAAA,MACd,cAAc;AAAA,MACd,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,eAAe;AAAA,MAGf;AAAA,wBAAAD,KAAC,SAAI,WAAW,mBAAmB,YAAY,IAAI,OAAO,kBAAkB;AAAA,QAG3E,CAAC,cACA,gBAAAA,KAAC,SAAI,WAAW,oBAAoB,aAAa,IAAI,OAAO,mBAAmB;AAAA,QAIjF,gBAAAA,KAAC,SAAI,WAAW,qBAAqB,cAAc,IAAI,OAAO,oBAAoB;AAAA;AAAA;AAAA,EACpF;AAEJ;;;AC/XA,SAAS,aAAAO,YAAW,cAAAC,mBAAkB;AAoM9B,gBAAAC,YAAA;AA3ID,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,cAAc;AAAA,EACd,YAAY;AAAA,EACZ;AAAA,EACA,2BAA2B;AAAA,EAC3B;AAAA,EACA,mBAAmB;AAAA,EACnB;AAAA,EACA,sBAAsB;AAAA,EACtB;AAAA,EACA,yBAAyB;AAAA,EACzB;AAAA,EACA,uBAAuB;AAAA,EACvB;AAAA,EACA,gBAAgB;AAClB,GAAmB;AAEjB,QAAM,YAAYF,WAAU,QAAQ,CAAC;AACrC,QAAM,UAAUC,YAAW,cAAc,CAAC;AAC1C,QAAM,UAAUA,YAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,cAAc,OAAO,OAAO;AAC9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,eAAe,QAAQ,OAAO,YAAa,SAAS;AAG1D,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,iBAAiB,CAAC,YAA+C;AACrE,UAAM,SAAS,WAAW;AAC1B,UAAM,YAAY,WAAW;AAE7B,UAAM,MACH,YAAY,gBAAgB,cAAgB,CAAC,YAAY,gBAAgB,eACtE,UAAU,IACV,WAAW;AAEjB,QAAI,OAAO,OAAQ,QAAO;AAC1B,QAAI,OAAO,UAAW,QAAO;AAC7B,WAAO;AAAA,EACT;AAGA,QAAM,kBAAkB,CAAC,YAA6B;AACpD,QAAI,eAAe,SAAU,QAAO;AACpC,QAAI,eAAe,SAAU,QAAO;AAEpC,UAAM,QAAQ,gBAAgB,WAAW;AACzC,QAAI,SAAS,KAAK,MAAM,WAAW,KAAK;AACxC,QAAI,UAAU,KAAK,WAAW,EAAG,UAAS;AAE1C,UAAM,MACH,YAAY,gBAAgB,cAAgB,CAAC,YAAY,gBAAgB,eACtE,UAAU,IACV,WAAW;AAEjB,WAAO,OAAO;AAAA,EAChB;AAGA,QAAM,kBAAkB,CAAC,YAA4B;AACnD,UAAM,OAAO,eAAe,OAAO;AACnC,UAAM,SAAS,gBAAgB,OAAO;AAEtC,UAAM,UAAU,CAAC,sBAAsB,MAAM,WAAW;AAExD,QAAI,QAAQ;AACV,cAAQ,KAAK,QAAQ;AAErB,UAAI,SAAS,UAAU,uBAAuB,mBAAmB;AAC/D,gBAAQ,KAAK,uBAAuB,gBAAgB;AAAA,MACtD,WAAW,SAAS,aAAa,0BAA0B,mBAAmB;AAC5E,gBAAQ,KAAK,0BAA0B,gBAAgB;AAAA,MACzD,WAAW,SAAS,WAAW,wBAAwB,mBAAmB;AACxE,gBAAQ,KAAK,wBAAwB,gBAAgB;AAAA,MACvD;AAAA,IACF,OAAO;AACL,UAAI,0BAA0B;AAC5B,gBAAQ,KAAK,wBAAwB;AAAA,MACvC;AAAA,IACF;AAEA,WAAO,QAAQ,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,EACzC;AAGA,QAAM,kBAAkB,CAAC,YAAqD;AAC5E,UAAM,OAAO,eAAe,OAAO;AACnC,UAAM,SAAS,gBAAgB,OAAO;AAEtC,QAAI,QAAQ;AACV,UAAI,SAAS,UAAU,mBAAmB,eAAe;AACvD,eAAO,mBAAmB;AAAA,MAC5B,WAAW,SAAS,aAAa,sBAAsB,eAAe;AACpE,eAAO,sBAAsB;AAAA,MAC/B,WAAW,SAAS,WAAW,oBAAoB,eAAe;AAChE,eAAO,oBAAoB;AAAA,MAC7B;AAAA,IACF,OAAO;AACL,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAGA,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,iBAAsC;AAAA,IAC1C,GAAG;AAAA,IACH,SAAS;AAAA,IACT,eAAe,gBAAgB,eAAe,QAAQ;AAAA,IACtD,gBAAgB;AAAA,IAChB,YAAY;AAAA,EACd;AAEA,SACE,gBAAAC,KAAC,SAAI,WAAW,kBAAkB,OAAO,gBACtC,gBAAM,KAAK,EAAE,QAAQ,SAAS,GAAG,CAAC,GAAG,YACpC,gBAAAA;AAAA,IAAC;AAAA;AAAA,MAEC,WAAW,gBAAgB,OAAO;AAAA,MAClC,OAAO,gBAAgB,OAAO;AAAA;AAAA,IAFzB;AAAA,EAGP,CACD,GACH;AAEJ;;;AC5MA,SAAS,aAAAC,YAAW,cAAAC,mBAAkB;AAsFlC,SACE,OAAAC,MADF,QAAAC,aAAA;AAvDJ,IAAM,kBAAgD;AAAA,EACpD,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;AAEO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ;AACF,GAAqB;AAEnB,QAAM,oBAAoBH,WAAU,gBAAgB,CAAC;AACrD,QAAM,UAAUC,YAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,kBAAkB,gBAAgB,OAAO,oBAAqB,YAAY;AAChF,QAAM,kBAAmB,kBAAkB,QAAS;AAGpD,QAAM,mBAAmB,CAAC,gBAAgB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAG7E,QAAM,eAAoC;AAAA,IACxC,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,QAAQ,GAAG,SAAS,YAAY,UAAU;AAAA,IAC1C,gBAAgB;AAAA,IAChB,WAAW;AAAA,EACb;AAEA,MAAI,SAAS;AAEX,iBAAa,YAAY,uBAAuB,gBAAgB,YAAY,CAAC;AAAA,EAC/E,OAAO;AAEL,iBAAa,YAAY,UAAU,eAAe;AAClD,iBAAa,aAAa;AAAA,EAC5B;AAEA,SACE,gBAAAE,MAAC,SAAI,WAAW,kBAAkB,OAChC;AAAA,oBAAAD,KAAC,SAAI,OAAO,cAAc;AAAA,IAC1B,gBAAAA,KAAC,WAAO;AAAA;AAAA;AAAA;AAAA,SAIN;AAAA,KACJ;AAEJ;","names":["useDigital","jsx","jsxs","useState","useRef","useEffect","useDigital","useJoinsStore","jsx","jsxs","useDigital","useJoinsStore","useState","useRef","useEffect","useAnalog","useDigital","jsx","useAnalog","useDigital","jsx","jsxs"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imperosoft/cris-webui-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-beta.0",
|
|
4
4
|
"description": "CRIS - Crestron React Impero Soft WebUI components library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"react": ">=18.0.0",
|
|
39
|
-
"@imperosoft/cris-webui-core": ">=
|
|
39
|
+
"@imperosoft/cris-webui-ch5-core": ">=1.0.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@imperosoft/cris-webui-core": "
|
|
42
|
+
"@imperosoft/cris-webui-ch5-core": "file:../cris-webui-ch5-core",
|
|
43
43
|
"@types/react": "^18.2.0",
|
|
44
44
|
"react": "^18.2.0",
|
|
45
45
|
"tsup": "^8.0.0",
|