@customviews-js/customviews 1.3.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.3.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)) {
@@ -436,6 +441,20 @@
436
441
  <path fill="currentColor" d="M11 6.41V12a1 1 0 0 0 2 0V6.41l1.29 1.3a1 1 0 0 0 1.42 0a1 1 0 0 0 0-1.42l-3-3a1 1 0 0 0-1.42 0l-3 3a1 1 0 1 0 1.42 1.42L11 6.41z"/>
437
442
  </svg>`;
438
443
  }
444
+ /**
445
+ * GitHub icon for footer link
446
+ */
447
+ function getGitHubIcon() {
448
+ return `<svg viewBox="0 0 98 96" width="16" height="16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
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"/>
450
+ </svg>`;
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
+ }
439
458
 
440
459
  // Constants for selectors
441
460
  const TABGROUP_SELECTOR$1 = 'cv-tabgroup';
@@ -1006,20 +1025,33 @@
1006
1025
  * ToggleManager handles discovery, visibility, and asset rendering for toggle elements
1007
1026
  */
1008
1027
  class ToggleManager {
1028
+ /**
1029
+ * Track locally expanded elements (that were in peek mode but user expanded them)
1030
+ */
1031
+ static expandedPeekElements = new WeakSet();
1009
1032
  /**
1010
1033
  * Apply toggle visibility to a given list of toggle elements
1011
1034
  */
1012
- static applyToggles(elements, activeToggles) {
1013
- elements.forEach(el => {
1035
+ static applyTogglesVisibility(allToggleElements, activeToggles, peekToggles = []) {
1036
+ allToggleElements.forEach(el => {
1014
1037
  const categories = this.getToggleCategories(el);
1015
1038
  const shouldShow = categories.some(cat => activeToggles.includes(cat));
1016
- 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));
1017
1046
  });
1018
1047
  }
1019
1048
  /**
1020
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)
1021
1051
  */
1022
- 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.
1023
1055
  elements.forEach(el => {
1024
1056
  const categories = this.getToggleCategories(el);
1025
1057
  const toggleId = this.getToggleId(el);
@@ -1032,6 +1064,7 @@
1032
1064
  }
1033
1065
  /**
1034
1066
  * Get toggle categories from an element (supports both data attributes and cv-toggle elements)
1067
+ * Note: a toggle can have multiple categories.
1035
1068
  */
1036
1069
  static getToggleCategories(el) {
1037
1070
  if (el.tagName.toLowerCase() === 'cv-toggle') {
@@ -1052,14 +1085,101 @@
1052
1085
  /**
1053
1086
  * Apply simple class-based visibility to a toggle element
1054
1087
  */
1055
- static applyToggleVisibility(el, visible) {
1088
+ static applyToggleVisibility(toggleElement, visible, peek = false) {
1089
+ const isLocallyExpanded = this.expandedPeekElements.has(toggleElement);
1056
1090
  if (visible) {
1057
- el.classList.remove('cv-hidden');
1058
- 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);
1059
1103
  }
1060
1104
  else {
1061
- el.classList.add('cv-hidden');
1062
- 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);
1063
1183
  }
1064
1184
  }
1065
1185
  /**
@@ -1067,15 +1187,15 @@
1067
1187
  * This includes applying visibility and rendering assets.
1068
1188
  */
1069
1189
  static initializeToggles(root, activeToggles, assetsManager) {
1070
- const elements = [];
1190
+ const allToggleElements = [];
1071
1191
  if (root.matches('[data-cv-toggle], [data-customviews-toggle], cv-toggle')) {
1072
- elements.push(root);
1192
+ allToggleElements.push(root);
1073
1193
  }
1074
- root.querySelectorAll('[data-cv-toggle], [data-customviews-toggle], cv-toggle').forEach(el => elements.push(el));
1075
- 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)
1076
1196
  return;
1077
- this.applyToggles(elements, activeToggles);
1078
- this.renderAssets(elements, activeToggles, assetsManager);
1197
+ this.applyTogglesVisibility(allToggleElements, activeToggles);
1198
+ this.renderToggleAssets(allToggleElements, activeToggles, assetsManager);
1079
1199
  }
1080
1200
  }
1081
1201
 
@@ -1183,17 +1303,16 @@
1183
1303
  const TOGGLE_STYLES = `
1184
1304
  /* Core toggle visibility transitions */
1185
1305
  [data-cv-toggle], [data-customviews-toggle], cv-toggle {
1186
- transition: opacity 150ms ease,
1187
- transform 150ms ease,
1188
- max-height 200ms ease,
1189
- margin 150ms ease;
1190
- will-change: opacity, transform, max-height, margin;
1306
+ display: block;
1307
+ overflow: hidden;
1308
+ /* Removed transitions for instant toggling */
1191
1309
  }
1192
1310
 
1311
+ /* Open State */
1193
1312
  .cv-visible {
1194
1313
  opacity: 1 !important;
1195
1314
  transform: translateY(0) !important;
1196
- max-height: var(--cv-max-height, 9999px) !important;
1315
+ max-height: none !important;
1197
1316
  }
1198
1317
 
1199
1318
  .cv-hidden {
@@ -1209,6 +1328,61 @@
1209
1328
  margin-bottom: 0 !important;
1210
1329
  overflow: hidden !important;
1211
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
+
1212
1386
  `;
1213
1387
 
1214
1388
  /**
@@ -2139,114 +2313,6 @@ ${TAB_STYLES}
2139
2313
  }
2140
2314
  }
2141
2315
 
2142
- const SHARE_BUTTON_ID = 'cv-share-button';
2143
- const SHARE_BUTTON_STYLES = `
2144
- #${SHARE_BUTTON_ID} {
2145
- position: fixed;
2146
- bottom: 20px;
2147
- right: 100px;
2148
- width: 50px;
2149
- height: 50px;
2150
- border-radius: 50%;
2151
- background-color: #007bff; /* Primary Blue */
2152
- color: white;
2153
- border: none;
2154
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); /* Drop shadow */
2155
- z-index: 9998; /* Below modals (9999) but above content */
2156
- cursor: pointer;
2157
- display: flex;
2158
- align-items: center;
2159
- justify-content: center;
2160
- transition: opacity 0.3s ease, transform 0.3s ease, background-color 0.2s;
2161
- opacity: 1;
2162
- transform: scale(1);
2163
- padding: 0;
2164
- margin: 0;
2165
- }
2166
-
2167
- #${SHARE_BUTTON_ID}:hover {
2168
- background-color: #0056b3; /* Darker blue on hover */
2169
- transform: scale(1.05);
2170
- }
2171
-
2172
- #${SHARE_BUTTON_ID}:active {
2173
- transform: scale(0.95);
2174
- }
2175
-
2176
- #${SHARE_BUTTON_ID}.cv-hidden {
2177
- opacity: 0;
2178
- pointer-events: none;
2179
- transform: scale(0.8);
2180
- }
2181
-
2182
- @media print {
2183
- #${SHARE_BUTTON_ID} {
2184
- display: none !important;
2185
- }
2186
- }
2187
-
2188
- #${SHARE_BUTTON_ID} svg {
2189
- width: 24px;
2190
- height: 24px;
2191
- fill: currentColor;
2192
- }
2193
- `;
2194
-
2195
- class ShareButton {
2196
- shareManager;
2197
- button = null;
2198
- boundHandleClick;
2199
- constructor(shareManager) {
2200
- this.shareManager = shareManager;
2201
- this.boundHandleClick = () => this.shareManager.toggleShareMode();
2202
- }
2203
- init() {
2204
- if (this.button)
2205
- return;
2206
- this.injectStyles();
2207
- this.button = this.createButton();
2208
- document.body.appendChild(this.button);
2209
- // Subscribe to share manager state changes
2210
- this.shareManager.addStateChangeListener((isActive) => {
2211
- this.setShareModeActive(isActive);
2212
- });
2213
- }
2214
- destroy() {
2215
- if (this.button) {
2216
- this.button.remove();
2217
- this.button = null;
2218
- }
2219
- }
2220
- createButton() {
2221
- const btn = document.createElement('button');
2222
- btn.id = SHARE_BUTTON_ID;
2223
- btn.setAttribute('aria-label', 'Share specific sections');
2224
- btn.title = 'Select sections to share';
2225
- // Using the link icon from utils, ensuring it's white via CSS currentColor
2226
- btn.innerHTML = getShareIcon();
2227
- btn.addEventListener('click', this.boundHandleClick);
2228
- return btn;
2229
- }
2230
- injectStyles() {
2231
- if (!document.getElementById('cv-share-button-styles')) {
2232
- const style = document.createElement('style');
2233
- style.id = 'cv-share-button-styles';
2234
- style.textContent = SHARE_BUTTON_STYLES;
2235
- document.head.appendChild(style);
2236
- }
2237
- }
2238
- setShareModeActive(isActive) {
2239
- if (!this.button)
2240
- return;
2241
- if (isActive) {
2242
- this.button.classList.add('cv-hidden');
2243
- }
2244
- else {
2245
- this.button.classList.remove('cv-hidden');
2246
- }
2247
- }
2248
- }
2249
-
2250
2316
  const FOCUS_MODE_STYLE_ID = 'cv-focus-mode-styles';
2251
2317
  const BODY_FOCUS_CLASS = 'cv-focus-mode';
2252
2318
  const HIDDEN_CLASS = 'cv-focus-hidden';
@@ -2588,7 +2654,6 @@ ${TAB_STYLES}
2588
2654
  visibilityManager;
2589
2655
  observer = null;
2590
2656
  shareManager;
2591
- shareButton;
2592
2657
  focusManager;
2593
2658
  componentRegistry = {
2594
2659
  toggles: new Set(),
@@ -2611,7 +2676,6 @@ ${TAB_STYLES}
2611
2676
  const excludedIds = [...DEFAULT_EXCLUDED_IDS, ...(this.config.shareExclusions?.ids || [])];
2612
2677
  const commonOptions = { excludedTags, excludedIds };
2613
2678
  this.shareManager = new ShareManager(commonOptions);
2614
- this.shareButton = new ShareButton(this.shareManager);
2615
2679
  this.focusManager = new FocusManager(this.rootEl, commonOptions);
2616
2680
  }
2617
2681
  getShareManager() {
@@ -2708,7 +2772,7 @@ ${TAB_STYLES}
2708
2772
  });
2709
2773
  }
2710
2774
  const computedState = {
2711
- toggles: this.config.toggles?.map(t => t.id) || [],
2775
+ shownToggles: this.config.toggles?.map(t => t.id) || [],
2712
2776
  tabs
2713
2777
  };
2714
2778
  return computedState;
@@ -2762,7 +2826,6 @@ ${TAB_STYLES}
2762
2826
  });
2763
2827
  this.loadAndCallApplyState();
2764
2828
  this.focusManager.init();
2765
- this.shareButton.init();
2766
2829
  this.initObserver();
2767
2830
  }
2768
2831
  initializeNewComponents() {
@@ -2781,9 +2844,9 @@ ${TAB_STYLES}
2781
2844
  const initialTop = anchorElement.getBoundingClientRect().top;
2782
2845
  const currentTabs = this.getCurrentActiveTabs();
2783
2846
  currentTabs[groupId] = tabId;
2784
- const currentToggles = this.getCurrentActiveToggles();
2847
+ const currentState = this.getCurrentState();
2785
2848
  const newState = {
2786
- toggles: currentToggles,
2849
+ ...currentState,
2787
2850
  tabs: currentTabs,
2788
2851
  };
2789
2852
  // 2. Apply state with scroll anchor information
@@ -2839,7 +2902,8 @@ ${TAB_STYLES}
2839
2902
  // 1. URL State
2840
2903
  const urlState = URLStateManager.parseURL();
2841
2904
  if (urlState) {
2842
- this.applyState(urlState);
2905
+ // Apply URL state temporarily (do not persist until interaction)
2906
+ this.applyState(urlState, { persist: false });
2843
2907
  return;
2844
2908
  }
2845
2909
  // 2. Persisted State
@@ -2853,9 +2917,10 @@ ${TAB_STYLES}
2853
2917
  }
2854
2918
  /**
2855
2919
  * Apply a custom state, saves to localStorage and updates the URL
2856
- * Add 'source' in options to indicate the origin of the state change
2920
+ * 'source' in options indicates the origin of the state change
2857
2921
  * (e.g., 'widget' to trigger scroll behavior)
2858
- * 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
2859
2924
  */
2860
2925
  applyState(state, options) {
2861
2926
  // console.log(`[Core] applyState called with source: ${options?.source}`, state);
@@ -2865,7 +2930,10 @@ ${TAB_STYLES}
2865
2930
  }
2866
2931
  const snapshot = this.cloneState(state);
2867
2932
  this.renderState(snapshot);
2868
- this.persistenceManager.persistState(snapshot);
2933
+ // Only persist if explicitly requested (default true)
2934
+ if (options?.persist !== false) {
2935
+ this.persistenceManager.persistState(snapshot);
2936
+ }
2869
2937
  if (this.showUrlEnabled) {
2870
2938
  URLStateManager.updateURL(snapshot);
2871
2939
  }
@@ -2892,14 +2960,13 @@ ${TAB_STYLES}
2892
2960
  renderState(state) {
2893
2961
  this.observer?.disconnect();
2894
2962
  this.lastAppliedState = this.cloneState(state);
2895
- const toggles = state?.toggles || [];
2963
+ const toggles = state?.shownToggles || [];
2896
2964
  const finalToggles = this.visibilityManager.filterVisibleToggles(toggles);
2897
- const toggleElements = Array.from(this.componentRegistry.toggles);
2965
+ const allToggleElements = Array.from(this.componentRegistry.toggles);
2898
2966
  const tabGroupElements = Array.from(this.componentRegistry.tabGroups);
2899
- // Apply toggle visibility
2900
- ToggleManager.applyToggles(toggleElements, finalToggles);
2967
+ ToggleManager.applyTogglesVisibility(allToggleElements, finalToggles, state.peekToggles);
2901
2968
  // Render assets into toggles
2902
- ToggleManager.renderAssets(toggleElements, finalToggles, this.assetsManager);
2969
+ ToggleManager.renderToggleAssets(allToggleElements, finalToggles, this.assetsManager);
2903
2970
  // Apply tab selections
2904
2971
  TabManager.applyTabSelections(tabGroupElements, state.tabs || {}, this.config.tabGroups);
2905
2972
  // Update nav active states (without rebuilding)
@@ -2930,16 +2997,16 @@ ${TAB_STYLES}
2930
2997
  URLStateManager.clearURL();
2931
2998
  }
2932
2999
  /**
2933
- * 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
2934
3001
  */
2935
- getCurrentActiveToggles() {
3002
+ getCurrentState() {
2936
3003
  if (this.lastAppliedState) {
2937
- return this.lastAppliedState.toggles || [];
3004
+ return this.cloneState(this.lastAppliedState);
2938
3005
  }
2939
3006
  if (this.config) {
2940
- return this.getComputedDefaultState().toggles || [];
3007
+ return this.cloneState(this.getComputedDefaultState());
2941
3008
  }
2942
- return [];
3009
+ return {};
2943
3010
  }
2944
3011
  /**
2945
3012
  * Clear all persistence and reset to default
@@ -3635,10 +3702,6 @@ ${TAB_STYLES}
3635
3702
  color: rgba(255, 255, 255, 0.6);
3636
3703
  }
3637
3704
 
3638
- .cv-widget-theme-dark .cv-toggle-slider {
3639
- background: rgba(255, 255, 255, 0.2);
3640
- }
3641
-
3642
3705
  .cv-widget-theme-dark .cv-tab-group-description {
3643
3706
  color: rgba(255, 255, 255, 0.8);
3644
3707
  }
@@ -3759,40 +3822,32 @@ ${TAB_STYLES}
3759
3822
  }
3760
3823
 
3761
3824
  .cv-toggle-input {
3825
+ /* Only hide if it is part of a custom slider toggle */
3826
+ }
3827
+ .cv-toggle-label .cv-toggle-input {
3762
3828
  opacity: 0;
3763
3829
  width: 0;
3764
3830
  height: 0;
3765
3831
  }
3766
3832
 
3767
- .cv-toggle-slider {
3768
- position: absolute;
3769
- top: 0;
3770
- left: 0;
3771
- right: 0;
3772
- bottom: 0;
3773
- background: rgba(0, 0, 0, 0.2);
3774
- border-radius: 9999px;
3775
- transition: background-color 0.2s ease;
3776
- }
3777
-
3778
- .cv-toggle-slider:before {
3779
- position: absolute;
3780
- content: "";
3781
- height: 1rem;
3782
- width: 1rem;
3783
- left: 0.25rem;
3784
- bottom: 0.25rem;
3785
- background: white;
3786
- border-radius: 50%;
3787
- transition: transform 0.2s ease;
3833
+ .cv-toggle-radios {
3834
+ display: flex;
3835
+ gap: 8px;
3788
3836
  }
3789
3837
 
3790
- .cv-toggle-input:checked + .cv-toggle-slider {
3791
- background: #3e84f4;
3838
+ .cv-radio-label {
3839
+ display: flex;
3840
+ align-items: center;
3841
+ gap: 4px;
3842
+ font-size: 0.85rem;
3843
+ cursor: pointer;
3792
3844
  }
3793
3845
 
3794
- .cv-toggle-input:checked + .cv-toggle-slider:before {
3795
- transform: translateX(1.25rem);
3846
+ .cv-radio-label input {
3847
+ margin: 0;
3848
+ opacity: 1;
3849
+ width: auto;
3850
+ height: auto;
3796
3851
  }
3797
3852
 
3798
3853
  /* Dark theme toggle switch styles */
@@ -4071,6 +4126,33 @@ ${TAB_STYLES}
4071
4126
  padding: 0.75rem;
4072
4127
  border-top: 1px solid rgba(0, 0, 0, 0.1);
4073
4128
  }
4129
+
4130
+ .cv-footer-link {
4131
+ display: flex;
4132
+ align-items: center;
4133
+ justify-content: center;
4134
+ gap: 0.5rem;
4135
+ font-size: 0.75rem;
4136
+ color: rgba(0, 0, 0, 0.5);
4137
+ text-decoration: none;
4138
+ transition: color 0.2s ease;
4139
+ }
4140
+
4141
+ .cv-footer-link:hover {
4142
+ color: #3e84f4;
4143
+ }
4144
+
4145
+ .cv-footer-link svg {
4146
+ opacity: 0.8;
4147
+ }
4148
+
4149
+ .cv-widget-theme-dark .cv-footer-link {
4150
+ color: rgba(255, 255, 255, 0.4);
4151
+ }
4152
+
4153
+ .cv-widget-theme-dark .cv-footer-link:hover {
4154
+ color: #60a5fa;
4155
+ }
4074
4156
 
4075
4157
  .cv-reset-btn,
4076
4158
  .cv-share-btn {
@@ -4262,6 +4344,154 @@ ${TAB_STYLES}
4262
4344
  display: none !important;
4263
4345
  }
4264
4346
  }
4347
+ /* Widget Modal Tabs */
4348
+ .cv-modal-tabs {
4349
+ display: flex;
4350
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
4351
+ margin-bottom: 0.5rem;
4352
+ }
4353
+
4354
+ .cv-tab-content > .cv-content-section + .cv-content-section {
4355
+ margin-top: 1.5rem;
4356
+ }
4357
+
4358
+ .cv-modal-tab {
4359
+ padding: 0.75rem 1.5rem;
4360
+ font-size: 0.875rem;
4361
+ font-weight: 500;
4362
+ color: rgba(0, 0, 0, 0.6);
4363
+ background: none;
4364
+ border: none;
4365
+ border-bottom: 2px solid transparent;
4366
+ cursor: pointer;
4367
+ transition: all 0.2s ease;
4368
+ }
4369
+
4370
+ .cv-modal-tab:hover {
4371
+ color: rgba(0, 0, 0, 0.9);
4372
+ }
4373
+
4374
+ .cv-modal-tab.active {
4375
+ color: #3e84f4;
4376
+ border-bottom-color: #3e84f4;
4377
+ }
4378
+
4379
+ .cv-tab-content {
4380
+ display: none;
4381
+ animation: fadeIn 0.3s ease;
4382
+ }
4383
+
4384
+ .cv-tab-content.active {
4385
+ display: block;
4386
+ }
4387
+
4388
+ /* Share Tab Content */
4389
+ .cv-share-content {
4390
+ display: flex;
4391
+ flex-direction: column;
4392
+ gap: 1rem;
4393
+ padding: 1rem 0;
4394
+ align-items: center;
4395
+ text-align: center;
4396
+ }
4397
+
4398
+ .cv-share-instruction {
4399
+ font-size: 0.9rem;
4400
+ color: rgba(0, 0, 0, 0.7);
4401
+ margin-bottom: 1rem;
4402
+ }
4403
+
4404
+ .cv-share-action-btn {
4405
+ display: flex;
4406
+ align-items: center;
4407
+ justify-content: center;
4408
+ gap: 0.5rem;
4409
+ width: 100%;
4410
+ padding: 12px 16px;
4411
+ background: white;
4412
+ color: #333;
4413
+ border: 1px solid rgba(0, 0, 0, 0.15);
4414
+ border-radius: 6px;
4415
+ cursor: pointer;
4416
+ font-size: 0.9rem;
4417
+ font-weight: 500;
4418
+ transition: all 0.2s ease;
4419
+ }
4420
+
4421
+ .cv-share-action-btn:hover {
4422
+ background: #f8f9fa;
4423
+ border-color: rgba(0, 0, 0, 0.25);
4424
+ transform: translateY(-1px);
4425
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
4426
+ }
4427
+
4428
+ .cv-share-action-btn.primary {
4429
+ background: #3e84f4;
4430
+ color: white;
4431
+ border-color: #3e84f4;
4432
+ }
4433
+
4434
+ .cv-share-action-btn.primary:hover {
4435
+ background: #2b74e6;
4436
+ border-color: #2b74e6;
4437
+ }
4438
+
4439
+ .cv-done-btn {
4440
+ padding: 0.375rem 1rem;
4441
+ background: #3e84f4;
4442
+ color: white;
4443
+ border: none;
4444
+ border-radius: 0.5rem;
4445
+ font-weight: 600;
4446
+ font-size: 0.875rem;
4447
+ cursor: pointer;
4448
+ transition: all 0.2s ease;
4449
+ }
4450
+
4451
+ .cv-done-btn:hover {
4452
+ background: #2b74e6;
4453
+ }
4454
+
4455
+ /* Dark Theme Adjustments */
4456
+ .cv-widget-theme-dark .cv-modal-tabs {
4457
+ border-color: rgba(255, 255, 255, 0.1);
4458
+ }
4459
+
4460
+ .cv-widget-theme-dark .cv-modal-tab {
4461
+ color: rgba(255, 255, 255, 0.6);
4462
+ }
4463
+
4464
+ .cv-widget-theme-dark .cv-modal-tab:hover {
4465
+ color: rgba(255, 255, 255, 0.9);
4466
+ }
4467
+
4468
+ .cv-widget-theme-dark .cv-modal-tab.active {
4469
+ color: #60a5fa;
4470
+ border-bottom-color: #60a5fa;
4471
+ }
4472
+
4473
+ .cv-widget-theme-dark .cv-share-instruction {
4474
+ color: rgba(255, 255, 255, 0.7);
4475
+ }
4476
+
4477
+ .cv-widget-theme-dark .cv-share-action-btn {
4478
+ background: #1a202c;
4479
+ color: white;
4480
+ border-color: rgba(255, 255, 255, 0.15);
4481
+ }
4482
+
4483
+ .cv-widget-theme-dark .cv-share-action-btn:hover {
4484
+ background: #2d3748;
4485
+ }
4486
+
4487
+ .cv-widget-theme-dark .cv-share-action-btn.primary {
4488
+ background: #3e84f4;
4489
+ border-color: #3e84f4;
4490
+ }
4491
+
4492
+ .cv-widget-theme-dark .cv-share-action-btn.primary:hover {
4493
+ background: #2b74e6;
4494
+ }
4265
4495
  `;
