@procaaso/alphinity-ui-components 1.0.11 → 1.0.12

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 CHANGED
@@ -911,7 +911,10 @@ function DeviceControlPanel({
911
911
  onParameterChange,
912
912
  onStart,
913
913
  onStop,
914
- onCustomAction
914
+ onCustomAction,
915
+ onOpenConfig,
916
+ showConfigButton = true,
917
+ modeSelectionStyle = "buttons"
915
918
  }) {
916
919
  const [localParameterValues, setLocalParameterValues] = useState5({});
917
920
  const [showToast, setShowToast] = useState5(true);
@@ -990,14 +993,13 @@ function DeviceControlPanel({
990
993
  position: "fixed",
991
994
  ...position === "bottom" ? {
992
995
  bottom: 0,
993
- ...align === "left" ? { left: 0, right: "auto" } : align === "right" ? { right: 0, left: "auto" } : { left: 0, right: 0, marginLeft: "auto", marginRight: "auto" },
994
- animation: "slideUp 0.3s ease-out",
995
- maxWidth
996
+ ...align === "left" ? { left: 0, right: "auto", width: maxWidth } : align === "right" ? { right: 0, left: "auto", width: maxWidth } : { left: 0, right: 0, marginLeft: "auto", marginRight: "auto", maxWidth },
997
+ animation: "slideUp 0.3s ease-out"
996
998
  } : {
997
999
  right: 0,
998
1000
  top: 0,
999
1001
  bottom: 0,
1000
- width: "400px",
1002
+ width: maxWidth || "400px",
1001
1003
  animation: "slideIn 0.3s ease-out"
1002
1004
  },
1003
1005
  backgroundColor,
@@ -1055,25 +1057,48 @@ function DeviceControlPanel({
1055
1057
  }
1056
1058
  )
1057
1059
  ] }),
1058
- /* @__PURE__ */ jsx8(
1059
- "button",
1060
- {
1061
- onClick: onClose,
1062
- style: {
1063
- width: touchSize * 0.7,
1064
- height: touchSize * 0.7,
1065
- backgroundColor: surfaceColor,
1066
- color: textColor,
1067
- border: `2px solid ${borderColor}`,
1068
- borderRadius: "2px",
1069
- fontSize: "18px",
1070
- cursor: "pointer",
1071
- fontWeight: "bold",
1072
- fontFamily: "Arial, sans-serif"
1073
- },
1074
- children: "\xD7"
1075
- }
1076
- )
1060
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "8px" }, children: [
1061
+ showConfigButton && onOpenConfig && /* @__PURE__ */ jsx8(
1062
+ "button",
1063
+ {
1064
+ onClick: onOpenConfig,
1065
+ title: "Open Configuration",
1066
+ style: {
1067
+ width: touchSize * 0.7,
1068
+ height: touchSize * 0.7,
1069
+ backgroundColor: surfaceColor,
1070
+ color: textColor,
1071
+ border: `2px solid ${borderColor}`,
1072
+ borderRadius: "2px",
1073
+ fontSize: "16px",
1074
+ cursor: "pointer",
1075
+ fontWeight: 600,
1076
+ fontFamily: "Arial, sans-serif",
1077
+ padding: "0"
1078
+ },
1079
+ children: "\u2699"
1080
+ }
1081
+ ),
1082
+ /* @__PURE__ */ jsx8(
1083
+ "button",
1084
+ {
1085
+ onClick: onClose,
1086
+ style: {
1087
+ width: touchSize * 0.7,
1088
+ height: touchSize * 0.7,
1089
+ backgroundColor: surfaceColor,
1090
+ color: textColor,
1091
+ border: `2px solid ${borderColor}`,
1092
+ borderRadius: "2px",
1093
+ fontSize: "18px",
1094
+ cursor: "pointer",
1095
+ fontWeight: "bold",
1096
+ fontFamily: "Arial, sans-serif"
1097
+ },
1098
+ children: "\xD7"
1099
+ }
1100
+ )
1101
+ ] })
1077
1102
  ]
1078
1103
  }
1079
1104
  ),
@@ -1168,7 +1193,30 @@ function DeviceControlPanel({
1168
1193
  children: "Mode"
1169
1194
  }
1170
1195
  ),
