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