@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
  */
@@ -165,8 +165,12 @@ class URLStateManager {
165
165
  // Create a compact representation
166
166
  const compact = {};
167
167
  // Add toggles if present and non-empty
168
- if (state.toggles && state.toggles.length > 0) {
169
- compact.t = state.toggles;
168
+ if (state.shownToggles && state.shownToggles.length > 0) {
169
+ compact.t = state.shownToggles;
170
+ }
171
+ // Add peek toggles if present and non-empty
172
+ if (state.peekToggles && state.peekToggles.length > 0) {
173
+ compact.p = state.peekToggles;
170
174
  }
171
175
  // Add tab groups if present
172
176
  if (state.tabs && Object.keys(state.tabs).length > 0) {
@@ -227,7 +231,8 @@ class URLStateManager {
227
231
  // Reconstruct State from compact format
228
232
  // Reconstruct Toggles
229
233
  const state = {
230
- toggles: Array.isArray(compact.t) ? compact.t : []
234
+ shownToggles: Array.isArray(compact.t) ? compact.t : [],
235
+ peekToggles: Array.isArray(compact.p) ? compact.p : []
231
236
  };
232
237
  // Reconstruct Tabs
233
238
  if (Array.isArray(compact.g)) {
@@ -440,6 +445,12 @@ function getGitHubIcon() {
440
445
  <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"/>
441
446
  </svg>`;
442
447
  }
448
+ function getChevronDownIcon() {
449
+ 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>`;
450
+ }
451
+ function getChevronUpIcon() {
452
+ 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>`;
453
+ }
443
454
 
444
455
  // Constants for selectors
445
456
  const TABGROUP_SELECTOR$1 = 'cv-tabgroup';
@@ -1010,20 +1021,33 @@ function renderAssetInto(el, assetId, assetsManager) {
1010
1021
  * ToggleManager handles discovery, visibility, and asset rendering for toggle elements
1011
1022
  */
1012
1023
  class ToggleManager {
1024
+ /**
1025
+ * Track locally expanded elements (that were in peek mode but user expanded them)
1026
+ */
1027
+ static expandedPeekElements = new WeakSet();
1013
1028
  /**
1014
1029
  * Apply toggle visibility to a given list of toggle elements
1015
1030
  */
1016
- static applyToggles(elements, activeToggles) {
1017
- elements.forEach(el => {
1031
+ static applyTogglesVisibility(allToggleElements, activeToggles, peekToggles = []) {
1032
+ allToggleElements.forEach(el => {
1018
1033
  const categories = this.getToggleCategories(el);
1019
1034
  const shouldShow = categories.some(cat => activeToggles.includes(cat));
1020
- this.applyToggleVisibility(el, shouldShow);
1035
+ const shouldPeek = !shouldShow && categories.some(cat => peekToggles.includes(cat));
1036
+ if (!shouldPeek) {
1037
+ this.expandedPeekElements.delete(el);
1038
+ }
1039
+ // If locally expanded, treat as shown (override peek)
1040
+ // Note: If neither show nor peek is active (i.e. hidden), local expansion is ignored/cleared effectively
1041
+ this.applyToggleVisibility(el, shouldShow || (shouldPeek && this.expandedPeekElements.has(el)), shouldPeek && !this.expandedPeekElements.has(el));
1021
1042
  });
1022
1043
  }
1023
1044
  /**
1024
1045
  * Render assets into a given list of toggle elements that are currently visible
1046
+ * Toggles that have a toggleId and are currently visible will have their assets rendered (if any)
1025
1047
  */
1026
- static renderAssets(elements, activeToggles, assetsManager) {
1048
+ static renderToggleAssets(elements, activeToggles, assetsManager) {
1049
+ // TO DO: (gerteck) Enable for peek toggles as well
1050
+ // Also, rework the rendering logic again to make it more user friendly.
1027
1051
  elements.forEach(el => {
1028
1052
  const categories = this.getToggleCategories(el);
1029
1053
  const toggleId = this.getToggleId(el);
@@ -1036,6 +1060,7 @@ class ToggleManager {
1036
1060
  }
1037
1061
  /**
1038
1062
  * Get toggle categories from an element (supports both data attributes and cv-toggle elements)
1063
+ * Note: a toggle can have multiple categories.
1039
1064
  */
1040
1065
  static getToggleCategories(el) {
1041
1066
  if (el.tagName.toLowerCase() === 'cv-toggle') {
@@ -1056,14 +1081,101 @@ class ToggleManager {
1056
1081
  /**
1057
1082
  * Apply simple class-based visibility to a toggle element
1058
1083
  */
1059
- static applyToggleVisibility(el, visible) {
1084
+ static applyToggleVisibility(toggleElement, visible, peek = false) {
1085
+ const isLocallyExpanded = this.expandedPeekElements.has(toggleElement);
1060
1086
  if (visible) {
1061
- el.classList.remove('cv-hidden');
1062
- el.classList.add('cv-visible');
1087
+ toggleElement.classList.remove('cv-hidden', 'cv-peek');
1088
+ toggleElement.classList.add('cv-visible');
1089
+ // Show collapse button ONLY if locally expanded (meaning we are actually in peek mode but expanded).
1090
+ // If globally visible (because of 'Show' state), isLocallyExpanded should have been cleared by applyTogglesVisibility,
1091
+ // so this will be false, and button will be removed.
1092
+ this.manageExpandButton(toggleElement, false, isLocallyExpanded);
1093
+ }
1094
+ else if (peek) {
1095
+ toggleElement.classList.remove('cv-hidden', 'cv-visible');
1096
+ toggleElement.classList.add('cv-peek');
1097
+ // Show/create expand button if peeked
1098
+ this.manageExpandButton(toggleElement, true, false);
1063
1099
  }
1064
1100
  else {
1065
- el.classList.add('cv-hidden');
1066
- el.classList.remove('cv-visible');
1101
+ toggleElement.classList.add('cv-hidden');
1102
+ toggleElement.classList.remove('cv-visible', 'cv-peek');
1103
+ // Ensure button is gone/hidden
1104
+ this.manageExpandButton(toggleElement, false, false);
1105
+ }
1106
+ }
1107
+ /**
1108
+ * Manage the presence of the inline Expand/Collapse button using a wrapper approach
1109
+ */
1110
+ static manageExpandButton(toggleElement, showExpand, showCollapse = false) {
1111
+ // 1. Ensure wrapper exists
1112
+ let wrapper = toggleElement.parentElement;
1113
+ if (!wrapper || !wrapper.classList.contains('cv-wrapper')) {
1114
+ wrapper = document.createElement('div');
1115
+ wrapper.className = 'cv-wrapper';
1116
+ toggleElement.parentNode?.insertBefore(wrapper, toggleElement);
1117
+ wrapper.appendChild(toggleElement);
1118
+ }
1119
+ const btn = wrapper.querySelector('.cv-expand-btn');
1120
+ // 2. Handle "No Button" case (neither expand nor collapse)
1121
+ if (!showExpand && !showCollapse) {
1122
+ if (btn)
1123
+ btn.style.display = 'none';
1124
+ // If content is visible globally (not hidden), ensure wrapper has 'cv-expanded'
1125
+ // to hide the peek fade effect (since fade is for peek state only).
1126
+ if (!toggleElement.classList.contains('cv-hidden')) {
1127
+ wrapper.classList.add('cv-expanded');
1128
+ }
1129
+ else {
1130
+ wrapper.classList.remove('cv-expanded');
1131
+ }
1132
+ return;
1133
+ }
1134
+ // 3. Handle Button Needed (Expand or Collapse)
1135
+ const action = showExpand ? 'expand' : 'collapse';
1136
+ // Update Wrapper Class Logic
1137
+ // If showExpand (Peek state) -> remove cv-expanded (show fade)
1138
+ // If showCollapse (Expanded peek) -> add cv-expanded (hide fade)
1139
+ if (showExpand) {
1140
+ wrapper.classList.remove('cv-expanded');
1141
+ }
1142
+ else {
1143
+ if (!wrapper.classList.contains('cv-expanded'))
1144
+ wrapper.classList.add('cv-expanded');
1145
+ }
1146
+ // Check if existing button matches desired state
1147
+ const currentAction = btn?.getAttribute('data-action');
1148
+ if (btn && currentAction === action) {
1149
+ btn.style.display = 'flex';
1150
+ return;
1151
+ }
1152
+ // 4. Create New Button (if missing or state changed)
1153
+ const iconSvg = showExpand ? getChevronDownIcon() : getChevronUpIcon();
1154
+ const newBtn = document.createElement('button');
1155
+ newBtn.className = 'cv-expand-btn';
1156
+ newBtn.innerHTML = iconSvg;
1157
+ newBtn.setAttribute('aria-label', showExpand ? 'Expand content' : 'Collapse content');
1158
+ newBtn.setAttribute('data-action', action); // Track state
1159
+ newBtn.style.display = 'flex';
1160
+ newBtn.addEventListener('click', (e) => {
1161
+ e.stopPropagation();
1162
+ // Logic: Toggle expansion state
1163
+ if (showExpand) {
1164
+ wrapper.classList.add('cv-expanded');
1165
+ this.expandedPeekElements.add(toggleElement);
1166
+ this.applyToggleVisibility(toggleElement, true, false);
1167
+ }
1168
+ else {
1169
+ wrapper.classList.remove('cv-expanded');
1170
+ this.expandedPeekElements.delete(toggleElement);
1171
+ this.applyToggleVisibility(toggleElement, false, true);
1172
+ }
1173
+ });
1174
+ if (btn) {
1175
+ btn.replaceWith(newBtn);
1176
+ }
1177
+ else {
1178
+ wrapper.appendChild(newBtn);
1067
1179
  }
1068
1180
  }
1069
1181
  /**
@@ -1071,15 +1183,15 @@ class ToggleManager {
1071
1183
  * This includes applying visibility and rendering assets.
1072
1184
  */
1073
1185
  static initializeToggles(root, activeToggles, assetsManager) {
1074
- const elements = [];
1186
+ const allToggleElements = [];
1075
1187
  if (root.matches('[data-cv-toggle], [data-customviews-toggle], cv-toggle')) {
1076
- elements.push(root);
1188
+ allToggleElements.push(root);
1077
1189
  }
1078
- root.querySelectorAll('[data-cv-toggle], [data-customviews-toggle], cv-toggle').forEach(el => elements.push(el));
1079
- if (elements.length === 0)
1190
+ root.querySelectorAll('[data-cv-toggle], [data-customviews-toggle], cv-toggle').forEach(el => allToggleElements.push(el));
1191
+ if (allToggleElements.length === 0)
1080
1192
  return;
1081
- this.applyToggles(elements, activeToggles);
1082
- this.renderAssets(elements, activeToggles, assetsManager);
1193
+ this.applyTogglesVisibility(allToggleElements, activeToggles);
1194
+ this.renderToggleAssets(allToggleElements, activeToggles, assetsManager);
1083
1195
  }
1084
1196
  }
1085
1197
 
@@ -1187,17 +1299,16 @@ class ScrollManager {
1187
1299
  const TOGGLE_STYLES = `
1188
1300
  /* Core toggle visibility transitions */
1189
1301
  [data-cv-toggle], [data-customviews-toggle], cv-toggle {
1190
- transition: opacity 150ms ease,
1191
- transform 150ms ease,
1192
- max-height 200ms ease,
1193
- margin 150ms ease;
1194
- will-change: opacity, transform, max-height, margin;
1302
+ display: block;
1303
+ overflow: hidden;
1304
+ /* Removed transitions for instant toggling */
1195
1305
  }
1196
1306
 
1307
+ /* Open State */
1197
1308
  .cv-visible {
1198
1309
  opacity: 1 !important;
1199
1310
  transform: translateY(0) !important;
1200
- max-height: var(--cv-max-height, 9999px) !important;
1311
+ max-height: none !important;
1201
1312
  }
1202
1313
 
1203
1314
  .cv-hidden {
@@ -1213,6 +1324,61 @@ const TOGGLE_STYLES = `
1213
1324
  margin-bottom: 0 !important;
1214
1325
  overflow: hidden !important;
1215
1326
  }
1327
+
1328
+ /* Close/Peek State */
1329
+ .cv-peek {
1330
+ display: block !important;
1331
+ max-height: 70px !important;
1332
+ overflow: hidden !important;
1333
+ opacity: 1 !important;
1334
+ transform: translateY(0) !important;
1335
+ mask-image: linear-gradient(to bottom, black 50%, transparent 100%);
1336
+ -webkit-mask-image: linear-gradient(to bottom, black 50%, transparent 100%);
1337
+ }
1338
+
1339
+ .cv-wrapper {
1340
+ position: relative;
1341
+ width: 100%;
1342
+ display: block;
1343
+ margin-bottom: 24px; /* Space for the button */
1344
+ }
1345
+
1346
+ .cv-expand-btn {
1347
+ position: absolute;
1348
+ bottom: -28px; /* Mostly outside, slight overlap */
1349
+ left: 50%;
1350
+ transform: translateX(-50%);
1351
+ display: flex;
1352
+ background: transparent;
1353
+ border: none;
1354
+ border-radius: 50%;
1355
+ padding: 4px;
1356
+ width: 32px;
1357
+ height: 32px;
1358
+ cursor: pointer;
1359
+ z-index: 100;
1360
+ align-items: center;
1361
+ justify-content: center;
1362
+ color: #888;
1363
+ transition: all 0.2s ease;
1364
+ box-shadow: none;
1365
+ }
1366
+
1367
+ .cv-expand-btn:hover {
1368
+ background: rgba(0, 0, 0, 0.05);
1369
+ color: #000;
1370
+ transform: translateX(-50%) scale(1.1);
1371
+ }
1372
+
1373
+ .cv-expand-btn svg {
1374
+ display: block;
1375
+ opacity: 0.6;
1376
+ }
1377
+
1378
+ .cv-expand-btn:hover svg {
1379
+ opacity: 1;
1380
+ }
1381
+
1216
1382
  `;
1217
1383
 
1218
1384
  /**
@@ -2602,7 +2768,7 @@ class CustomViewsCore {
2602
2768
  });
2603
2769
  }
2604
2770
  const computedState = {
2605
- toggles: this.config.toggles?.map(t => t.id) || [],
2771
+ shownToggles: this.config.toggles?.map(t => t.id) || [],
2606
2772
  tabs
2607
2773
  };
2608
2774
  return computedState;
@@ -2674,9 +2840,9 @@ class CustomViewsCore {
2674
2840
  const initialTop = anchorElement.getBoundingClientRect().top;
2675
2841
  const currentTabs = this.getCurrentActiveTabs();
2676
2842
  currentTabs[groupId] = tabId;
2677
- const currentToggles = this.getCurrentActiveToggles();
2843
+ const currentState = this.getCurrentState();
2678
2844
  const newState = {
2679
- toggles: currentToggles,
2845
+ ...currentState,
2680
2846
  tabs: currentTabs,
2681
2847
  };
2682
2848
  // 2. Apply state with scroll anchor information
@@ -2732,7 +2898,8 @@ class CustomViewsCore {
2732
2898
  // 1. URL State
2733
2899
  const urlState = URLStateManager.parseURL();
2734
2900
  if (urlState) {
2735
- this.applyState(urlState);
2901
+ // Apply URL state temporarily (do not persist until interaction)
2902
+ this.applyState(urlState, { persist: false });
2736
2903
  return;
2737
2904
  }
2738
2905
  // 2. Persisted State
@@ -2746,9 +2913,10 @@ class CustomViewsCore {
2746
2913
  }
2747
2914
  /**
2748
2915
  * Apply a custom state, saves to localStorage and updates the URL
2749
- * Add 'source' in options to indicate the origin of the state change
2916
+ * 'source' in options indicates the origin of the state change
2750
2917
  * (e.g., 'widget' to trigger scroll behavior)
2751
- * Add scrollAnchor in options to maintain scroll position of a specific element
2918
+ * 'scrollAnchor' in options indicates the element to maintain scroll position of
2919
+ * 'persist' (default true) to control whether to save to localStorage
2752
2920
  */
2753
2921
  applyState(state, options) {
2754
2922
  // console.log(`[Core] applyState called with source: ${options?.source}`, state);
@@ -2758,7 +2926,10 @@ class CustomViewsCore {
2758
2926
  }
2759
2927
  const snapshot = this.cloneState(state);
2760
2928
  this.renderState(snapshot);
2761
- this.persistenceManager.persistState(snapshot);
2929
+ // Only persist if explicitly requested (default true)
2930
+ if (options?.persist !== false) {
2931
+ this.persistenceManager.persistState(snapshot);
2932
+ }
2762
2933
  if (this.showUrlEnabled) {
2763
2934
  URLStateManager.updateURL(snapshot);
2764
2935
  }
@@ -2785,14 +2956,13 @@ class CustomViewsCore {
2785
2956
  renderState(state) {
2786
2957
  this.observer?.disconnect();
2787
2958
  this.lastAppliedState = this.cloneState(state);
2788
- const toggles = state?.toggles || [];
2959
+ const toggles = state?.shownToggles || [];
2789
2960
  const finalToggles = this.visibilityManager.filterVisibleToggles(toggles);
2790
- const toggleElements = Array.from(this.componentRegistry.toggles);
2961
+ const allToggleElements = Array.from(this.componentRegistry.toggles);
2791
2962
  const tabGroupElements = Array.from(this.componentRegistry.tabGroups);
2792
- // Apply toggle visibility
2793
- ToggleManager.applyToggles(toggleElements, finalToggles);
2963
+ ToggleManager.applyTogglesVisibility(allToggleElements, finalToggles, state.peekToggles);
2794
2964
  // Render assets into toggles
2795
- ToggleManager.renderAssets(toggleElements, finalToggles, this.assetsManager);
2965
+ ToggleManager.renderToggleAssets(allToggleElements, finalToggles, this.assetsManager);
2796
2966
  // Apply tab selections
2797
2967
  TabManager.applyTabSelections(tabGroupElements, state.tabs || {}, this.config.tabGroups);
2798
2968
  // Update nav active states (without rebuilding)
@@ -2823,16 +2993,16 @@ class CustomViewsCore {
2823
2993
  URLStateManager.clearURL();
2824
2994
  }
2825
2995
  /**
2826
- * Get the currently active toggles regardless of whether they come from custom state or default configuration
2996
+ * Get the full current state including active toggles, peek toggles, and tabs
2827
2997
  */
2828
- getCurrentActiveToggles() {
2998
+ getCurrentState() {
2829
2999
  if (this.lastAppliedState) {
2830
- return this.lastAppliedState.toggles || [];
3000
+ return this.cloneState(this.lastAppliedState);
2831
3001
  }
2832
3002
  if (this.config) {
2833
- return this.getComputedDefaultState().toggles || [];
3003
+ return this.cloneState(this.getComputedDefaultState());
2834
3004
  }
2835
- return [];
3005
+ return {};
2836
3006
  }
2837
3007
  /**
2838
3008
  * Clear all persistence and reset to default
@@ -3528,10 +3698,6 @@ const WIDGET_STYLES = `
3528
3698
  color: rgba(255, 255, 255, 0.6);
3529
3699
  }
3530
3700
 
3531
- .cv-widget-theme-dark .cv-toggle-slider {
3532
- background: rgba(255, 255, 255, 0.2);
3533
- }
3534
-
3535
3701
  .cv-widget-theme-dark .cv-tab-group-description {
3536
3702
  color: rgba(255, 255, 255, 0.8);
3537
3703
  }
@@ -3652,40 +3818,32 @@ const WIDGET_STYLES = `
3652
3818
  }
3653
3819
 
3654
3820
  .cv-toggle-input {
3821
+ /* Only hide if it is part of a custom slider toggle */
3822
+ }
3823
+ .cv-toggle-label .cv-toggle-input {
3655
3824
  opacity: 0;
3656
3825
  width: 0;
3657
3826
  height: 0;
3658
3827
  }
3659
3828
 
3660
- .cv-toggle-slider {
3661
- position: absolute;
3662
- top: 0;
3663
- left: 0;
3664
- right: 0;
3665
- bottom: 0;
3666
- background: rgba(0, 0, 0, 0.2);
3667
- border-radius: 9999px;
3668
- transition: background-color 0.2s ease;
3669
- }
3670
-
3671
- .cv-toggle-slider:before {
3672
- position: absolute;
3673
- content: "";
3674
- height: 1rem;
3675
- width: 1rem;
3676
- left: 0.25rem;
3677
- bottom: 0.25rem;
3678
- background: white;
3679
- border-radius: 50%;
3680
- transition: transform 0.2s ease;
3829
+ .cv-toggle-radios {
3830
+ display: flex;
3831
+ gap: 8px;
3681
3832
  }
3682
3833
 
3683
- .cv-toggle-input:checked + .cv-toggle-slider {
3684
- background: #3e84f4;
3834
+ .cv-radio-label {
3835
+ display: flex;
3836
+ align-items: center;
3837
+ gap: 4px;
3838
+ font-size: 0.85rem;
3839
+ cursor: pointer;
3685
3840
  }
3686
3841
 
3687
- .cv-toggle-input:checked + .cv-toggle-slider:before {
3688
- transform: translateX(1.25rem);
3842
+ .cv-radio-label input {
3843
+ margin: 0;
3844
+ opacity: 1;
3845
+ width: auto;
3846
+ height: auto;
3689
3847
  }
3690
3848
 
3691
3849
  /* Dark theme toggle switch styles */
@@ -4502,10 +4660,20 @@ class CustomViewsWidget {
4502
4660
  <div>
4503
4661
  <p class="cv-toggle-title">${toggle.label || toggle.id}</p>
4504
4662
  </div>
4505
- <label class="cv-toggle-label">
4506
- <input class="cv-toggle-input" type="checkbox" data-toggle="${toggle.id}"/>
4507
- <span class="cv-toggle-slider"></span>
4508
- </label>
4663
+ <div class="cv-toggle-radios">
4664
+ <label class="cv-radio-label" title="Hide">
4665
+ <input class="cv-toggle-input" type="radio" name="cv-toggle-${toggle.id}" value="hide" data-toggle="${toggle.id}"/>
4666
+ <span>Hide</span>
4667
+ </label>
4668
+ <label class="cv-radio-label" title="Peek">
4669
+ <input class="cv-toggle-input" type="radio" name="cv-toggle-${toggle.id}" value="peek" data-toggle="${toggle.id}"/>
4670
+ <span>Peek</span>
4671
+ </label>
4672
+ <label class="cv-radio-label" title="Show">
4673
+ <input class="cv-toggle-input" type="radio" name="cv-toggle-${toggle.id}" value="show" data-toggle="${toggle.id}"/>
4674
+ <span>Show</span>
4675
+ </label>
4676
+ </div>
4509
4677
  </div>
4510
4678
  </div>
4511
4679
  `).join('');
@@ -4706,10 +4874,11 @@ class CustomViewsWidget {
4706
4874
  if (groupId && tabId) {
4707
4875
  const currentTabs = this.core.getCurrentActiveTabs();
4708
4876
  currentTabs[groupId] = tabId;
4709
- const currentToggles = this.core.getCurrentActiveToggles();
4877
+ const currentState = this.core.getCurrentState();
4710
4878
  const newState = {
4711
- toggles: currentToggles,
4712
- tabs: currentTabs
4879
+ shownToggles: currentState.shownToggles || [],
4880
+ peekToggles: currentState.peekToggles || [], // Preserve peek state, fallback to empty array
4881
+ tabs: currentTabs,
4713
4882
  };
4714
4883
  this.core.applyState(newState, { source: 'widget' });
4715
4884
  }
@@ -4807,25 +4976,33 @@ class CustomViewsWidget {
4807
4976
  }
4808
4977
  // Collect toggle values
4809
4978
  const toggles = [];
4810
- const toggleInputs = this.stateModal.querySelectorAll('.cv-toggle-input');
4811
- toggleInputs.forEach(toggleInput => {
4812
- const toggle = toggleInput.dataset.toggle;
4813
- if (toggle && toggleInput.checked) {
4814
- toggles.push(toggle);
4815
- }
4816
- });
4817
- // Collect tab selections
4818
- const tabGroupSelects = this.stateModal.querySelectorAll('.cv-tabgroup-select');
4819
- const tabs = {};
4820
- tabGroupSelects.forEach(select => {
4821
- const groupId = select.dataset.groupId;
4822
- if (groupId) {
4823
- tabs[groupId] = select.value;
4979
+ const peekToggles = [];
4980
+ // Get all radio inputs
4981
+ const radios = this.stateModal.querySelectorAll('input[type="radio"]:checked');
4982
+ radios.forEach(radio => {
4983
+ const input = radio;
4984
+ const toggleId = input.getAttribute('data-toggle');
4985
+ if (toggleId) {
4986
+ if (input.value === 'show') {
4987
+ toggles.push(toggleId);
4988
+ }
4989
+ else if (input.value === 'peek') {
4990
+ peekToggles.push(toggleId);
4991
+ }
4824
4992
  }
4825
4993
  });
4826
- const result = { toggles };
4827
- if (Object.keys(tabs).length > 0) {
4828
- result.tabs = tabs;
4994
+ const result = { shownToggles: toggles, peekToggles };
4995
+ // Get active tabs from selects
4996
+ const selects = this.stateModal.querySelectorAll('select[data-group-id]');
4997
+ if (selects.length > 0) {
4998
+ result.tabs = {};
4999
+ selects.forEach(select => {
5000
+ const el = select;
5001
+ const groupId = el.getAttribute('data-group-id');
5002
+ if (groupId) {
5003
+ result.tabs[groupId] = el.value;
5004
+ }
5005
+ });
4829
5006
  }
4830
5007
  return result;
4831
5008
  }
@@ -4845,18 +5022,29 @@ class CustomViewsWidget {
4845
5022
  loadCurrentStateIntoForm() {
4846
5023
  if (!this.stateModal)
4847
5024
  return;
4848
- // Get currently active toggles (from custom state or default configuration)
4849
- const activeToggles = this.core.getCurrentActiveToggles();
4850
- // First, uncheck all toggle inputs
5025
+ // We need complete state for both shown and peek toggles
5026
+ const currentState = this.core.getCurrentState();
5027
+ const currentToggles = currentState.shownToggles || [];
5028
+ const currentPeekToggles = currentState.peekToggles || [];
5029
+ // Reset all inputs first (optional, but good for clarity)
4851
5030
  const allToggleInputs = this.stateModal.querySelectorAll('.cv-toggle-input');
4852
- allToggleInputs.forEach(toggleInput => {
4853
- toggleInput.checked = false;
5031
+ // Identify unique toggles present in the modal
5032
+ const uniqueToggles = new Set();
5033
+ allToggleInputs.forEach(input => {
5034
+ if (input.dataset.toggle)
5035
+ uniqueToggles.add(input.dataset.toggle);
4854
5036
  });
4855
- // Then check the ones that should be active
4856
- activeToggles.forEach(toggle => {
4857
- const toggleInput = this.stateModal?.querySelector(`[data-toggle="${toggle}"]`);
4858
- if (toggleInput) {
4859
- toggleInput.checked = true;
5037
+ uniqueToggles.forEach(toggleId => {
5038
+ let valueToSelect = 'hide';
5039
+ if (currentToggles.includes(toggleId)) {
5040
+ valueToSelect = 'show';
5041
+ }
5042
+ else if (currentPeekToggles.includes(toggleId)) {
5043
+ valueToSelect = 'peek';
5044
+ }
5045
+ const input = this.stateModal.querySelector(`input[name="cv-toggle-${toggleId}"][value="${valueToSelect}"]`);
5046
+ if (input) {
5047
+ input.checked = true;
4860
5048
  }
4861
5049
  });
4862
5050
  // Load tab group selections