1171
- /* @__PURE__ */ jsx8(
1196
+ modeSelectionStyle === "dropdown" ? /* @__PURE__ */ jsx8(
1197
+ "select",
1198
+ {
1199
+ value: currentMode,
1200
+ onChange: (e) => handleModeChange(e.target.value),
1201
+ style: {
1202
+ width: "100%",
1203
+ height: touchSize,
1204
+ backgroundColor: surfaceColor,
1205
+ color: textColor,
1206
+ border: `1px solid ${borderColor}`,
1207
+ borderRadius: "6px",
1208
+ fontSize: `${fontSize.label}px`,
1209
+ fontWeight: 600,
1210
+ fontFamily: "Arial, sans-serif",
1211
+ letterSpacing: "0.5px",
1212
+ textTransform: "uppercase",
1213
+ paddingLeft: "12px",
1214
+ paddingRight: "12px",
1215
+ cursor: "pointer"
1216
+ },
1217
+ children: binding.modes.map((mode) => /* @__PURE__ */ jsx8("option", { value: mode.value, disabled: mode.enabled === false, children: mode.label }, mode.value))
1218
+ }
1219
+ ) : /* @__PURE__ */ jsx8(
1172
1220
  "div",
1173
1221
  {
1174
1222
  style: {
@@ -7497,8 +7545,938 @@ function useDeviceControls(initialBindings) {
7497
7545
  };
7498
7546
  }
7499
7547
 
7548
+ // src/diagram/schema/DeviceConfigBinding.ts
7549
+ function createConfigBinding(nodeId, tag, parameters, actions, options) {
7550
+ return {
7551
+ nodeId,
7552
+ tag,
7553
+ deviceName: options?.deviceName,
7554
+ deviceType: options?.deviceType,
7555
+ sections: options?.sections,
7556
+ parameters,
7557
+ state: {
7558
+ isDirty: false,
7559
+ isSaving: false,
7560
+ errors: {}
7561
+ },
7562
+ actions,
7563
+ display: options?.display,
7564
+ theme: options?.theme,
7565
+ metadata: options?.metadata
7566
+ };
7567
+ }
7568
+
7569
+ // src/hooks/useDeviceConfigs.ts
7570
+ import { useState as useState16, useCallback as useCallback9 } from "react";
7571
+ function useDeviceConfigs(initialConfigs) {
7572
+ const [configs, setConfigs] = useState16(() => {
7573
+ const map = /* @__PURE__ */ new Map();
7574
+ if (initialConfigs) {
7575
+ initialConfigs.forEach((config) => {
7576
+ map.set(config.nodeId, config);
7577
+ });
7578
+ }
7579
+ return map;
7580
+ });
7581
+ const getConfig = useCallback9(
7582
+ (nodeId) => {
7583
+ return configs.get(nodeId);
7584
+ },
7585
+ [configs]
7586
+ );
7587
+ const updateConfig = useCallback9((nodeId, updates) => {
7588
+ setConfigs((prev) => {
7589
+ const newMap = new Map(prev);
7590
+ const existing = newMap.get(nodeId);
7591
+ if (existing) {
7592
+ newMap.set(nodeId, { ...existing, ...updates });
7593
+ } else {
7594
+ if ("tag" in updates && "parameters" in updates && "state" in updates && "actions" in updates) {
7595
+ newMap.set(nodeId, { nodeId, ...updates });
7596
+ }
7597
+ }
7598
+ return newMap;
7599
+ });
7600
+ }, []);
7601
+ const updateConfigState = useCallback9((nodeId, stateUpdates) => {
7602
+ setConfigs((prev) => {
7603
+ const newMap = new Map(prev);
7604
+ const existing = newMap.get(nodeId);
7605
+ if (existing) {
7606
+ newMap.set(nodeId, {
7607
+ ...existing,
7608
+ state: { ...existing.state, ...stateUpdates }
7609
+ });
7610
+ }
7611
+ return newMap;
7612
+ });
7613
+ }, []);
7614
+ const updateConfigBatch = useCallback9((updates) => {
7615
+ setConfigs((prev) => {
7616
+ const newMap = new Map(prev);
7617
+ Object.entries(updates).forEach(([nodeId, update]) => {
7618
+ const existing = newMap.get(nodeId);
7619
+ if (existing) {
7620
+ newMap.set(nodeId, { ...existing, ...update });
7621
+ }
7622
+ });
7623
+ return newMap;
7624
+ });
7625
+ }, []);
7626
+ const updateParameterValue = useCallback9((nodeId, parameterId, value) => {
7627
+ setConfigs((prev) => {
7628
+ const newMap = new Map(prev);
7629
+ const existing = newMap.get(nodeId);
7630
+ if (existing) {
7631
+ const updatedParameters = existing.parameters.map(
7632
+ (param) => param.id === parameterId ? { ...param, value } : param
7633
+ );
7634
+ newMap.set(nodeId, {
7635
+ ...existing,
7636
+ parameters: updatedParameters,
7637
+ state: { ...existing.state, isDirty: true }
7638
+ });
7639
+ }
7640
+ return newMap;
7641
+ });
7642
+ }, []);
7643
+ const applyConfig = useCallback9(
7644
+ async (nodeId) => {
7645
+ const config = configs.get(nodeId);
7646
+ if (!config || !config.actions.onApply) {
7647
+ return { success: false, message: "No apply handler defined" };
7648
+ }
7649
+ updateConfigState(nodeId, { isSaving: true });
7650
+ try {
7651
+ const values = config.parameters.reduce(
7652
+ (acc, param) => {
7653
+ acc[param.id] = param.value;
7654
+ return acc;
7655
+ },
7656
+ {}
7657
+ );
7658
+ const result = await config.actions.onApply(values);
7659
+ updateConfigState(nodeId, {
7660
+ isSaving: false,
7661
+ lastSaveResult: result,
7662
+ isDirty: result.success ? false : config.state.isDirty
7663
+ });
7664
+ return result;
7665
+ } catch (error) {
7666
+ const message = error instanceof Error ? error.message : "Apply failed";
7667
+ updateConfigState(nodeId, {
7668
+ isSaving: false,
7669
+ lastSaveResult: { success: false, message }
7670
+ });
7671
+ return { success: false, message };
7672
+ }
7673
+ },
7674
+ [configs, updateConfigState]
7675
+ );
7676
+ const saveConfig = useCallback9(
7677
+ async (nodeId) => {
7678
+ const config = configs.get(nodeId);
7679
+ if (!config || !config.actions.onSave) {
7680
+ return { success: false, message: "No save handler defined" };
7681
+ }
7682
+ updateConfigState(nodeId, { isSaving: true });
7683
+ try {
7684
+ const values = config.parameters.reduce(
7685
+ (acc, param) => {
7686
+ acc[param.id] = param.value;
7687
+ return acc;
7688
+ },
7689
+ {}
7690
+ );
7691
+ const result = await config.actions.onSave(values);
7692
+ updateConfigState(nodeId, {
7693
+ isSaving: false,
7694
+ lastSaved: result.success ? Date.now() : config.state.lastSaved,
7695
+ lastSaveResult: result,
7696
+ isDirty: result.success ? false : config.state.isDirty
7697
+ });
7698
+ return result;
7699
+ } catch (error) {
7700
+ const message = error instanceof Error ? error.message : "Save failed";
7701
+ updateConfigState(nodeId, {
7702
+ isSaving: false,
7703
+ lastSaveResult: { success: false, message }
7704
+ });
7705
+ return { success: false, message };
7706
+ }
7707
+ },
7708
+ [configs, updateConfigState]
7709
+ );
7710
+ const resetConfig = useCallback9(
7711
+ async (nodeId) => {
7712
+ const config = configs.get(nodeId);
7713
+ if (!config) {
7714
+ return { success: false, message: "Configuration not found" };
7715
+ }
7716
+ try {
7717
+ const resetParameters = config.parameters.map((param) => ({
7718
+ ...param,
7719
+ value: param.defaultValue !== void 0 ? param.defaultValue : param.value
7720
+ }));
7721
+ updateConfig(nodeId, {
7722
+ parameters: resetParameters,
7723
+ state: { ...config.state, isDirty: true }
7724
+ });
7725
+ if (config.actions.onReset) {
7726
+ const result = await config.actions.onReset();
7727
+ updateConfigState(nodeId, {
7728
+ lastSaveResult: result
7729
+ });
7730
+ return result;
7731
+ }
7732
+ return { success: true, message: "Reset to defaults" };
7733
+ } catch (error) {
7734
+ const message = error instanceof Error ? error.message : "Reset failed";
7735
+ return { success: false, message };
7736
+ }
7737
+ },
7738
+ [configs, updateConfig, updateConfigState]
7739
+ );
7740
+ const cancelConfig = useCallback9(
7741
+ (nodeId) => {
7742
+ const config = configs.get(nodeId);
7743
+ if (config?.actions.onCancel) {
7744
+ config.actions.onCancel();
7745
+ }
7746
+ },
7747
+ [configs]
7748
+ );
7749
+ const validateConfig = useCallback9(
7750
+ (nodeId) => {
7751
+ const config = configs.get(nodeId);
7752
+ if (!config) {
7753
+ return { valid: false, errors: { _general: "Configuration not found" } };
7754
+ }
7755
+ const errors = {};
7756
+ const values = config.parameters.reduce(
7757
+ (acc, param) => {
7758
+ acc[param.id] = param.value;
7759
+ return acc;
7760
+ },
7761
+ {}
7762
+ );
7763
+ config.parameters.forEach((param) => {
7764
+ if (param.required && (param.value === void 0 || param.value === null || param.value === "")) {
7765
+ errors[param.id] = `${param.label} is required`;
7766
+ return;
7767
+ }
7768
+ if (param.type === "number" && typeof param.value === "number") {
7769
+ if (param.min !== void 0 && param.value < param.min) {
7770
+ errors[param.id] = `${param.label} must be at least ${param.min}`;
7771
+ }
7772
+ if (param.max !== void 0 && param.value > param.max) {
7773
+ errors[param.id] = `${param.label} must be at most ${param.max}`;
7774
+ }
7775
+ }
7776
+ if (param.validate) {
7777
+ const result = param.validate(param.value);
7778
+ if (result !== true) {
7779
+ errors[param.id] = typeof result === "string" ? result : `${param.label} is invalid`;
7780
+ }
7781
+ }
7782
+ if (param.visibleWhen && !param.visibleWhen(values)) {
7783
+ delete errors[param.id];
7784
+ }
7785
+ });
7786
+ updateConfigState(nodeId, { errors });
7787
+ return { valid: Object.keys(errors).length === 0, errors };
7788
+ },
7789
+ [configs, updateConfigState]
7790
+ );
7791
+ const setConfig = useCallback9((config) => {
7792
+ setConfigs((prev) => {
7793
+ const newMap = new Map(prev);
7794
+ newMap.set(config.nodeId, config);
7795
+ return newMap;
7796
+ });
7797
+ }, []);
7798
+ const removeConfig = useCallback9((nodeId) => {
7799
+ setConfigs((prev) => {
7800
+ const newMap = new Map(prev);
7801
+ newMap.delete(nodeId);
7802
+ return newMap;
7803
+ });
7804
+ }, []);
7805
+ const clearConfigs = useCallback9(() => {
7806
+ setConfigs(/* @__PURE__ */ new Map());
7807
+ }, []);
7808
+ return {
7809
+ configs,
7810
+ getConfig,
7811
+ setConfig,
7812
+ removeConfig,
7813
+ clearConfigs,
7814
+ updateConfig,
7815
+ updateConfigState,
7816
+ updateConfigBatch,
7817
+ updateParameterValue,
7818
+ applyConfig,
7819
+ saveConfig,
7820
+ resetConfig,
7821
+ cancelConfig,
7822
+ validateConfig
7823
+ };
7824
+ }
7825
+
7826
+ // src/components/DeviceConfigPanel/DeviceConfigPanel.tsx
7827
+ import { useState as useState17, useEffect as useEffect12, useMemo, useRef as useRef6 } from "react";
7828
+ import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
7829
+ function DeviceConfigPanel({
7830
+ binding,
7831
+ onClose,
7832
+ position = "bottom",
7833
+ align = "center",
7834
+ touchOptimized = true,
7835
+ compact = false,
7836
+ fontScale = 1,
7837
+ maxHeight,
7838
+ maxWidth,
7839
+ toastDuration = 3e3,
7840
+ onOpenControls,
7841
+ showControlsButton = true
7842
+ }) {
7843
+ const [localValues, setLocalValues] = useState17(() => {
7844
+ if (binding) {
7845
+ const values = {};
7846
+ binding.parameters.forEach((param) => {
7847
+ values[param.id] = param.value;
7848
+ });
7849
+ return values;
7850
+ }
7851
+ return {};
7852
+ });
7853
+ const [collapsedSections, setCollapsedSections] = useState17(() => {
7854
+ const collapsed = /* @__PURE__ */ new Set();
7855
+ if (binding?.sections) {
7856
+ binding.sections.forEach((section) => {
7857
+ if (section.collapsible && section.defaultCollapsed) {
7858
+ collapsed.add(section.id);
7859
+ }
7860
+ });
7861
+ }
7862
+ return collapsed;
7863
+ });
7864
+ const [showToast, setShowToast] = useState17(true);
7865
+ const [bindingId, setBindingId] = useState17(binding?.nodeId || null);
7866
+ const lastResultTimestampRef = useRef6(0);
7867
+ if (binding && binding.nodeId !== bindingId) {
7868
+ const values = {};
7869
+ binding.parameters.forEach((param) => {
7870
+ values[param.id] = param.value;
7871
+ });
7872
+ setLocalValues(values);
7873
+ const collapsed = /* @__PURE__ */ new Set();
7874
+ binding.sections?.forEach((section) => {
7875
+ if (section.collapsible && section.defaultCollapsed) {
7876
+ collapsed.add(section.id);
7877
+ }
7878
+ });
7879
+ setCollapsedSections(collapsed);
7880
+ setBindingId(binding.nodeId);
7881
+ }
7882
+ useEffect12(() => {
7883
+ const currentTimestamp = binding?.state.lastSaved || 0;
7884
+ if (binding?.state.lastSaveResult && currentTimestamp !== lastResultTimestampRef.current) {
7885
+ lastResultTimestampRef.current = currentTimestamp;
7886
+ const showTimer = setTimeout(() => {
7887
+ setShowToast(true);
7888
+ const hideTimer = setTimeout(() => {
7889
+ setShowToast(false);
7890
+ }, toastDuration);
7891
+ return () => clearTimeout(hideTimer);
7892
+ }, 0);
7893
+ return () => clearTimeout(showTimer);
7894
+ }
7895
+ }, [binding?.state.lastSaveResult, binding?.state.lastSaved, toastDuration]);
7896
+ const parametersBySection = useMemo(() => {
7897
+ if (!binding) return { _unsectioned: [] };
7898
+ const groups = { _unsectioned: [] };
7899
+ binding.parameters.forEach((param) => {
7900
+ const sectionId = param.section || "_unsectioned";
7901
+ if (!groups[sectionId]) {
7902
+ groups[sectionId] = [];
7903
+ }
7904
+ groups[sectionId].push(param);
7905
+ });
7906
+ Object.keys(groups).forEach((sectionId) => {
7907
+ groups[sectionId].sort((a, b) => (a.order || 0) - (b.order || 0));
7908
+ });
7909
+ return groups;
7910
+ }, [binding]);
7911
+ const sortedSections = useMemo(() => {
7912
+ if (!binding?.sections) return [];
7913
+ return [...binding.sections].sort((a, b) => (a.order || 0) - (b.order || 0));
7914
+ }, [binding]);
7915
+ if (!binding) return null;
7916
+ const touchSize = touchOptimized ? 56 : 40;
7917
+ const spacing = {
7918
+ padding: compact ? "12px" : "20px",
7919
+ gap: compact ? "6px" : "8px",
7920
+ marginBottom: compact ? "10px" : "16px",
7921
+ itemPadding: compact ? "8px 10px" : "10px 12px"
7922
+ };
7923
+ const fontSize = {
7924
+ title: Math.round(14 * fontScale),
7925
+ label: Math.round(11 * fontScale),
7926
+ value: Math.round(13 * fontScale),
7927
+ input: Math.round(13 * fontScale),
7928
+ button: Math.round(12 * fontScale),
7929
+ section: Math.round(12 * fontScale),
7930
+ description: Math.round(10 * fontScale)
7931
+ };
7932
+ const backgroundColor = binding.theme?.backgroundColor || "#edeeef";
7933
+ const surfaceColor = binding.theme?.surfaceColor || "#f5f6f7";
7934
+ const borderColor = binding.theme?.borderColor || "#d2d2d2";
7935
+ const textColor = binding.theme?.textColor || "#000000";
7936
+ const mutedTextColor = binding.theme?.mutedTextColor || "#666666";
7937
+ const accentColor = binding.theme?.accentColor || "#b4d0fe";
7938
+ const errorColor = binding.theme?.errorColor || "#ef4444";
7939
+ const successColor = binding.theme?.successColor || "#10b981";
7940
+ const handleValueChange = (parameterId, value) => {
7941
+ setLocalValues((prev) => ({ ...prev, [parameterId]: value }));
7942
+ };
7943
+ const handleApply = async () => {
7944
+ if (binding.actions.onApply) {
7945
+ await binding.actions.onApply(localValues);
7946
+ }
7947
+ };
7948
+ const handleSave = async () => {
7949
+ if (binding.actions.onSave) {
7950
+ await binding.actions.onSave(localValues);
7951
+ }
7952
+ };
7953
+ const handleReset = async () => {
7954
+ if (binding.display?.resetConfirmMessage) {
7955
+ if (!confirm(binding.display.resetConfirmMessage)) {
7956
+ return;
7957
+ }
7958
+ }
7959
+ if (binding.actions.onReset) {
7960
+ await binding.actions.onReset();
7961
+ }
7962
+ };
7963
+ const handleCancel = () => {
7964
+ if (binding.actions.onCancel) {
7965
+ binding.actions.onCancel();
7966
+ }
7967
+ onClose();
7968
+ };
7969
+ const toggleSection = (sectionId) => {
7970
+ setCollapsedSections((prev) => {
7971
+ const next = new Set(prev);
7972
+ if (next.has(sectionId)) {
7973
+ next.delete(sectionId);
7974
+ } else {
7975
+ next.add(sectionId);
7976
+ }
7977
+ return next;
7978
+ });
7979
+ };
7980
+ const renderParameter = (param) => {
7981
+ if (param.visibleWhen && !param.visibleWhen(localValues)) {
7982
+ return null;
7983
+ }
7984
+ const value = localValues[param.id];
7985
+ const error = binding.state.errors?.[param.id];
7986
+ const isDisabled = param.readOnly || param.enabled === false;
7987
+ return /* @__PURE__ */ jsxs18(
7988
+ "div",
7989
+ {
7990
+ style: {
7991
+ marginBottom: spacing.marginBottom
7992
+ },
7993
+ children: [
7994
+ /* @__PURE__ */ jsxs18(
7995
+ "label",
7996
+ {
7997
+ style: {
7998
+ display: "block",
7999
+ fontSize: `${fontSize.label}px`,
8000
+ fontWeight: 600,
8001
+ color: textColor,
8002
+ marginBottom: "4px",
8003
+ fontFamily: "Arial, sans-serif",
8004
+ textTransform: "uppercase",
8005
+ letterSpacing: "0.5px"
8006
+ },
8007
+ children: [
8008
+ param.label,
8009
+ param.required && /* @__PURE__ */ jsx22("span", { style: { color: errorColor }, children: " *" }),
8010
+ param.unit && /* @__PURE__ */ jsxs18("span", { style: { color: mutedTextColor, fontWeight: "normal" }, children: [
8011
+ " (",
8012
+ param.unit,
8013
+ ")"
8014
+ ] })
8015
+ ]
8016
+ }
8017
+ ),
8018
+ param.description && /* @__PURE__ */ jsx22(
8019
+ "div",
8020
+ {
8021
+ style: {
8022
+ fontSize: `${fontSize.description}px`,
8023
+ color: mutedTextColor,
8024
+ marginBottom: "6px",
8025
+ fontFamily: "Arial, sans-serif"
8026
+ },
8027
+ children: param.description
8028
+ }
8029
+ ),
8030
+ param.type === "number" && /* @__PURE__ */ jsx22(
8031
+ "input",
8032
+ {
8033
+ type: "number",
8034
+ value: value ?? "",
8035
+ onChange: (e) => handleValueChange(param.id, parseFloat(e.target.value)),
8036
+ min: param.min,
8037
+ max: param.max,
8038
+ step: param.step,
8039
+ disabled: isDisabled,
8040
+ style: {
8041
+ width: "100%",
8042
+ height: `${touchSize * 0.8}px`,
8043
+ padding: spacing.itemPadding,
8044
+ fontSize: `${fontSize.input}px`,
8045
+ fontFamily: "Arial, sans-serif",
8046
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8047
+ color: textColor,
8048
+ border: `1px solid ${error ? errorColor : borderColor}`,
8049
+ borderRadius: "2px",
8050
+ outline: "none"
8051
+ }
8052
+ }
8053
+ ),
8054
+ param.type === "string" && /* @__PURE__ */ jsx22(
8055
+ "input",
8056
+ {
8057
+ type: "text",
8058
+ value: value ?? "",
8059
+ onChange: (e) => handleValueChange(param.id, e.target.value),
8060
+ disabled: isDisabled,
8061
+ style: {
8062
+ width: "100%",
8063
+ height: `${touchSize * 0.8}px`,
8064
+ padding: spacing.itemPadding,
8065
+ fontSize: `${fontSize.input}px`,
8066
+ fontFamily: "Arial, sans-serif",
8067
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8068
+ color: textColor,
8069
+ border: `1px solid ${error ? errorColor : borderColor}`,
8070
+ borderRadius: "2px",
8071
+ outline: "none"
8072
+ }
8073
+ }
8074
+ ),
8075
+ param.type === "boolean" && /* @__PURE__ */ jsxs18(
8076
+ "label",
8077
+ {
8078
+ style: {
8079
+ display: "flex",
8080
+ alignItems: "center",
8081
+ gap: "8px",
8082
+ cursor: isDisabled ? "not-allowed" : "pointer"
8083
+ },
8084
+ children: [
8085
+ /* @__PURE__ */ jsx22(
8086
+ "input",
8087
+ {
8088
+ type: "checkbox",
8089
+ checked: value ?? false,
8090
+ onChange: (e) => handleValueChange(param.id, e.target.checked),
8091
+ disabled: isDisabled,
8092
+ style: {
8093
+ width: "20px",
8094
+ height: "20px",
8095
+ cursor: isDisabled ? "not-allowed" : "pointer"
8096
+ }
8097
+ }
8098
+ ),
8099
+ /* @__PURE__ */ jsx22("span", { style: { fontSize: `${fontSize.value}px`, color: textColor }, children: value ? "Enabled" : "Disabled" })
8100
+ ]
8101
+ }
8102
+ ),
8103
+ param.type === "select" && /* @__PURE__ */ jsx22(
8104
+ "select",
8105
+ {
8106
+ value: value ?? "",
8107
+ onChange: (e) => handleValueChange(param.id, e.target.value),
8108
+ disabled: isDisabled,
8109
+ style: {
8110
+ width: "100%",
8111
+ height: `${touchSize * 0.8}px`,
8112
+ padding: spacing.itemPadding,
8113
+ fontSize: `${fontSize.input}px`,
8114
+ fontFamily: "Arial, sans-serif",
8115
+ backgroundColor: isDisabled ? "#e5e7eb" : surfaceColor,
8116
+ color: textColor,
8117
+ border: `1px solid ${error ? errorColor : borderColor}`,
8118
+ borderRadius: "2px",
8119
+ outline: "none"
8120
+ },
8121
+ children: param.options?.map((option) => /* @__PURE__ */ jsx22("option", { value: option.value, children: option.label }, option.value))
8122
+ }
8123
+ ),
8124
+ error && /* @__PURE__ */ jsx22(
8125
+ "div",
8126
+ {
8127
+ style: {
8128
+ fontSize: `${fontSize.description}px`,
8129
+ color: errorColor,
8130
+ marginTop: "4px",
8131
+ fontFamily: "Arial, sans-serif"
8132
+ },
8133
+ children: error
8134
+ }
8135
+ )
8136
+ ]
8137
+ },
8138
+ param.id
8139
+ );
8140
+ };
8141
+ const renderSection = (section) => {
8142
+ const params = parametersBySection[section.id] || [];
8143
+ const isCollapsed = collapsedSections.has(section.id);
8144
+ return /* @__PURE__ */ jsxs18(
8145
+ "div",
8146
+ {
8147
+ style: {
8148
+ marginBottom: spacing.marginBottom,
8149
+ border: `1px solid ${borderColor}`,
8150
+ borderRadius: "4px",
8151
+ backgroundColor: surfaceColor
8152
+ },
8153
+ children: [
8154
+ /* @__PURE__ */ jsxs18(
8155
+ "div",
8156
+ {
8157
+ onClick: () => section.collapsible && toggleSection(section.id),
8158
+ style: {
8159
+ padding: spacing.itemPadding,
8160
+ borderBottom: isCollapsed ? "none" : `1px solid ${borderColor}`,
8161
+ cursor: section.collapsible ? "pointer" : "default",
8162
+ display: "flex",
8163
+ justifyContent: "space-between",
8164
+ alignItems: "center"
8165
+ },
8166
+ children: [
8167
+ /* @__PURE__ */ jsxs18("div", { children: [
8168
+ /* @__PURE__ */ jsxs18(
8169
+ "div",
8170
+ {
8171
+ style: {
8172
+ fontSize: `${fontSize.section}px`,
8173
+ fontWeight: 600,
8174
+ color: textColor,
8175
+ fontFamily: "Arial, sans-serif",
8176
+ textTransform: "uppercase",
8177
+ letterSpacing: "0.5px"
8178
+ },
8179
+ children: [
8180
+ section.icon && /* @__PURE__ */ jsx22("span", { style: { marginRight: "8px" }, children: section.icon }),
8181
+ section.title
8182
+ ]
8183
+ }
8184
+ ),
8185
+ section.description && /* @__PURE__ */ jsx22(
8186
+ "div",
8187
+ {
8188
+ style: {
8189
+ fontSize: `${fontSize.description}px`,
8190
+ color: mutedTextColor,
8191
+ marginTop: "4px",
8192
+ fontFamily: "Arial, sans-serif"
8193
+ },
8194
+ children: section.description
8195
+ }
8196
+ )
8197
+ ] }),
8198
+ section.collapsible && /* @__PURE__ */ jsx22("span", { style: { fontSize: "16px", color: mutedTextColor }, children: isCollapsed ? "\u25BC" : "\u25B2" })
8199
+ ]
8200
+ }
8201
+ ),
8202
+ !isCollapsed && /* @__PURE__ */ jsx22("div", { style: { padding: spacing.padding }, children: params.map(renderParameter) })
8203
+ ]
8204
+ },
8205
+ section.id
8206
+ );
8207
+ };
8208
+ const panelStyle = {
8209
+ position: "fixed",
8210
+ ...position === "bottom" ? {
8211
+ bottom: 0,
8212
+ ...align === "left" ? { left: 0, right: "auto", width: maxWidth } : align === "right" ? { right: 0, left: "auto", width: maxWidth } : { left: 0, right: 0, marginLeft: "auto", marginRight: "auto", maxWidth },
8213
+ animation: "slideUp 0.3s ease-out"
8214
+ } : {
8215
+ right: 0,
8216
+ top: 0,
8217
+ bottom: 0,
8218
+ width: maxWidth || "400px",
8219
+ animation: "slideIn 0.3s ease-out"
8220
+ },
8221
+ backgroundColor,
8222
+ color: textColor,
8223
+ borderRadius: position === "bottom" ? "8px 8px 0 0" : "8px 0 0 8px",
8224
+ boxShadow: "none",
8225
+ border: `2px solid ${borderColor}`,
8226
+ padding: spacing.padding,
8227
+ zIndex: 1e4,
8228
+ maxHeight: maxHeight || (position === "bottom" ? "70vh" : "100vh"),
8229
+ overflowY: "auto",
8230
+ display: "flex",
8231
+ flexDirection: "column",
8232
+ ...binding.display?.style
8233
+ };
8234
+ const titleText = binding.display?.title || binding.deviceName || binding.tag || binding.nodeId;
8235
+ const showApply = binding.display?.showApply !== false && binding.actions.onApply;
8236
+ const showSave = binding.display?.showSave !== false && binding.actions.onSave;
8237
+ const showReset = binding.display?.showReset !== false && binding.actions.onReset;
8238
+ const showCancel = binding.display?.showCancel !== false;
8239
+ return /* @__PURE__ */ jsxs18(Fragment9, { children: [
8240
+ /* @__PURE__ */ jsxs18("div", { style: panelStyle, className: binding.display?.className, children: [
8241
+ /* @__PURE__ */ jsxs18(
8242
+ "div",
8243
+ {
8244
+ style: {
8245
+ display: "flex",
8246
+ justifyContent: "space-between",
8247
+ alignItems: "center",
8248
+ marginBottom: "16px",
8249
+ paddingBottom: "12px",
8250
+ borderBottom: `2px solid ${borderColor}`
8251
+ },
8252
+ children: [
8253
+ /* @__PURE__ */ jsxs18("div", { children: [
8254
+ /* @__PURE__ */ jsx22(
8255
+ "h3",
8256
+ {
8257
+ style: {
8258
+ margin: 0,
8259
+ fontSize: `${fontSize.title}px`,
8260
+ fontWeight: 600,
8261
+ fontFamily: "Arial, sans-serif",
8262
+ letterSpacing: "0.5px",
8263
+ textTransform: "uppercase",
8264
+ color: textColor
8265
+ },
8266
+ children: titleText
8267
+ }
8268
+ ),
8269
+ binding.display?.subtitle && /* @__PURE__ */ jsx22(
8270
+ "div",
8271
+ {
8272
+ style: {
8273
+ fontSize: `${fontSize.description}px`,
8274
+ color: mutedTextColor,
8275
+ marginTop: "4px",
8276
+ fontFamily: "Arial, sans-serif"
8277
+ },
8278
+ children: binding.display.subtitle
8279
+ }
8280
+ )
8281
+ ] }),
8282
+ /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: "8px" }, children: [
8283
+ showControlsButton && onOpenControls && /* @__PURE__ */ jsx22(
8284
+ "button",
8285
+ {
8286
+ onClick: onOpenControls,
8287
+ title: "Open Controls",
8288
+ style: {
8289
+ minWidth: `${touchSize * 1.2}px`,
8290
+ height: touchSize * 0.7,
8291
+ backgroundColor: surfaceColor,
8292
+ color: textColor,
8293
+ border: `2px solid ${borderColor}`,
8294
+ borderRadius: "2px",
8295
+ fontSize: `${fontSize.button}px`,
8296
+ cursor: "pointer",
8297
+ fontWeight: 600,
8298
+ fontFamily: "Arial, sans-serif",
8299
+ padding: "0 8px",
8300
+ textTransform: "uppercase",
8301
+ letterSpacing: "0.5px"
8302
+ },
8303
+ children: "CTRL"
8304
+ }
8305
+ ),
8306
+ /* @__PURE__ */ jsx22(
8307
+ "button",
8308
+ {
8309
+ onClick: onClose,
8310
+ style: {
8311
+ width: touchSize * 0.7,
8312
+ height: touchSize * 0.7,
8313
+ backgroundColor: surfaceColor,
8314
+ color: textColor,
8315
+ border: `2px solid ${borderColor}`,
8316
+ borderRadius: "2px",
8317
+ fontSize: "18px",
8318
+ cursor: "pointer",
8319
+ fontWeight: "bold",
8320
+ fontFamily: "Arial, sans-serif"
8321
+ },
8322
+ children: "\xD7"
8323
+ }
8324
+ )
8325
+ ] })
8326
+ ]
8327
+ }
8328
+ ),
8329
+ binding.state.isDirty && /* @__PURE__ */ jsx22(
8330
+ "div",
8331
+ {
8332
+ style: {
8333
+ padding: spacing.itemPadding,
8334
+ backgroundColor: "#fff3cd",
8335
+ border: "1px solid #ffc107",
8336
+ borderRadius: "4px",
8337
+ marginBottom: spacing.marginBottom,
8338
+ fontSize: `${fontSize.description}px`,
8339
+ fontFamily: "Arial, sans-serif"
8340
+ },
8341
+ children: "Unsaved changes"
8342
+ }
8343
+ ),
8344
+ /* @__PURE__ */ jsx22("div", { style: { flex: 1, overflowY: "auto", marginBottom: spacing.marginBottom }, children: sortedSections.length > 0 ? sortedSections.map(renderSection) : (
8345
+ /* Render unsectioned parameters */
8346
+ /* @__PURE__ */ jsx22("div", { children: parametersBySection._unsectioned?.map(renderParameter) })
8347
+ ) }),
8348
+ /* @__PURE__ */ jsxs18(
8349
+ "div",
8350
+ {
8351
+ style: {
8352
+ display: "flex",
8353
+ gap: spacing.gap,
8354
+ paddingTop: "12px",
8355
+ borderTop: `2px solid ${borderColor}`,
8356
+ flexWrap: "wrap"
8357
+ },
8358
+ children: [
8359
+ showApply && /* @__PURE__ */ jsx22(
8360
+ "button",
8361
+ {
8362
+ onClick: handleApply,
8363
+ disabled: binding.state.isSaving,
8364
+ style: {
8365
+ flex: 1,
8366
+ minWidth: "80px",
8367
+ height: `${touchSize}px`,
8368
+ backgroundColor: accentColor,
8369
+ color: "#000000",
8370
+ border: `2px solid ${borderColor}`,
8371
+ borderRadius: "2px",
8372
+ fontSize: `${fontSize.button}px`,
8373
+ fontWeight: 600,
8374
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8375
+ fontFamily: "Arial, sans-serif",
8376
+ textTransform: "uppercase",
8377
+ letterSpacing: "0.5px"
8378
+ },
8379
+ children: binding.state.isSaving ? "Applying..." : "Apply"
8380
+ }
8381
+ ),
8382
+ showSave && /* @__PURE__ */ jsx22(
8383
+ "button",
8384
+ {
8385
+ onClick: handleSave,
8386
+ disabled: binding.state.isSaving,
8387
+ style: {
8388
+ flex: 1,
8389
+ minWidth: "80px",
8390
+ height: `${touchSize}px`,
8391
+ backgroundColor: successColor,
8392
+ color: "#ffffff",
8393
+ border: `2px solid ${borderColor}`,
8394
+ borderRadius: "2px",
8395
+ fontSize: `${fontSize.button}px`,
8396
+ fontWeight: 600,
8397
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8398
+ fontFamily: "Arial, sans-serif",
8399
+ textTransform: "uppercase",
8400
+ letterSpacing: "0.5px"
8401
+ },
8402
+ children: binding.state.isSaving ? "Saving..." : "Save"
8403
+ }
8404
+ ),
8405
+ showReset && /* @__PURE__ */ jsx22(
8406
+ "button",
8407
+ {
8408
+ onClick: handleReset,
8409
+ disabled: binding.state.isSaving,
8410
+ style: {
8411
+ flex: 1,
8412
+ minWidth: "80px",
8413
+ height: `${touchSize}px`,
8414
+ backgroundColor: surfaceColor,
8415
+ color: textColor,
8416
+ border: `2px solid ${borderColor}`,
8417
+ borderRadius: "2px",
8418
+ fontSize: `${fontSize.button}px`,
8419
+ fontWeight: 600,
8420
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8421
+ fontFamily: "Arial, sans-serif",
8422
+ textTransform: "uppercase",
8423
+ letterSpacing: "0.5px"
8424
+ },
8425
+ children: "Reset"
8426
+ }
8427
+ ),
8428
+ showCancel && /* @__PURE__ */ jsx22(
8429
+ "button",
8430
+ {
8431
+ onClick: handleCancel,
8432
+ disabled: binding.state.isSaving,
8433
+ style: {
8434
+ flex: 1,
8435
+ minWidth: "80px",
8436
+ height: `${touchSize}px`,
8437
+ backgroundColor: surfaceColor,
8438
+ color: textColor,
8439
+ border: `2px solid ${borderColor}`,
8440
+ borderRadius: "2px",
8441
+ fontSize: `${fontSize.button}px`,
8442
+ fontWeight: 600,
8443
+ cursor: binding.state.isSaving ? "not-allowed" : "pointer",
8444
+ fontFamily: "Arial, sans-serif",
8445
+ textTransform: "uppercase",
8446
+ letterSpacing: "0.5px"
8447
+ },
8448
+ children: "Cancel"
8449
+ }
8450
+ )
8451
+ ]
8452
+ }
8453
+ )
8454
+ ] }),
8455
+ binding.state.lastSaveResult && showToast && /* @__PURE__ */ jsx22(
8456
+ "div",
8457
+ {
8458
+ style: {
8459
+ position: "fixed",
8460
+ bottom: "20px",
8461
+ ...align === "right" ? { left: "20px" } : { right: "20px" },
8462
+ backgroundColor: binding.state.lastSaveResult.success ? successColor : errorColor,
8463
+ color: "#ffffff",
8464
+ padding: "12px 20px",
8465
+ borderRadius: "4px",
8466
+ boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
8467
+ zIndex: 1e4,
8468
+ fontSize: `${fontSize.value}px`,
8469
+ fontFamily: "Arial, sans-serif",
8470
+ fontWeight: 600
8471
+ },
8472
+ children: binding.state.lastSaveResult.message || (binding.state.lastSaveResult.success ? "Success" : "Error")
8473
+ }
8474
+ )
8475
+ ] });
8476
+ }
8477
+
7500
8478
  // src/diagram/hooks/useSimulation.ts