4266
4496
  /**
4267
4497
  * Inject widget styles into the document head
@@ -4284,6 +4514,7 @@ ${TAB_STYLES}
4284
4514
  _hasVisibleConfig = false;
4285
4515
  pageToggleIds = new Set();
4286
4516
  pageTabIds = new Set();
4517
+ currentTab = 'customize';
4287
4518
  // Modal state
4288
4519
  stateModal = null;
4289
4520
  constructor(options) {
@@ -4433,10 +4664,20 @@ ${TAB_STYLES}
4433
4664
  <div>
4434
4665
  <p class="cv-toggle-title">${toggle.label || toggle.id}</p>
4435
4666
  </div>
4436
- <label class="cv-toggle-label">
4437
- <input class="cv-toggle-input" type="checkbox" data-toggle="${toggle.id}"/>
4438
- <span class="cv-toggle-slider"></span>
4439
- </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>
4440
4681
  </div>
4441
4682
  </div>
4442
4683
  `).join('');
@@ -4459,12 +4700,12 @@ ${TAB_STYLES}
4459
4700
  </div>
4460
4701
  <div class="cv-tabgroup-info">
4461
4702
  <div class="cv-tabgroup-title-container">
4462
- <p class="cv-tabgroup-title">Navigation Headers</p>
4703
+ <p class="cv-tabgroup-title">Show only the selected tab</p>
4463
4704
  </div>
4464
- <p class="cv-tabgroup-description">Show or hide navigation headers</p>
4705
+ <p class="cv-tabgroup-description">Hide the navigation headers</p>
4465
4706
  </div>
4466
4707
  <label class="cv-toggle-switch cv-nav-toggle">
4467
- <input class="cv-nav-pref-input" type="checkbox" ${initialNavsVisible ? 'checked' : ''} aria-label="Show or hide navigation headers" />
4708
+ <input class="cv-nav-pref-input" type="checkbox" ${initialNavsVisible ? '' : 'checked'} aria-label="Show only the selected tab" />
4468
4709
  <span class="cv-switch-bg"></span>
4469
4710
  <span class="cv-switch-knob"></span>
4470
4711
  </label>
@@ -4500,37 +4741,64 @@ ${TAB_STYLES}
4500
4741
  <main class="cv-modal-main">
4501
4742
  ${this.options.description ? `<p class="cv-modal-description">${this.options.description}</p>` : ''}
4502
4743
 
4503
- ${visibleToggles.length ? `
4504
- <div class="cv-content-section">
4505
- <div class="cv-section-heading">Toggles</div>
4506
- <div class="cv-toggles-container">
4507
- ${toggleControlsHtml}
4744
+ <div class="cv-modal-tabs">
4745
+ <button class="cv-modal-tab ${this.currentTab === 'customize' ? 'active' : ''}" data-tab="customize">Customize</button>
4746
+ <button class="cv-modal-tab ${this.currentTab === 'share' ? 'active' : ''}" data-tab="share">Share</button>
4747
+ </div>
4748
+
4749
+ <div class="cv-tab-content ${this.currentTab === 'customize' ? 'active' : ''}" data-content="customize">
4750
+ ${visibleToggles.length ? `
4751
+ <div class="cv-content-section">
4752
+ <div class="cv-section-heading">Toggles</div>
4753
+ <div class="cv-toggles-container">
4754
+ ${toggleControlsHtml}
4755
+ </div>
4756
+ </div>
4757
+ ` : ''}
4758
+
4759
+ ${this.options.showTabGroups && tabGroups && tabGroups.length > 0 ? `
4760
+ <div class="cv-content-section">
4761
+ <div class="cv-section-heading">Tab Groups</div>
4762
+ <div class="cv-tabgroups-container">
4763
+ ${tabGroupControlsHTML}
4764
+ </div>
4508
4765
  </div>
4766
+ ` : ''}
4509
4767
  </div>
4510
- ` : ''}
4511
-
4512
- ${this.options.showTabGroups && tabGroups && tabGroups.length > 0 ? `
4513
- <div class="cv-content-section">
4514
- <div class="cv-section-heading">Tab Groups</div>
4515
- <div class="cv-tabgroups-container">
4516
- ${tabGroupControlsHTML}
4768
+
4769
+ <div class="cv-tab-content ${this.currentTab === 'share' ? 'active' : ''}" data-content="share">
4770
+ <div class="cv-share-content">
4771
+ <div class="cv-share-instruction">
4772
+ Create a shareable link for your current customization, or select specific parts of the page to share.
4773
+ </div>
4774
+
4775
+ <button class="cv-share-action-btn primary cv-start-share-btn">
4776
+ <span class="cv-btn-icon">${getShareIcon()}</span>
4777
+ <span>Select elements to share</span>
4778
+ </button>
4779
+
4780
+ <button class="cv-share-action-btn cv-copy-url-btn">
4781
+ <span class="cv-btn-icon">${getCopyIcon()}</span>
4782
+ <span>Copy Shareable URL of Settings</span>
4783
+ </button>
4517
4784
  </div>
4518
4785
  </div>
4519
- ` : ''}
4520
4786
  </main>
4521
4787
 
4522
4788
  <footer class="cv-modal-footer">
4523
4789
  ${this.options.showReset ? `
4524
- <button class="cv-reset-btn">
4790
+ <button class="cv-reset-btn" title="Reset to Default">
4525
4791
  <span class="cv-reset-btn-icon">${getResetIcon()}</span>
4526
- <span>Reset to Default</span>
4527
- </button>
4528
- ` : ''}
4529
- <button class="cv-share-btn">
4530
- <span>Copy Shareable URL</span>
4531
- <span class="cv-share-btn-icon">${getCopyIcon()}</span>
4792
+ <span>Reset</span>
4532
4793
  </button>
4794
+ ` : '<div></div>'}
4795
+
4796
+ <a href="https://github.com/customviews-js/customviews" target="_blank" class="cv-footer-link">
4797
+ ${getGitHubIcon()}
4798
+ <span>View on GitHub</span>
4799
+ </a>
4533
4800
 
4801
+ <button class="cv-done-btn">Done</button>
4534
4802
  </footer>
4535
4803
  </div>
4536
4804
  `;
@@ -4551,20 +4819,6 @@ ${TAB_STYLES}
4551
4819
  this.closeModal();
4552
4820
  return;
4553
4821
  }
4554
- // Copy URL button
4555
- if (target.closest('.cv-share-btn')) {
4556
- this.copyShareableURL();
4557
- const copyUrlBtn = target.closest('.cv-share-btn');
4558
- const iconContainer = copyUrlBtn?.querySelector('.cv-share-btn-icon');
4559
- if (iconContainer) {
4560
- const originalIcon = iconContainer.innerHTML;
4561
- iconContainer.innerHTML = getTickIcon();
4562
- setTimeout(() => {
4563
- iconContainer.innerHTML = originalIcon;
4564
- }, 3000);
4565
- }
4566
- return;
4567
- }
4568
4822
  // Reset to default button
4569
4823
  if (target.closest('.cv-reset-btn')) {
4570
4824
  const resetBtn = target.closest('.cv-reset-btn');
@@ -4581,6 +4835,11 @@ ${TAB_STYLES}
4581
4835
  }, 600);
4582
4836
  return;
4583
4837
  }
4838
+ // Done button
4839
+ if (target.closest('.cv-done-btn')) {
4840
+ this.closeModal();
4841
+ return;
4842
+ }
4584
4843
  // Overlay click to close
4585
4844
  if (e.target === this.stateModal) {
4586
4845
  this.closeModal();
@@ -4619,10 +4878,11 @@ ${TAB_STYLES}
4619
4878
  if (groupId && tabId) {
4620
4879
  const currentTabs = this.core.getCurrentActiveTabs();
4621
4880
  currentTabs[groupId] = tabId;
4622
- const currentToggles = this.core.getCurrentActiveToggles();
4881
+ const currentState = this.core.getCurrentState();
4623
4882
  const newState = {
4624
- toggles: currentToggles,
4625
- tabs: currentTabs
4883
+ shownToggles: currentState.shownToggles || [],
4884
+ peekToggles: currentState.peekToggles || [], // Preserve peek state, fallback to empty array
4885
+ tabs: currentTabs,
4626
4886
  };
4627
4887
  this.core.applyState(newState, { source: 'widget' });
4628
4888
  }
@@ -4641,10 +4901,10 @@ ${TAB_STYLES}
4641
4901
  navIcon.innerHTML = isVisible ? getNavHeadingOnIcon() : getNavHeadingOffIcon();
4642
4902
  }
4643
4903
  };
4644
- navHeaderCard.addEventListener('mouseenter', () => updateIcon(tabNavToggle.checked, true));
4645
- navHeaderCard.addEventListener('mouseleave', () => updateIcon(tabNavToggle.checked, false));
4904
+ navHeaderCard.addEventListener('mouseenter', () => updateIcon(!tabNavToggle.checked, true));
4905
+ navHeaderCard.addEventListener('mouseleave', () => updateIcon(!tabNavToggle.checked, false));
4646
4906
  tabNavToggle.addEventListener('change', () => {
4647
- const visible = tabNavToggle.checked;
4907
+ const visible = !tabNavToggle.checked;
4648
4908
  updateIcon(visible, false);
4649
4909
  this.core.persistTabNavVisibility(visible);
4650
4910
  try {
@@ -4655,6 +4915,48 @@ ${TAB_STYLES}
4655
4915
  }
4656
4916
  });
4657
4917
  }
4918
+ // Tab switching
4919
+ const tabs = this.stateModal.querySelectorAll('.cv-modal-tab');
4920
+ tabs.forEach(tab => {
4921
+ tab.addEventListener('click', () => {
4922
+ const tabId = tab.dataset.tab;
4923
+ if (tabId === 'customize' || tabId === 'share') {
4924
+ this.currentTab = tabId;
4925
+ // Update UI without full re-render
4926
+ tabs.forEach(t => t.classList.remove('active'));
4927
+ tab.classList.add('active');
4928
+ const contents = this.stateModal?.querySelectorAll('.cv-tab-content');
4929
+ contents?.forEach(c => {
4930
+ c.classList.remove('active');
4931
+ if (c.dataset.content === tabId) {
4932
+ c.classList.add('active');
4933
+ }
4934
+ });
4935
+ }
4936
+ });
4937
+ });
4938
+ // Share buttons (inside content)
4939
+ const startShareBtn = this.stateModal.querySelector('.cv-start-share-btn');
4940
+ if (startShareBtn) {
4941
+ startShareBtn.addEventListener('click', () => {
4942
+ this.closeModal();
4943
+ this.core.toggleShareMode();
4944
+ });
4945
+ }
4946
+ const copyUrlBtn = this.stateModal.querySelector('.cv-copy-url-btn');
4947
+ if (copyUrlBtn) {
4948
+ copyUrlBtn.addEventListener('click', () => {
4949
+ this.copyShareableURL();
4950
+ const iconContainer = copyUrlBtn.querySelector('.cv-btn-icon');
4951
+ if (iconContainer) {
4952
+ const originalIcon = iconContainer.innerHTML;
4953
+ iconContainer.innerHTML = getTickIcon();
4954
+ setTimeout(() => {
4955
+ iconContainer.innerHTML = originalIcon;
4956
+ }, 2000);
4957
+ }
4958
+ });
4959
+ }
4658
4960
  }
4659
4961
  /**
4660
4962
  * Apply theme class to the modal overlay based on options
@@ -4678,25 +4980,33 @@ ${TAB_STYLES}
4678
4980
  }
4679
4981
  // Collect toggle values
4680
4982
  const toggles = [];
4681
- const toggleInputs = this.stateModal.querySelectorAll('.cv-toggle-input');
4682
- toggleInputs.forEach(toggleInput => {
4683
- const toggle = toggleInput.dataset.toggle;
4684
- if (toggle && toggleInput.checked) {
4685
- toggles.push(toggle);
4686
- }
4687
- });
4688
- // Collect tab selections
4689
- const tabGroupSelects = this.stateModal.querySelectorAll('.cv-tabgroup-select');
4690
- const tabs = {};
4691
- tabGroupSelects.forEach(select => {
4692
- const groupId = select.dataset.groupId;
4693
- if (groupId) {
4694
- 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
+ }
4695
4996
  }
4696
4997
  });
4697
- const result = { toggles };
4698
- if (Object.keys(tabs).length > 0) {
4699
- 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
+ });
4700
5010
  }
4701
5011
  return result;
4702
5012
  }
@@ -4716,18 +5026,29 @@ ${TAB_STYLES}
4716
5026
  loadCurrentStateIntoForm() {
4717
5027
  if (!this.stateModal)
4718
5028
  return;
4719
- // Get currently active toggles (from custom state or default configuration)
4720
- const activeToggles = this.core.getCurrentActiveToggles();
4721
- // 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)
4722
5034
  const allToggleInputs = this.stateModal.querySelectorAll('.cv-toggle-input');
4723
- allToggleInputs.forEach(toggleInput => {
4724
- 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);
4725
5040
  });
4726
- // Then check the ones that should be active
4727
- activeToggles.forEach(toggle => {
4728
- const toggleInput = this.stateModal?.querySelector(`[data-toggle="${toggle}"]`);
4729
- if (toggleInput) {
4730
- 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;
4731
5052
  }
4732
5053
  });
4733
5054
  // Load tab group selections
@@ -4750,7 +5071,7 @@ ${TAB_STYLES}
4750
5071
  const tabNavToggle = this.stateModal.querySelector('.cv-nav-pref-input');
4751
5072
  const navIcon = this.stateModal?.querySelector('#cv-nav-icon');
4752
5073
  if (tabNavToggle) {
4753
- tabNavToggle.checked = navPref;
5074
+ tabNavToggle.checked = !navPref;
4754
5075
  // Ensure UI matches actual visibility
4755
5076
  TabManager.setNavsVisibility(document.body, navPref);
4756
5077
  // Update the nav icon to reflect the current state