@customviews-js/customviews 1.4.0 → 1.4.1-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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @customviews-js/customviews v1.4.0
2
+ * @customviews-js/customviews v1.4.1-beta.0
3
3
  * (c) 2025 Chan Ger Teck
4
4
  * Released under the MIT License.
5
5
  */
@@ -169,8 +169,12 @@
169
169
  // Create a compact representation
170
170
  const compact = {};
171
171
  // Add toggles if present and non-empty
172
- if (state.toggles && state.toggles.length > 0) {
173
- compact.t = state.toggles;
172
+ if (state.shownToggles && state.shownToggles.length > 0) {
173
+ compact.t = state.shownToggles;
174
+ }
175
+ // Add peek toggles if present and non-empty
176
+ if (state.peekToggles && state.peekToggles.length > 0) {
177
+ compact.p = state.peekToggles;
174
178
  }
175
179
  // Add tab groups if present
176
180
  if (state.tabs && Object.keys(state.tabs).length > 0) {
@@ -231,7 +235,8 @@
231
235
  // Reconstruct State from compact format
232
236
  // Reconstruct Toggles
233
237
  const state = {
234
- toggles: Array.isArray(compact.t) ? compact.t : []
238
+ shownToggles: Array.isArray(compact.t) ? compact.t : [],
239
+ peekToggles: Array.isArray(compact.p) ? compact.p : []
235
240
  };
236
241
  // Reconstruct Tabs
237
242
  if (Array.isArray(compact.g)) {
@@ -444,6 +449,12 @@
444
449
  <path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"/>
445
450
  </svg>`;
446
451
  }
452
+ function getChevronDownIcon() {
453
+ return `<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>`;
454
+ }
455
+ function getChevronUpIcon() {
456
+ return `<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"></polyline></svg>`;
457
+ }
447
458
 
448
459
  // Constants for selectors
449
460
  const TABGROUP_SELECTOR$1 = 'cv-tabgroup';
@@ -1014,20 +1025,33 @@
1014
1025
  * ToggleManager handles discovery, visibility, and asset rendering for toggle elements
1015
1026
  */
1016
1027
  class ToggleManager {
1028
+ /**
1029
+ * Track locally expanded elements (that were in peek mode but user expanded them)
1030
+ */
1031
+ static expandedPeekElements = new WeakSet();
1017
1032
  /**
1018
1033
  * Apply toggle visibility to a given list of toggle elements
1019
1034
  */
1020
- static applyToggles(elements, activeToggles) {
1021
- elements.forEach(el => {
1035
+ static applyTogglesVisibility(allToggleElements, activeToggles, peekToggles = []) {
1036
+ allToggleElements.forEach(el => {
1022
1037
  const categories = this.getToggleCategories(el);
1023
1038
  const shouldShow = categories.some(cat => activeToggles.includes(cat));
1024
- this.applyToggleVisibility(el, shouldShow);
1039
+ const shouldPeek = !shouldShow && categories.some(cat => peekToggles.includes(cat));
1040
+ if (!shouldPeek) {
1041
+ this.expandedPeekElements.delete(el);
1042
+ }
1043
+ // If locally expanded, treat as shown (override peek)
1044
+ // Note: If neither show nor peek is active (i.e. hidden), local expansion is ignored/cleared effectively
1045
+ this.applyToggleVisibility(el, shouldShow || (shouldPeek && this.expandedPeekElements.has(el)), shouldPeek && !this.expandedPeekElements.has(el));
1025
1046
  });
1026
1047
  }
1027
1048
  /**
1028
1049
  * Render assets into a given list of toggle elements that are currently visible
1050
+ * Toggles that have a toggleId and are currently visible will have their assets rendered (if any)
1029
1051
  */
1030
- static renderAssets(elements, activeToggles, assetsManager) {
1052
+ static renderToggleAssets(elements, activeToggles, assetsManager) {
1053
+ // TO DO: (gerteck) Enable for peek toggles as well
1054
+ // Also, rework the rendering logic again to make it more user friendly.
1031
1055
  elements.forEach(el => {
1032
1056
  const categories = this.getToggleCategories(el);
1033
1057
  const toggleId = this.getToggleId(el);
@@ -1040,6 +1064,7 @@
1040
1064
  }
1041
1065
  /**
1042
1066
  * Get toggle categories from an element (supports both data attributes and cv-toggle elements)
1067
+ * Note: a toggle can have multiple categories.
1043
1068
  */
1044
1069
  static getToggleCategories(el) {
1045
1070
  if (el.tagName.toLowerCase() === 'cv-toggle') {
@@ -1060,14 +1085,101 @@
1060
1085
  /**
1061
1086
  * Apply simple class-based visibility to a toggle element
1062
1087
  */
1063
- static applyToggleVisibility(el, visible) {
1088
+ static applyToggleVisibility(toggleElement, visible, peek = false) {
1089
+ const isLocallyExpanded = this.expandedPeekElements.has(toggleElement);
1064
1090
  if (visible) {
1065
- el.classList.remove('cv-hidden');
1066
- el.classList.add('cv-visible');
1091
+ toggleElement.classList.remove('cv-hidden', 'cv-peek');
1092
+ toggleElement.classList.add('cv-visible');
1093
+ // Show collapse button ONLY if locally expanded (meaning we are actually in peek mode but expanded).
1094
+ // If globally visible (because of 'Show' state), isLocallyExpanded should have been cleared by applyTogglesVisibility,
1095
+ // so this will be false, and button will be removed.
1096
+ this.manageExpandButton(toggleElement, false, isLocallyExpanded);
1097
+ }
1098
+ else if (peek) {
1099
+ toggleElement.classList.remove('cv-hidden', 'cv-visible');
1100
+ toggleElement.classList.add('cv-peek');
1101
+ // Show/create expand button if peeked
1102
+ this.manageExpandButton(toggleElement, true, false);
1067
1103
  }
1068
1104
  else {
1069
- el.classList.add('cv-hidden');
1070
- el.classList.remove('cv-visible');
1105
+ toggleElement.classList.add('cv-hidden');
1106
+ toggleElement.classList.remove('cv-visible', 'cv-peek');
1107
+ // Ensure button is gone/hidden
1108
+ this.manageExpandButton(toggleElement, false, false);
1109
+ }
1110
+ }
1111
+ /**
1112
+ * Manage the presence of the inline Expand/Collapse button using a wrapper approach
1113
+ */
1114
+ static manageExpandButton(toggleElement, showExpand, showCollapse = false) {
1115
+ // 1. Ensure wrapper exists
1116
+ let wrapper = toggleElement.parentElement;
1117
+ if (!wrapper || !wrapper.classList.contains('cv-wrapper')) {
1118
+ wrapper = document.createElement('div');
1119
+ wrapper.className = 'cv-wrapper';
1120
+ toggleElement.parentNode?.insertBefore(wrapper, toggleElement);
1121
+ wrapper.appendChild(toggleElement);
1122
+ }
1123
+ const btn = wrapper.querySelector('.cv-expand-btn');
1124
+ // 2. Handle "No Button" case (neither expand nor collapse)
1125
+ if (!showExpand && !showCollapse) {
1126
+ if (btn)
1127
+ btn.style.display = 'none';
1128
+ // If content is visible globally (not hidden), ensure wrapper has 'cv-expanded'
1129
+ // to hide the peek fade effect (since fade is for peek state only).
1130
+ if (!toggleElement.classList.contains('cv-hidden')) {
1131
+ wrapper.classList.add('cv-expanded');
1132
+ }
1133
+ else {
1134
+ wrapper.classList.remove('cv-expanded');
1135
+ }
1136
+ return;
1137
+ }
1138
+ // 3. Handle Button Needed (Expand or Collapse)
1139
+ const action = showExpand ? 'expand' : 'collapse';
1140
+ // Update Wrapper Class Logic
1141
+ // If showExpand (Peek state) -> remove cv-expanded (show fade)
1142
+ // If showCollapse (Expanded peek) -> add cv-expanded (hide fade)
1143
+ if (showExpand) {
1144
+ wrapper.classList.remove('cv-expanded');
1145
+ }
1146
+ else {
1147
+ if (!wrapper.classList.contains('cv-expanded'))
1148
+ wrapper.classList.add('cv-expanded');
1149
+ }
1150
+ // Check if existing button matches desired state
1151
+ const currentAction = btn?.getAttribute('data-action');
1152
+ if (btn && currentAction === action) {
1153
+ btn.style.display = 'flex';
1154
+ return;
1155
+ }
1156
+ // 4. Create New Button (if missing or state changed)
1157
+ const iconSvg = showExpand ? getChevronDownIcon() : getChevronUpIcon();
1158
+ const newBtn = document.createElement('button');
1159
+ newBtn.className = 'cv-expand-btn';
1160
+ newBtn.innerHTML = iconSvg;
1161
+ newBtn.setAttribute('aria-label', showExpand ? 'Expand content' : 'Collapse content');
1162
+ newBtn.setAttribute('data-action', action); // Track state
1163
+ newBtn.style.display = 'flex';
1164
+ newBtn.addEventListener('click', (e) => {
1165
+ e.stopPropagation();
1166
+ // Logic: Toggle expansion state
1167
+ if (showExpand) {
1168
+ wrapper.classList.add('cv-expanded');
1169
+ this.expandedPeekElements.add(toggleElement);
1170
+ this.applyToggleVisibility(toggleElement, true, false);
1171
+ }
1172
+ else {
1173
+ wrapper.classList.remove('cv-expanded');
1174
+ this.expandedPeekElements.delete(toggleElement);
1175
+ this.applyToggleVisibility(toggleElement, false, true);
1176
+ }
1177
+ });
1178
+ if (btn) {
1179
+ btn.replaceWith(newBtn);
1180
+ }
1181
+ else {
1182
+ wrapper.appendChild(newBtn);
1071
1183
  }
1072
1184
  }
1073
1185
  /**
@@ -1075,15 +1187,15 @@
1075
1187
  * This includes applying visibility and rendering assets.
1076
1188
  */
1077
1189
  static initializeToggles(root, activeToggles, assetsManager) {
1078
- const elements = [];
1190
+ const allToggleElements = [];
1079
1191
  if (root.matches('[data-cv-toggle], [data-customviews-toggle], cv-toggle')) {
1080
- elements.push(root);
1192
+ allToggleElements.push(root);
1081
1193
  }
1082
- root.querySelectorAll('[data-cv-toggle], [data-customviews-toggle], cv-toggle').forEach(el => elements.push(el));
1083
- if (elements.length === 0)
1194
+ root.querySelectorAll('[data-cv-toggle], [data-customviews-toggle], cv-toggle').forEach(el => allToggleElements.push(el));
1195
+ if (allToggleElements.length === 0)
1084
1196
  return;
1085
- this.applyToggles(elements, activeToggles);
1086
- this.renderAssets(elements, activeToggles, assetsManager);
1197
+ this.applyTogglesVisibility(allToggleElements, activeToggles);
1198
+ this.renderToggleAssets(allToggleElements, activeToggles, assetsManager);
1087
1199
  }
1088
1200
  }
1089
1201
 
@@ -1191,17 +1303,16 @@
1191
1303
  const TOGGLE_STYLES = `
1192
1304
  /* Core toggle visibility transitions */
1193
1305
  [data-cv-toggle], [data-customviews-toggle], cv-toggle {
1194
- transition: opacity 150ms ease,
1195
- transform 150ms ease,
1196
- max-height 200ms ease,
1197
- margin 150ms ease;
1198
- will-change: opacity, transform, max-height, margin;
1306
+ display: block;
1307
+ overflow: hidden;
1308
+ /* Removed transitions for instant toggling */
1199
1309
  }
1200
1310
 
1311
+ /* Open State */
1201
1312
  .cv-visible {
1202
1313
  opacity: 1 !important;
1203
1314
  transform: translateY(0) !important;
1204
- max-height: var(--cv-max-height, 9999px) !important;
1315
+ max-height: none !important;
1205
1316
  }
1206
1317
 
1207
1318
  .cv-hidden {
@@ -1217,6 +1328,61 @@
1217
1328
  margin-bottom: 0 !important;
1218
1329
  overflow: hidden !important;
1219
1330
  }
1331
+
1332
+ /* Close/Peek State */
1333
+ .cv-peek {
1334
+ display: block !important;
1335
+ max-height: 70px !important;
1336
+ overflow: hidden !important;
1337
+ opacity: 1 !important;
1338
+ transform: translateY(0) !important;
1339
+ mask-image: linear-gradient(to bottom, black 50%, transparent 100%);
1340
+ -webkit-mask-image: linear-gradient(to bottom, black 50%, transparent 100%);
1341
+ }
1342
+
1343
+ .cv-wrapper {
1344
+ position: relative;
1345
+ width: 100%;
1346
+ display: block;
1347
+ margin-bottom: 24px; /* Space for the button */
1348
+ }
1349
+
1350
+ .cv-expand-btn {
1351
+ position: absolute;
1352
+ bottom: -28px; /* Mostly outside, slight overlap */
1353
+ left: 50%;
1354
+ transform: translateX(-50%);
1355
+ display: flex;
1356
+ background: transparent;
1357
+ border: none;
1358
+ border-radius: 50%;
1359
+ padding: 4px;
1360
+ width: 32px;
1361
+ height: 32px;
1362
+ cursor: pointer;
1363
+ z-index: 100;
1364
+ align-items: center;
1365
+ justify-content: center;
1366
+ color: #888;
1367
+ transition: all 0.2s ease;
1368
+ box-shadow: none;
1369
+ }
1370
+
1371
+ .cv-expand-btn:hover {
1372
+ background: rgba(0, 0, 0, 0.05);
1373
+ color: #000;
1374
+ transform: translateX(-50%) scale(1.1);
1375
+ }
1376
+
1377
+ .cv-expand-btn svg {
1378
+ display: block;
1379
+ opacity: 0.6;
1380
+ }
1381
+
1382
+ .cv-expand-btn:hover svg {
1383
+ opacity: 1;
1384
+ }
1385
+
1220
1386
  `;
1221
1387
 
1222
1388
  /**
@@ -2606,7 +2772,7 @@ ${TAB_STYLES}
2606
2772
  });
2607
2773
  }
2608
2774
  const computedState = {
2609
- toggles: this.config.toggles?.map(t => t.id) || [],
2775
+ shownToggles: this.config.toggles?.map(t => t.id) || [],
2610
2776
  tabs
2611
2777
  };
2612
2778
  return computedState;
@@ -2678,9 +2844,9 @@ ${TAB_STYLES}
2678
2844
  const initialTop = anchorElement.getBoundingClientRect().top;
2679
2845
  const currentTabs = this.getCurrentActiveTabs();
2680
2846
  currentTabs[groupId] = tabId;
2681
- const currentToggles = this.getCurrentActiveToggles();
2847
+ const currentState = this.getCurrentState();
2682
2848
  const newState = {
2683
- toggles: currentToggles,
2849
+ ...currentState,
2684
2850
  tabs: currentTabs,
2685
2851
  };
2686
2852
  // 2. Apply state with scroll anchor information
@@ -2736,7 +2902,8 @@ ${TAB_STYLES}
2736
2902
  // 1. URL State
2737
2903
  const urlState = URLStateManager.parseURL();
2738
2904
  if (urlState) {
2739
- this.applyState(urlState);
2905
+ // Apply URL state temporarily (do not persist until interaction)
2906
+ this.applyState(urlState, { persist: false });
2740
2907
  return;
2741
2908
  }
2742
2909
  // 2. Persisted State
@@ -2750,9 +2917,10 @@ ${TAB_STYLES}
2750
2917
  }
2751
2918
  /**
2752
2919
  * Apply a custom state, saves to localStorage and updates the URL
2753
- * Add 'source' in options to indicate the origin of the state change
2920
+ * 'source' in options indicates the origin of the state change
2754
2921
  * (e.g., 'widget' to trigger scroll behavior)
2755
- * Add scrollAnchor in options to maintain scroll position of a specific element
2922
+ * 'scrollAnchor' in options indicates the element to maintain scroll position of
2923
+ * 'persist' (default true) to control whether to save to localStorage
2756
2924
  */
2757
2925
  applyState(state, options) {
2758
2926
  // console.log(`[Core] applyState called with source: ${options?.source}`, state);
@@ -2762,7 +2930,10 @@ ${TAB_STYLES}
2762
2930
  }
2763
2931
  const snapshot = this.cloneState(state);
2764
2932
  this.renderState(snapshot);
2765
- this.persistenceManager.persistState(snapshot);
2933
+ // Only persist if explicitly requested (default true)
2934
+ if (options?.persist !== false) {
2935
+ this.persistenceManager.persistState(snapshot);
2936
+ }
2766
2937
  if (this.showUrlEnabled) {
2767
2938
  URLStateManager.updateURL(snapshot);
2768
2939
  }
@@ -2789,14 +2960,13 @@ ${TAB_STYLES}
2789
2960
  renderState(state) {
2790
2961
  this.observer?.disconnect();
2791
2962
  this.lastAppliedState = this.cloneState(state);
2792
- const toggles = state?.toggles || [];
2963
+ const toggles = state?.shownToggles || [];
2793
2964
  const finalToggles = this.visibilityManager.filterVisibleToggles(toggles);
2794
- const toggleElements = Array.from(this.componentRegistry.toggles);
2965
+ const allToggleElements = Array.from(this.componentRegistry.toggles);
2795
2966
  const tabGroupElements = Array.from(this.componentRegistry.tabGroups);
2796
- // Apply toggle visibility
2797
- ToggleManager.applyToggles(toggleElements, finalToggles);
2967
+ ToggleManager.applyTogglesVisibility(allToggleElements, finalToggles, state.peekToggles);
2798
2968
  // Render assets into toggles
2799
- ToggleManager.renderAssets(toggleElements, finalToggles, this.assetsManager);
2969
+ ToggleManager.renderToggleAssets(allToggleElements, finalToggles, this.assetsManager);
2800
2970
  // Apply tab selections
2801
2971
  TabManager.applyTabSelections(tabGroupElements, state.tabs || {}, this.config.tabGroups);
2802
2972
  // Update nav active states (without rebuilding)
@@ -2827,16 +2997,16 @@ ${TAB_STYLES}
2827
2997
  URLStateManager.clearURL();
2828
2998
  }
2829
2999
  /**
2830
- * Get the currently active toggles regardless of whether they come from custom state or default configuration
3000
+ * Get the full current state including active toggles, peek toggles, and tabs
2831
3001
  */
2832
- getCurrentActiveToggles() {
3002
+ getCurrentState() {
2833
3003
  if (this.lastAppliedState) {
2834
- return this.lastAppliedState.toggles || [];
3004
+ return this.cloneState(this.lastAppliedState);
2835
3005
  }
2836
3006
  if (this.config) {
2837
- return this.getComputedDefaultState().toggles || [];
3007
+ return this.cloneState(this.getComputedDefaultState());
2838
3008
  }
2839
- return [];
3009
+ return {};
2840
3010
  }
2841
3011
  /**
2842
3012
  * Clear all persistence and reset to default
@@ -3532,10 +3702,6 @@ ${TAB_STYLES}
3532
3702
  color: rgba(255, 255, 255, 0.6);
3533
3703
  }
3534
3704
 
3535
- .cv-widget-theme-dark .cv-toggle-slider {
3536
- background: rgba(255, 255, 255, 0.2);
3537
- }
3538
-
3539
3705
  .cv-widget-theme-dark .cv-tab-group-description {
3540
3706
  color: rgba(255, 255, 255, 0.8);
3541
3707
  }
@@ -3656,40 +3822,32 @@ ${TAB_STYLES}
3656
3822
  }
3657
3823
 
3658
3824
  .cv-toggle-input {
3825
+ /* Only hide if it is part of a custom slider toggle */
3826
+ }
3827
+ .cv-toggle-label .cv-toggle-input {
3659
3828
  opacity: 0;
3660
3829
  width: 0;
3661
3830
  height: 0;
3662
3831
  }
3663
3832
 
3664
- .cv-toggle-slider {
3665
- position: absolute;
3666
- top: 0;
3667
- left: 0;
3668
- right: 0;
3669
- bottom: 0;
3670
- background: rgba(0, 0, 0, 0.2);
3671
- border-radius: 9999px;
3672
- transition: background-color 0.2s ease;
3673
- }
3674
-
3675
- .cv-toggle-slider:before {
3676
- position: absolute;
3677
- content: "";
3678
- height: 1rem;
3679
- width: 1rem;
3680
- left: 0.25rem;
3681
- bottom: 0.25rem;
3682
- background: white;
3683
- border-radius: 50%;
3684
- transition: transform 0.2s ease;
3833
+ .cv-toggle-radios {
3834
+ display: flex;
3835
+ gap: 8px;
3685
3836
  }
3686
3837
 
3687
- .cv-toggle-input:checked + .cv-toggle-slider {
3688
- background: #3e84f4;
3838
+ .cv-radio-label {
3839
+ display: flex;
3840
+ align-items: center;
3841
+ gap: 4px;
3842
+ font-size: 0.85rem;
3843
+ cursor: pointer;
3689
3844
  }
3690
3845
 
3691
- .cv-toggle-input:checked + .cv-toggle-slider:before {
3692
- transform: translateX(1.25rem);
3846
+ .cv-radio-label input {
3847
+ margin: 0;
3848
+ opacity: 1;
3849
+ width: auto;
3850
+ height: auto;
3693
3851
  }
3694
3852
 
3695
3853
  /* Dark theme toggle switch styles */
@@ -4506,10 +4664,20 @@ ${TAB_STYLES}
4506
4664
  <div>
4507
4665
  <p class="cv-toggle-title">${toggle.label || toggle.id}</p>
4508
4666
  </div>
4509
- <label class="cv-toggle-label">
4510
- <input class="cv-toggle-input" type="checkbox" data-toggle="${toggle.id}"/>
4511
- <span class="cv-toggle-slider"></span>
4512
- </label>
4667
+ <div class="cv-toggle-radios">
4668
+ <label class="cv-radio-label" title="Hide">
4669
+ <input class="cv-toggle-input" type="radio" name="cv-toggle-${toggle.id}" value="hide" data-toggle="${toggle.id}"/>
4670
+ <span>Hide</span>
4671
+ </label>
4672
+ <label class="cv-radio-label" title="Peek">
4673
+ <input class="cv-toggle-input" type="radio" name="cv-toggle-${toggle.id}" value="peek" data-toggle="${toggle.id}"/>
4674
+ <span>Peek</span>
4675
+ </label>
4676
+ <label class="cv-radio-label" title="Show">
4677
+ <input class="cv-toggle-input" type="radio" name="cv-toggle-${toggle.id}" value="show" data-toggle="${toggle.id}"/>
4678
+ <span>Show</span>
4679
+ </label>
4680
+ </div>
4513
4681
  </div>
4514
4682
  </div>
4515
4683
  `).join('');
@@ -4710,10 +4878,11 @@ ${TAB_STYLES}
4710
4878
  if (groupId && tabId) {
4711
4879
  const currentTabs = this.core.getCurrentActiveTabs();
4712
4880
  currentTabs[groupId] = tabId;
4713
- const currentToggles = this.core.getCurrentActiveToggles();
4881
+ const currentState = this.core.getCurrentState();
4714
4882
  const newState = {
4715
- toggles: currentToggles,
4716
- tabs: currentTabs
4883
+ shownToggles: currentState.shownToggles || [],
4884
+ peekToggles: currentState.peekToggles || [], // Preserve peek state, fallback to empty array
4885
+ tabs: currentTabs,
4717
4886
  };
4718
4887
  this.core.applyState(newState, { source: 'widget' });
4719
4888
  }
@@ -4811,25 +4980,33 @@ ${TAB_STYLES}
4811
4980
  }
4812
4981
  // Collect toggle values
4813
4982
  const toggles = [];
4814
- const toggleInputs = this.stateModal.querySelectorAll('.cv-toggle-input');
4815
- toggleInputs.forEach(toggleInput => {
4816
- const toggle = toggleInput.dataset.toggle;
4817
- if (toggle && toggleInput.checked) {
4818
- toggles.push(toggle);
4819
- }
4820
- });
4821
- // Collect tab selections
4822
- const tabGroupSelects = this.stateModal.querySelectorAll('.cv-tabgroup-select');
4823
- const tabs = {};
4824
- tabGroupSelects.forEach(select => {
4825
- const groupId = select.dataset.groupId;
4826
- if (groupId) {
4827
- tabs[groupId] = select.value;
4983
+ const peekToggles = [];
4984
+ // Get all radio inputs
4985
+ const radios = this.stateModal.querySelectorAll('input[type="radio"]:checked');
4986
+ radios.forEach(radio => {
4987
+ const input = radio;
4988
+ const toggleId = input.getAttribute('data-toggle');
4989
+ if (toggleId) {
4990
+ if (input.value === 'show') {
4991
+ toggles.push(toggleId);
4992
+ }
4993
+ else if (input.value === 'peek') {
4994
+ peekToggles.push(toggleId);
4995
+ }
4828
4996
  }
4829
4997
  });
4830
- const result = { toggles };
4831
- if (Object.keys(tabs).length > 0) {
4832
- result.tabs = tabs;
4998
+ const result = { shownToggles: toggles, peekToggles };
4999
+ // Get active tabs from selects
5000
+ const selects = this.stateModal.querySelectorAll('select[data-group-id]');
5001
+ if (selects.length > 0) {
5002
+ result.tabs = {};
5003
+ selects.forEach(select => {
5004
+ const el = select;
5005
+ const groupId = el.getAttribute('data-group-id');
5006
+ if (groupId) {
5007
+ result.tabs[groupId] = el.value;
5008
+ }
5009
+ });
4833
5010
  }
4834
5011
  return result;
4835
5012
  }
@@ -4849,18 +5026,29 @@ ${TAB_STYLES}
4849
5026
  loadCurrentStateIntoForm() {
4850
5027
  if (!this.stateModal)
4851
5028
  return;
4852
- // Get currently active toggles (from custom state or default configuration)
4853
- const activeToggles = this.core.getCurrentActiveToggles();
4854
- // First, uncheck all toggle inputs
5029
+ // We need complete state for both shown and peek toggles
5030
+ const currentState = this.core.getCurrentState();
5031
+ const currentToggles = currentState.shownToggles || [];
5032
+ const currentPeekToggles = currentState.peekToggles || [];
5033
+ // Reset all inputs first (optional, but good for clarity)
4855
5034
  const allToggleInputs = this.stateModal.querySelectorAll('.cv-toggle-input');
4856
- allToggleInputs.forEach(toggleInput => {
4857
- toggleInput.checked = false;
5035
+ // Identify unique toggles present in the modal
5036
+ const uniqueToggles = new Set();
5037
+ allToggleInputs.forEach(input => {
5038
+ if (input.dataset.toggle)
5039
+ uniqueToggles.add(input.dataset.toggle);
4858
5040
  });
4859
- // Then check the ones that should be active
4860
- activeToggles.forEach(toggle => {
4861
- const toggleInput = this.stateModal?.querySelector(`[data-toggle="${toggle}"]`);
4862
- if (toggleInput) {
4863
- toggleInput.checked = true;
5041
+ uniqueToggles.forEach(toggleId => {
5042
+ let valueToSelect = 'hide';
5043
+ if (currentToggles.includes(toggleId)) {
5044
+ valueToSelect = 'show';
5045
+ }
5046
+ else if (currentPeekToggles.includes(toggleId)) {
5047
+ valueToSelect = 'peek';
5048
+ }
5049
+ const input = this.stateModal.querySelector(`input[name="cv-toggle-${toggleId}"][value="${valueToSelect}"]`);
5050
+ if (input) {
5051
+ input.checked = true;
4864
5052
  }
4865
5053
  });
4866
5054
  // Load tab group selections