7501
- import { useEffect as useEffect12 } from "react";
8479
+ import { useEffect as useEffect13 } from "react";
7502
8480
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
7503
8481
  const {
7504
8482
  interval = 2e3,
@@ -7509,7 +8487,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
7509
8487
  historyLength = 10,
7510
8488
  generateValue
7511
8489
  } = options;
7512
- useEffect12(() => {
8490
+ useEffect13(() => {
7513
8491
  if (!enabled) return;
7514
8492
  const intervalId = setInterval(() => {
7515
8493
  const updates = [];
@@ -7566,6 +8544,7 @@ export {
7566
8544
  ControlPanel,
7567
8545
  DEFAULT_THRESHOLDS,
7568
8546
  DashboardCard,
8547
+ DeviceConfigPanel,
7569
8548
  DeviceControlPanel,
7570
8549
  DisplayModeToggle,
7571
8550
  EquipmentIndicator,
@@ -7598,6 +8577,7 @@ export {
7598
8577
  ZoomButton,
7599
8578
  ZoomControls,
7600
8579
  calculateThresholdStatus,
8580
+ createConfigBinding,
7601
8581
  createControlBinding,
7602
8582
  exampleDiagram,
7603
8583
  exportDiagram,
@@ -7611,6 +8591,7 @@ export {
7611
8591
  overlayStyles,
7612
8592
  registerSymbol,
7613
8593
  registerSymbols,
8594
+ useDeviceConfigs,
7614
8595
  useDeviceControls,
7615
8596
  useDrag,
7616
8597
  useKeyboardShortcuts,