@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.
- package/dist/custom-views.core.cjs.js +578 -257
- package/dist/custom-views.core.cjs.js.map +1 -1
- package/dist/custom-views.core.esm.js +578 -257
- package/dist/custom-views.core.esm.js.map +1 -1
- package/dist/custom-views.esm.js +578 -257
- package/dist/custom-views.esm.js.map +1 -1
- package/dist/custom-views.js +578 -257
- package/dist/custom-views.js.map +1 -1
- package/dist/custom-views.min.js +2 -2
- package/dist/custom-views.min.js.map +1 -1
- package/dist/types/core/core.d.ts +6 -5
- package/dist/types/core/core.d.ts.map +1 -1
- package/dist/types/core/toggle-manager.d.ts +12 -2
- package/dist/types/core/toggle-manager.d.ts.map +1 -1
- package/dist/types/core/url-state-manager.d.ts.map +1 -1
- package/dist/types/core/widget.d.ts +1 -0
- package/dist/types/core/widget.d.ts.map +1 -1
- package/dist/types/styles/toggle-styles.d.ts +1 -1
- package/dist/types/styles/toggle-styles.d.ts.map +1 -1
- package/dist/types/styles/widget-styles.d.ts +1 -1
- package/dist/types/styles/widget-styles.d.ts.map +1 -1
- package/dist/types/types/types.d.ts +4 -2
- package/dist/types/types/types.d.ts.map +1 -1
- package/dist/types/utils/icons.d.ts +6 -0
- package/dist/types/utils/icons.d.ts.map +1 -1
- package/package.json +10 -4
- package/dist/types/core/share-button.d.ts +0 -13
- package/dist/types/core/share-button.d.ts.map +0 -1
- package/dist/types/styles/share-button-styles.d.ts +0 -3
- package/dist/types/styles/share-button-styles.d.ts.map +0 -1
package/dist/custom-views.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @customviews-js/customviews v1.
|
|
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.
|
|
167
|
-
compact.t = state.
|
|
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
|
-
|
|
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)) {
|
|
@@ -430,6 +435,20 @@ function getShareIcon() {
|
|
|
430
435
|
<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"/>
|
|
431
436
|
</svg>`;
|
|
432
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* GitHub icon for footer link
|
|
440
|
+
*/
|
|
441
|
+
function getGitHubIcon() {
|
|
442
|
+
return `<svg viewBox="0 0 98 96" width="16" height="16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
|
|
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"/>
|
|
444
|
+
</svg>`;
|
|
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
|
+
}
|
|
433
452
|
|
|
434
453
|
// Constants for selectors
|
|
435
454
|
const TABGROUP_SELECTOR$1 = 'cv-tabgroup';
|
|
@@ -1000,20 +1019,33 @@ function renderAssetInto(el, assetId, assetsManager) {
|
|
|
1000
1019
|
* ToggleManager handles discovery, visibility, and asset rendering for toggle elements
|
|
1001
1020
|
*/
|
|
1002
1021
|
class ToggleManager {
|
|
1022
|
+
/**
|
|
1023
|
+
* Track locally expanded elements (that were in peek mode but user expanded them)
|
|
1024
|
+
*/
|
|
1025
|
+
static expandedPeekElements = new WeakSet();
|
|
1003
1026
|
/**
|
|
1004
1027
|
* Apply toggle visibility to a given list of toggle elements
|
|
1005
1028
|
*/
|
|
1006
|
-
static
|
|
1007
|
-
|
|
1029
|
+
static applyTogglesVisibility(allToggleElements, activeToggles, peekToggles = []) {
|
|
1030
|
+
allToggleElements.forEach(el => {
|
|
1008
1031
|
const categories = this.getToggleCategories(el);
|
|
1009
1032
|
const shouldShow = categories.some(cat => activeToggles.includes(cat));
|
|
1010
|
-
|
|
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));
|
|
1011
1040
|
});
|
|
1012
1041
|
}
|
|
1013
1042
|
/**
|
|
1014
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)
|
|
1015
1045
|
*/
|
|
1016
|
-
static
|
|
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.
|
|
1017
1049
|
elements.forEach(el => {
|
|
1018
1050
|
const categories = this.getToggleCategories(el);
|
|
1019
1051
|
const toggleId = this.getToggleId(el);
|
|
@@ -1026,6 +1058,7 @@ class ToggleManager {
|
|
|
1026
1058
|
}
|
|
1027
1059
|
/**
|
|
1028
1060
|
* Get toggle categories from an element (supports both data attributes and cv-toggle elements)
|
|
1061
|
+
* Note: a toggle can have multiple categories.
|
|
1029
1062
|
*/
|
|
1030
1063
|
static getToggleCategories(el) {
|
|
1031
1064
|
if (el.tagName.toLowerCase() === 'cv-toggle') {
|
|
@@ -1046,14 +1079,101 @@ class ToggleManager {
|
|
|
1046
1079
|
/**
|
|
1047
1080
|
* Apply simple class-based visibility to a toggle element
|
|
1048
1081
|
*/
|
|
1049
|
-
static applyToggleVisibility(
|
|
1082
|
+
static applyToggleVisibility(toggleElement, visible, peek = false) {
|
|
1083
|
+
const isLocallyExpanded = this.expandedPeekElements.has(toggleElement);
|
|
1050
1084
|
if (visible) {
|
|
1051
|
-
|
|
1052
|
-
|
|
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);
|
|
1053
1097
|
}
|
|
1054
1098
|
else {
|
|
1055
|
-
|
|
1056
|
-
|
|
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);
|
|
1057
1177
|
}
|
|
1058
1178
|
}
|
|
1059
1179
|
/**
|
|
@@ -1061,15 +1181,15 @@ class ToggleManager {
|
|
|
1061
1181
|
* This includes applying visibility and rendering assets.
|
|
1062
1182
|
*/
|
|
1063
1183
|
static initializeToggles(root, activeToggles, assetsManager) {
|
|
1064
|
-
const
|
|
1184
|
+
const allToggleElements = [];
|
|
1065
1185
|
if (root.matches('[data-cv-toggle], [data-customviews-toggle], cv-toggle')) {
|
|
1066
|
-
|
|
1186
|
+
allToggleElements.push(root);
|
|
1067
1187
|
}
|
|
1068
|
-
root.querySelectorAll('[data-cv-toggle], [data-customviews-toggle], cv-toggle').forEach(el =>
|
|
1069
|
-
if (
|
|
1188
|
+
root.querySelectorAll('[data-cv-toggle], [data-customviews-toggle], cv-toggle').forEach(el => allToggleElements.push(el));
|
|
1189
|
+
if (allToggleElements.length === 0)
|
|
1070
1190
|
return;
|
|
1071
|
-
this.
|
|
1072
|
-
this.
|
|
1191
|
+
this.applyTogglesVisibility(allToggleElements, activeToggles);
|
|
1192
|
+
this.renderToggleAssets(allToggleElements, activeToggles, assetsManager);
|
|
1073
1193
|
}
|
|
1074
1194
|
}
|
|
1075
1195
|
|
|
@@ -1177,17 +1297,16 @@ class ScrollManager {
|
|
|
1177
1297
|
const TOGGLE_STYLES = `
|
|
1178
1298
|
/* Core toggle visibility transitions */
|
|
1179
1299
|
[data-cv-toggle], [data-customviews-toggle], cv-toggle {
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
margin 150ms ease;
|
|
1184
|
-
will-change: opacity, transform, max-height, margin;
|
|
1300
|
+
display: block;
|
|
1301
|
+
overflow: hidden;
|
|
1302
|
+
/* Removed transitions for instant toggling */
|
|
1185
1303
|
}
|
|
1186
1304
|
|
|
1305
|
+
/* Open State */
|
|
1187
1306
|
.cv-visible {
|
|
1188
1307
|
opacity: 1 !important;
|
|
1189
1308
|
transform: translateY(0) !important;
|
|
1190
|
-
max-height:
|
|
1309
|
+
max-height: none !important;
|
|
1191
1310
|
}
|
|
1192
1311
|
|
|
1193
1312
|
.cv-hidden {
|
|
@@ -1203,6 +1322,61 @@ const TOGGLE_STYLES = `
|
|
|
1203
1322
|
margin-bottom: 0 !important;
|
|
1204
1323
|
overflow: hidden !important;
|
|
1205
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
|
+
|
|
1206
1380
|
`;
|
|
1207
1381
|
|
|
1208
1382
|
/**
|
|
@@ -2133,114 +2307,6 @@ class ShareManager {
|
|
|
2133
2307
|
}
|
|
2134
2308
|
}
|
|
2135
2309
|
|
|
2136
|
-
const SHARE_BUTTON_ID = 'cv-share-button';
|
|
2137
|
-
const SHARE_BUTTON_STYLES = `
|
|
2138
|
-
#${SHARE_BUTTON_ID} {
|
|
2139
|
-
position: fixed;
|
|
2140
|
-
bottom: 20px;
|
|
2141
|
-
right: 100px;
|
|
2142
|
-
width: 50px;
|
|
2143
|
-
height: 50px;
|
|
2144
|
-
border-radius: 50%;
|
|
2145
|
-
background-color: #007bff; /* Primary Blue */
|
|
2146
|
-
color: white;
|
|
2147
|
-
border: none;
|
|
2148
|
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); /* Drop shadow */
|
|
2149
|
-
z-index: 9998; /* Below modals (9999) but above content */
|
|
2150
|
-
cursor: pointer;
|
|
2151
|
-
display: flex;
|
|
2152
|
-
align-items: center;
|
|
2153
|
-
justify-content: center;
|
|
2154
|
-
transition: opacity 0.3s ease, transform 0.3s ease, background-color 0.2s;
|
|
2155
|
-
opacity: 1;
|
|
2156
|
-
transform: scale(1);
|
|
2157
|
-
padding: 0;
|
|
2158
|
-
margin: 0;
|
|
2159
|
-
}
|
|
2160
|
-
|
|
2161
|
-
#${SHARE_BUTTON_ID}:hover {
|
|
2162
|
-
background-color: #0056b3; /* Darker blue on hover */
|
|
2163
|
-
transform: scale(1.05);
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
#${SHARE_BUTTON_ID}:active {
|
|
2167
|
-
transform: scale(0.95);
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
#${SHARE_BUTTON_ID}.cv-hidden {
|
|
2171
|
-
opacity: 0;
|
|
2172
|
-
pointer-events: none;
|
|
2173
|
-
transform: scale(0.8);
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
|
-
@media print {
|
|
2177
|
-
#${SHARE_BUTTON_ID} {
|
|
2178
|
-
display: none !important;
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
#${SHARE_BUTTON_ID} svg {
|
|
2183
|
-
width: 24px;
|
|
2184
|
-
height: 24px;
|
|
2185
|
-
fill: currentColor;
|
|
2186
|
-
}
|
|
2187
|
-
`;
|
|
2188
|
-
|
|
2189
|
-
class ShareButton {
|
|
2190
|
-
shareManager;
|
|
2191
|
-
button = null;
|
|
2192
|
-
boundHandleClick;
|
|
2193
|
-
constructor(shareManager) {
|
|
2194
|
-
this.shareManager = shareManager;
|
|
2195
|
-
this.boundHandleClick = () => this.shareManager.toggleShareMode();
|
|
2196
|
-
}
|
|
2197
|
-
init() {
|
|
2198
|
-
if (this.button)
|
|
2199
|
-
return;
|
|
2200
|
-
this.injectStyles();
|
|
2201
|
-
this.button = this.createButton();
|
|
2202
|
-
document.body.appendChild(this.button);
|
|
2203
|
-
// Subscribe to share manager state changes
|
|
2204
|
-
this.shareManager.addStateChangeListener((isActive) => {
|
|
2205
|
-
this.setShareModeActive(isActive);
|
|
2206
|
-
});
|
|
2207
|
-
}
|
|
2208
|
-
destroy() {
|
|
2209
|
-
if (this.button) {
|
|
2210
|
-
this.button.remove();
|
|
2211
|
-
this.button = null;
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
createButton() {
|
|
2215
|
-
const btn = document.createElement('button');
|
|
2216
|
-
btn.id = SHARE_BUTTON_ID;
|
|
2217
|
-
btn.setAttribute('aria-label', 'Share specific sections');
|
|
2218
|
-
btn.title = 'Select sections to share';
|
|
2219
|
-
// Using the link icon from utils, ensuring it's white via CSS currentColor
|
|
2220
|
-
btn.innerHTML = getShareIcon();
|
|
2221
|
-
btn.addEventListener('click', this.boundHandleClick);
|
|
2222
|
-
return btn;
|
|
2223
|
-
}
|
|
2224
|
-
injectStyles() {
|
|
2225
|
-
if (!document.getElementById('cv-share-button-styles')) {
|
|
2226
|
-
const style = document.createElement('style');
|
|
2227
|
-
style.id = 'cv-share-button-styles';
|
|
2228
|
-
style.textContent = SHARE_BUTTON_STYLES;
|
|
2229
|
-
document.head.appendChild(style);
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
setShareModeActive(isActive) {
|
|
2233
|
-
if (!this.button)
|
|
2234
|
-
return;
|
|
2235
|
-
if (isActive) {
|
|
2236
|
-
this.button.classList.add('cv-hidden');
|
|
2237
|
-
}
|
|
2238
|
-
else {
|
|
2239
|
-
this.button.classList.remove('cv-hidden');
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
2310
|
const FOCUS_MODE_STYLE_ID = 'cv-focus-mode-styles';
|
|
2245
2311
|
const BODY_FOCUS_CLASS = 'cv-focus-mode';
|
|
2246
2312
|
const HIDDEN_CLASS = 'cv-focus-hidden';
|
|
@@ -2582,7 +2648,6 @@ class CustomViewsCore {
|
|
|
2582
2648
|
visibilityManager;
|
|
2583
2649
|
observer = null;
|
|
2584
2650
|
shareManager;
|
|
2585
|
-
shareButton;
|
|
2586
2651
|
focusManager;
|
|
2587
2652
|
componentRegistry = {
|
|
2588
2653
|
toggles: new Set(),
|
|
@@ -2605,7 +2670,6 @@ class CustomViewsCore {
|
|
|
2605
2670
|
const excludedIds = [...DEFAULT_EXCLUDED_IDS, ...(this.config.shareExclusions?.ids || [])];
|
|
2606
2671
|
const commonOptions = { excludedTags, excludedIds };
|
|
2607
2672
|
this.shareManager = new ShareManager(commonOptions);
|
|
2608
|
-
this.shareButton = new ShareButton(this.shareManager);
|
|
2609
2673
|
this.focusManager = new FocusManager(this.rootEl, commonOptions);
|
|
2610
2674
|
}
|
|
2611
2675
|
getShareManager() {
|
|
@@ -2702,7 +2766,7 @@ class CustomViewsCore {
|
|
|
2702
2766
|
});
|
|
2703
2767
|
}
|
|
2704
2768
|
const computedState = {
|
|
2705
|
-
|
|
2769
|
+
shownToggles: this.config.toggles?.map(t => t.id) || [],
|
|
2706
2770
|
tabs
|
|
2707
2771
|
};
|
|
2708
2772
|
return computedState;
|
|
@@ -2756,7 +2820,6 @@ class CustomViewsCore {
|
|
|
2756
2820
|
});
|
|
2757
2821
|
this.loadAndCallApplyState();
|
|
2758
2822
|
this.focusManager.init();
|
|
2759
|
-
this.shareButton.init();
|
|
2760
2823
|
this.initObserver();
|
|
2761
2824
|
}
|
|
2762
2825
|
initializeNewComponents() {
|
|
@@ -2775,9 +2838,9 @@ class CustomViewsCore {
|
|
|
2775
2838
|
const initialTop = anchorElement.getBoundingClientRect().top;
|
|
2776
2839
|
const currentTabs = this.getCurrentActiveTabs();
|
|
2777
2840
|
currentTabs[groupId] = tabId;
|
|
2778
|
-
const
|
|
2841
|
+
const currentState = this.getCurrentState();
|
|
2779
2842
|
const newState = {
|
|
2780
|
-
|
|
2843
|
+
...currentState,
|
|
2781
2844
|
tabs: currentTabs,
|
|
2782
2845
|
};
|
|
2783
2846
|
// 2. Apply state with scroll anchor information
|
|
@@ -2833,7 +2896,8 @@ class CustomViewsCore {
|
|
|
2833
2896
|
// 1. URL State
|
|
2834
2897
|
const urlState = URLStateManager.parseURL();
|
|
2835
2898
|
if (urlState) {
|
|
2836
|
-
|
|
2899
|
+
// Apply URL state temporarily (do not persist until interaction)
|
|
2900
|
+
this.applyState(urlState, { persist: false });
|
|
2837
2901
|
return;
|
|
2838
2902
|
}
|
|
2839
2903
|
// 2. Persisted State
|
|
@@ -2847,9 +2911,10 @@ class CustomViewsCore {
|
|
|
2847
2911
|
}
|
|
2848
2912
|
/**
|
|
2849
2913
|
* Apply a custom state, saves to localStorage and updates the URL
|
|
2850
|
-
*
|
|
2914
|
+
* 'source' in options indicates the origin of the state change
|
|
2851
2915
|
* (e.g., 'widget' to trigger scroll behavior)
|
|
2852
|
-
*
|
|
2916
|
+
* 'scrollAnchor' in options indicates the element to maintain scroll position of
|
|
2917
|
+
* 'persist' (default true) to control whether to save to localStorage
|
|
2853
2918
|
*/
|
|
2854
2919
|
applyState(state, options) {
|
|
2855
2920
|
// console.log(`[Core] applyState called with source: ${options?.source}`, state);
|
|
@@ -2859,7 +2924,10 @@ class CustomViewsCore {
|
|
|
2859
2924
|
}
|
|
2860
2925
|
const snapshot = this.cloneState(state);
|
|
2861
2926
|
this.renderState(snapshot);
|
|
2862
|
-
|
|
2927
|
+
// Only persist if explicitly requested (default true)
|
|
2928
|
+
if (options?.persist !== false) {
|
|
2929
|
+
this.persistenceManager.persistState(snapshot);
|
|
2930
|
+
}
|
|
2863
2931
|
if (this.showUrlEnabled) {
|
|
2864
2932
|
URLStateManager.updateURL(snapshot);
|
|
2865
2933
|
}
|
|
@@ -2886,14 +2954,13 @@ class CustomViewsCore {
|
|
|
2886
2954
|
renderState(state) {
|
|
2887
2955
|
this.observer?.disconnect();
|
|
2888
2956
|
this.lastAppliedState = this.cloneState(state);
|
|
2889
|
-
const toggles = state?.
|
|
2957
|
+
const toggles = state?.shownToggles || [];
|
|
2890
2958
|
const finalToggles = this.visibilityManager.filterVisibleToggles(toggles);
|
|
2891
|
-
const
|
|
2959
|
+
const allToggleElements = Array.from(this.componentRegistry.toggles);
|
|
2892
2960
|
const tabGroupElements = Array.from(this.componentRegistry.tabGroups);
|
|
2893
|
-
|
|
2894
|
-
ToggleManager.applyToggles(toggleElements, finalToggles);
|
|
2961
|
+
ToggleManager.applyTogglesVisibility(allToggleElements, finalToggles, state.peekToggles);
|
|
2895
2962
|
// Render assets into toggles
|
|
2896
|
-
ToggleManager.
|
|
2963
|
+
ToggleManager.renderToggleAssets(allToggleElements, finalToggles, this.assetsManager);
|
|
2897
2964
|
// Apply tab selections
|
|
2898
2965
|
TabManager.applyTabSelections(tabGroupElements, state.tabs || {}, this.config.tabGroups);
|
|
2899
2966
|
// Update nav active states (without rebuilding)
|
|
@@ -2924,16 +2991,16 @@ class CustomViewsCore {
|
|
|
2924
2991
|
URLStateManager.clearURL();
|
|
2925
2992
|
}
|
|
2926
2993
|
/**
|
|
2927
|
-
* Get the
|
|
2994
|
+
* Get the full current state including active toggles, peek toggles, and tabs
|
|
2928
2995
|
*/
|
|
2929
|
-
|
|
2996
|
+
getCurrentState() {
|
|
2930
2997
|
if (this.lastAppliedState) {
|
|
2931
|
-
return this.lastAppliedState
|
|
2998
|
+
return this.cloneState(this.lastAppliedState);
|
|
2932
2999
|
}
|
|
2933
3000
|
if (this.config) {
|
|
2934
|
-
return this.getComputedDefaultState()
|
|
3001
|
+
return this.cloneState(this.getComputedDefaultState());
|
|
2935
3002
|
}
|
|
2936
|
-
return
|
|
3003
|
+
return {};
|
|
2937
3004
|
}
|
|
2938
3005
|
/**
|
|
2939
3006
|
* Clear all persistence and reset to default
|
|
@@ -3629,10 +3696,6 @@ const WIDGET_STYLES = `
|
|
|
3629
3696
|
color: rgba(255, 255, 255, 0.6);
|
|
3630
3697
|
}
|
|
3631
3698
|
|
|
3632
|
-
.cv-widget-theme-dark .cv-toggle-slider {
|
|
3633
|
-
background: rgba(255, 255, 255, 0.2);
|
|
3634
|
-
}
|
|
3635
|
-
|
|
3636
3699
|
.cv-widget-theme-dark .cv-tab-group-description {
|
|
3637
3700
|
color: rgba(255, 255, 255, 0.8);
|
|
3638
3701
|
}
|
|
@@ -3753,40 +3816,32 @@ const WIDGET_STYLES = `
|
|
|
3753
3816
|
}
|
|
3754
3817
|
|
|
3755
3818
|
.cv-toggle-input {
|
|
3819
|
+
/* Only hide if it is part of a custom slider toggle */
|
|
3820
|
+
}
|
|
3821
|
+
.cv-toggle-label .cv-toggle-input {
|
|
3756
3822
|
opacity: 0;
|
|
3757
3823
|
width: 0;
|
|
3758
3824
|
height: 0;
|
|
3759
3825
|
}
|
|
3760
3826
|
|
|
3761
|
-
.cv-toggle-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
left: 0;
|
|
3765
|
-
right: 0;
|
|
3766
|
-
bottom: 0;
|
|
3767
|
-
background: rgba(0, 0, 0, 0.2);
|
|
3768
|
-
border-radius: 9999px;
|
|
3769
|
-
transition: background-color 0.2s ease;
|
|
3770
|
-
}
|
|
3771
|
-
|
|
3772
|
-
.cv-toggle-slider:before {
|
|
3773
|
-
position: absolute;
|
|
3774
|
-
content: "";
|
|
3775
|
-
height: 1rem;
|
|
3776
|
-
width: 1rem;
|
|
3777
|
-
left: 0.25rem;
|
|
3778
|
-
bottom: 0.25rem;
|
|
3779
|
-
background: white;
|
|
3780
|
-
border-radius: 50%;
|
|
3781
|
-
transition: transform 0.2s ease;
|
|
3827
|
+
.cv-toggle-radios {
|
|
3828
|
+
display: flex;
|
|
3829
|
+
gap: 8px;
|
|
3782
3830
|
}
|
|
3783
3831
|
|
|
3784
|
-
.cv-
|
|
3785
|
-
|
|
3832
|
+
.cv-radio-label {
|
|
3833
|
+
display: flex;
|
|
3834
|
+
align-items: center;
|
|
3835
|
+
gap: 4px;
|
|
3836
|
+
font-size: 0.85rem;
|
|
3837
|
+
cursor: pointer;
|
|
3786
3838
|
}
|
|
3787
3839
|
|
|
3788
|
-
.cv-
|
|
3789
|
-
|
|
3840
|
+
.cv-radio-label input {
|
|
3841
|
+
margin: 0;
|
|
3842
|
+
opacity: 1;
|
|
3843
|
+
width: auto;
|
|
3844
|
+
height: auto;
|
|
3790
3845
|
}
|
|
3791
3846
|
|
|
3792
3847
|
/* Dark theme toggle switch styles */
|
|
@@ -4065,6 +4120,33 @@ const WIDGET_STYLES = `
|
|
|
4065
4120
|
padding: 0.75rem;
|
|
4066
4121
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
|
4067
4122
|
}
|
|
4123
|
+
|
|
4124
|
+
.cv-footer-link {
|
|
4125
|
+
display: flex;
|
|
4126
|
+
align-items: center;
|
|
4127
|
+
justify-content: center;
|
|
4128
|
+
gap: 0.5rem;
|
|
4129
|
+
font-size: 0.75rem;
|
|
4130
|
+
color: rgba(0, 0, 0, 0.5);
|
|
4131
|
+
text-decoration: none;
|
|
4132
|
+
transition: color 0.2s ease;
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4135
|
+
.cv-footer-link:hover {
|
|
4136
|
+
color: #3e84f4;
|
|
4137
|
+
}
|
|
4138
|
+
|
|
4139
|
+
.cv-footer-link svg {
|
|
4140
|
+
opacity: 0.8;
|
|
4141
|
+
}
|
|
4142
|
+
|
|
4143
|
+
.cv-widget-theme-dark .cv-footer-link {
|
|
4144
|
+
color: rgba(255, 255, 255, 0.4);
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
.cv-widget-theme-dark .cv-footer-link:hover {
|
|
4148
|
+
color: #60a5fa;
|
|
4149
|
+
}
|
|
4068
4150
|
|
|
4069
4151
|
.cv-reset-btn,
|
|
4070
4152
|
.cv-share-btn {
|
|
@@ -4256,6 +4338,154 @@ const WIDGET_STYLES = `
|
|
|
4256
4338
|
display: none !important;
|
|
4257
4339
|
}
|
|
4258
4340
|
}
|
|
4341
|
+
/* Widget Modal Tabs */
|
|
4342
|
+
.cv-modal-tabs {
|
|
4343
|
+
display: flex;
|
|
4344
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
4345
|
+
margin-bottom: 0.5rem;
|
|
4346
|
+
}
|
|
4347
|
+
|
|
4348
|
+
.cv-tab-content > .cv-content-section + .cv-content-section {
|
|
4349
|
+
margin-top: 1.5rem;
|
|
4350
|
+
}
|
|
4351
|
+
|
|
4352
|
+
.cv-modal-tab {
|
|
4353
|
+
padding: 0.75rem 1.5rem;
|
|
4354
|
+
font-size: 0.875rem;
|
|
4355
|
+
font-weight: 500;
|
|
4356
|
+
color: rgba(0, 0, 0, 0.6);
|
|
4357
|
+
background: none;
|
|
4358
|
+
border: none;
|
|
4359
|
+
border-bottom: 2px solid transparent;
|
|
4360
|
+
cursor: pointer;
|
|
4361
|
+
transition: all 0.2s ease;
|
|
4362
|
+
}
|
|
4363
|
+
|
|
4364
|
+
.cv-modal-tab:hover {
|
|
4365
|
+
color: rgba(0, 0, 0, 0.9);
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
.cv-modal-tab.active {
|
|
4369
|
+
color: #3e84f4;
|
|
4370
|
+
border-bottom-color: #3e84f4;
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
.cv-tab-content {
|
|
4374
|
+
display: none;
|
|
4375
|
+
animation: fadeIn 0.3s ease;
|
|
4376
|
+
}
|
|
4377
|
+
|
|
4378
|
+
.cv-tab-content.active {
|
|
4379
|
+
display: block;
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
/* Share Tab Content */
|
|
4383
|
+
.cv-share-content {
|
|
4384
|
+
display: flex;
|
|
4385
|
+
flex-direction: column;
|
|
4386
|
+
gap: 1rem;
|
|
4387
|
+
padding: 1rem 0;
|
|
4388
|
+
align-items: center;
|
|
4389
|
+
text-align: center;
|
|
4390
|
+
}
|
|
4391
|
+
|
|
4392
|
+
.cv-share-instruction {
|
|
4393
|
+
font-size: 0.9rem;
|
|
4394
|
+
color: rgba(0, 0, 0, 0.7);
|
|
4395
|
+
margin-bottom: 1rem;
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
.cv-share-action-btn {
|
|
4399
|
+
display: flex;
|
|
4400
|
+
align-items: center;
|
|
4401
|
+
justify-content: center;
|
|
4402
|
+
gap: 0.5rem;
|
|
4403
|
+
width: 100%;
|
|
4404
|
+
padding: 12px 16px;
|
|
4405
|
+
background: white;
|
|
4406
|
+
color: #333;
|
|
4407
|
+
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
4408
|
+
border-radius: 6px;
|
|
4409
|
+
cursor: pointer;
|
|
4410
|
+
font-size: 0.9rem;
|
|
4411
|
+
font-weight: 500;
|
|
4412
|
+
transition: all 0.2s ease;
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
.cv-share-action-btn:hover {
|
|
4416
|
+
background: #f8f9fa;
|
|
4417
|
+
border-color: rgba(0, 0, 0, 0.25);
|
|
4418
|
+
transform: translateY(-1px);
|
|
4419
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4422
|
+
.cv-share-action-btn.primary {
|
|
4423
|
+
background: #3e84f4;
|
|
4424
|
+
color: white;
|
|
4425
|
+
border-color: #3e84f4;
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
.cv-share-action-btn.primary:hover {
|
|
4429
|
+
background: #2b74e6;
|
|
4430
|
+
border-color: #2b74e6;
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
.cv-done-btn {
|
|
4434
|
+
padding: 0.375rem 1rem;
|
|
4435
|
+
background: #3e84f4;
|
|
4436
|
+
color: white;
|
|
4437
|
+
border: none;
|
|
4438
|
+
border-radius: 0.5rem;
|
|
4439
|
+
font-weight: 600;
|
|
4440
|
+
font-size: 0.875rem;
|
|
4441
|
+
cursor: pointer;
|
|
4442
|
+
transition: all 0.2s ease;
|
|
4443
|
+
}
|
|
4444
|
+
|
|
4445
|
+
.cv-done-btn:hover {
|
|
4446
|
+
background: #2b74e6;
|
|
4447
|
+
}
|
|
4448
|
+
|
|
4449
|
+
/* Dark Theme Adjustments */
|
|
4450
|
+
.cv-widget-theme-dark .cv-modal-tabs {
|
|
4451
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
4452
|
+
}
|
|
4453
|
+
|
|
4454
|
+
.cv-widget-theme-dark .cv-modal-tab {
|
|
4455
|
+
color: rgba(255, 255, 255, 0.6);
|
|
4456
|
+
}
|
|
4457
|
+
|
|
4458
|
+
.cv-widget-theme-dark .cv-modal-tab:hover {
|
|
4459
|
+
color: rgba(255, 255, 255, 0.9);
|
|
4460
|
+
}
|
|
4461
|
+
|
|
4462
|
+
.cv-widget-theme-dark .cv-modal-tab.active {
|
|
4463
|
+
color: #60a5fa;
|
|
4464
|
+
border-bottom-color: #60a5fa;
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
.cv-widget-theme-dark .cv-share-instruction {
|
|
4468
|
+
color: rgba(255, 255, 255, 0.7);
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4471
|
+
.cv-widget-theme-dark .cv-share-action-btn {
|
|
4472
|
+
background: #1a202c;
|
|
4473
|
+
color: white;
|
|
4474
|
+
border-color: rgba(255, 255, 255, 0.15);
|
|
4475
|
+
}
|
|
4476
|
+
|
|
4477
|
+
.cv-widget-theme-dark .cv-share-action-btn:hover {
|
|
4478
|
+
background: #2d3748;
|
|
4479
|
+
}
|
|
4480
|
+
|
|
4481
|
+
.cv-widget-theme-dark .cv-share-action-btn.primary {
|
|
4482
|
+
background: #3e84f4;
|
|
4483
|
+
border-color: #3e84f4;
|
|
4484
|
+
}
|
|
4485
|
+
|
|
4486
|
+
.cv-widget-theme-dark .cv-share-action-btn.primary:hover {
|
|
4487
|
+
background: #2b74e6;
|
|
4488
|
+
}
|
|
4259
4489
|
`;
|
|
4260
4490
|
/**
|
|
4261
4491
|
* Inject widget styles into the document head
|
|
@@ -4278,6 +4508,7 @@ class CustomViewsWidget {
|
|
|
4278
4508
|
_hasVisibleConfig = false;
|
|
4279
4509
|
pageToggleIds = new Set();
|
|
4280
4510
|
pageTabIds = new Set();
|
|
4511
|
+
currentTab = 'customize';
|
|
4281
4512
|
// Modal state
|
|
4282
4513
|
stateModal = null;
|
|
4283
4514
|
constructor(options) {
|
|
@@ -4427,10 +4658,20 @@ class CustomViewsWidget {
|
|
|
4427
4658
|
<div>
|
|
4428
4659
|
<p class="cv-toggle-title">${toggle.label || toggle.id}</p>
|
|
4429
4660
|
</div>
|
|
4430
|
-
<
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
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>
|
|
4434
4675
|
</div>
|
|
4435
4676
|
</div>
|
|
4436
4677
|
`).join('');
|
|
@@ -4453,12 +4694,12 @@ class CustomViewsWidget {
|
|
|
4453
4694
|
</div>
|
|
4454
4695
|
<div class="cv-tabgroup-info">
|
|
4455
4696
|
<div class="cv-tabgroup-title-container">
|
|
4456
|
-
<p class="cv-tabgroup-title">
|
|
4697
|
+
<p class="cv-tabgroup-title">Show only the selected tab</p>
|
|
4457
4698
|
</div>
|
|
4458
|
-
<p class="cv-tabgroup-description">
|
|
4699
|
+
<p class="cv-tabgroup-description">Hide the navigation headers</p>
|
|
4459
4700
|
</div>
|
|
4460
4701
|
<label class="cv-toggle-switch cv-nav-toggle">
|
|
4461
|
-
<input class="cv-nav-pref-input" type="checkbox" ${initialNavsVisible ? '
|
|
4702
|
+
<input class="cv-nav-pref-input" type="checkbox" ${initialNavsVisible ? '' : 'checked'} aria-label="Show only the selected tab" />
|
|
4462
4703
|
<span class="cv-switch-bg"></span>
|
|
4463
4704
|
<span class="cv-switch-knob"></span>
|
|
4464
4705
|
</label>
|
|
@@ -4494,37 +4735,64 @@ class CustomViewsWidget {
|
|
|
4494
4735
|
<main class="cv-modal-main">
|
|
4495
4736
|
${this.options.description ? `<p class="cv-modal-description">${this.options.description}</p>` : ''}
|
|
4496
4737
|
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
<
|
|
4500
|
-
|
|
4501
|
-
|
|
4738
|
+
<div class="cv-modal-tabs">
|
|
4739
|
+
<button class="cv-modal-tab ${this.currentTab === 'customize' ? 'active' : ''}" data-tab="customize">Customize</button>
|
|
4740
|
+
<button class="cv-modal-tab ${this.currentTab === 'share' ? 'active' : ''}" data-tab="share">Share</button>
|
|
4741
|
+
</div>
|
|
4742
|
+
|
|
4743
|
+
<div class="cv-tab-content ${this.currentTab === 'customize' ? 'active' : ''}" data-content="customize">
|
|
4744
|
+
${visibleToggles.length ? `
|
|
4745
|
+
<div class="cv-content-section">
|
|
4746
|
+
<div class="cv-section-heading">Toggles</div>
|
|
4747
|
+
<div class="cv-toggles-container">
|
|
4748
|
+
${toggleControlsHtml}
|
|
4749
|
+
</div>
|
|
4750
|
+
</div>
|
|
4751
|
+
` : ''}
|
|
4752
|
+
|
|
4753
|
+
${this.options.showTabGroups && tabGroups && tabGroups.length > 0 ? `
|
|
4754
|
+
<div class="cv-content-section">
|
|
4755
|
+
<div class="cv-section-heading">Tab Groups</div>
|
|
4756
|
+
<div class="cv-tabgroups-container">
|
|
4757
|
+
${tabGroupControlsHTML}
|
|
4758
|
+
</div>
|
|
4502
4759
|
</div>
|
|
4760
|
+
` : ''}
|
|
4503
4761
|
</div>
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4762
|
+
|
|
4763
|
+
<div class="cv-tab-content ${this.currentTab === 'share' ? 'active' : ''}" data-content="share">
|
|
4764
|
+
<div class="cv-share-content">
|
|
4765
|
+
<div class="cv-share-instruction">
|
|
4766
|
+
Create a shareable link for your current customization, or select specific parts of the page to share.
|
|
4767
|
+
</div>
|
|
4768
|
+
|
|
4769
|
+
<button class="cv-share-action-btn primary cv-start-share-btn">
|
|
4770
|
+
<span class="cv-btn-icon">${getShareIcon()}</span>
|
|
4771
|
+
<span>Select elements to share</span>
|
|
4772
|
+
</button>
|
|
4773
|
+
|
|
4774
|
+
<button class="cv-share-action-btn cv-copy-url-btn">
|
|
4775
|
+
<span class="cv-btn-icon">${getCopyIcon()}</span>
|
|
4776
|
+
<span>Copy Shareable URL of Settings</span>
|
|
4777
|
+
</button>
|
|
4511
4778
|
</div>
|
|
4512
4779
|
</div>
|
|
4513
|
-
` : ''}
|
|
4514
4780
|
</main>
|
|
4515
4781
|
|
|
4516
4782
|
<footer class="cv-modal-footer">
|
|
4517
4783
|
${this.options.showReset ? `
|
|
4518
|
-
<button class="cv-reset-btn">
|
|
4784
|
+
<button class="cv-reset-btn" title="Reset to Default">
|
|
4519
4785
|
<span class="cv-reset-btn-icon">${getResetIcon()}</span>
|
|
4520
|
-
<span>Reset
|
|
4521
|
-
</button>
|
|
4522
|
-
` : ''}
|
|
4523
|
-
<button class="cv-share-btn">
|
|
4524
|
-
<span>Copy Shareable URL</span>
|
|
4525
|
-
<span class="cv-share-btn-icon">${getCopyIcon()}</span>
|
|
4786
|
+
<span>Reset</span>
|
|
4526
4787
|
</button>
|
|
4788
|
+
` : '<div></div>'}
|
|
4789
|
+
|
|
4790
|
+
<a href="https://github.com/customviews-js/customviews" target="_blank" class="cv-footer-link">
|
|
4791
|
+
${getGitHubIcon()}
|
|
4792
|
+
<span>View on GitHub</span>
|
|
4793
|
+
</a>
|
|
4527
4794
|
|
|
4795
|
+
<button class="cv-done-btn">Done</button>
|
|
4528
4796
|
</footer>
|
|
4529
4797
|
</div>
|
|
4530
4798
|
`;
|
|
@@ -4545,20 +4813,6 @@ class CustomViewsWidget {
|
|
|
4545
4813
|
this.closeModal();
|
|
4546
4814
|
return;
|
|
4547
4815
|
}
|
|
4548
|
-
// Copy URL button
|
|
4549
|
-
if (target.closest('.cv-share-btn')) {
|
|
4550
|
-
this.copyShareableURL();
|
|
4551
|
-
const copyUrlBtn = target.closest('.cv-share-btn');
|
|
4552
|
-
const iconContainer = copyUrlBtn?.querySelector('.cv-share-btn-icon');
|
|
4553
|
-
if (iconContainer) {
|
|
4554
|
-
const originalIcon = iconContainer.innerHTML;
|
|
4555
|
-
iconContainer.innerHTML = getTickIcon();
|
|
4556
|
-
setTimeout(() => {
|
|
4557
|
-
iconContainer.innerHTML = originalIcon;
|
|
4558
|
-
}, 3000);
|
|
4559
|
-
}
|
|
4560
|
-
return;
|
|
4561
|
-
}
|
|
4562
4816
|
// Reset to default button
|
|
4563
4817
|
if (target.closest('.cv-reset-btn')) {
|
|
4564
4818
|
const resetBtn = target.closest('.cv-reset-btn');
|
|
@@ -4575,6 +4829,11 @@ class CustomViewsWidget {
|
|
|
4575
4829
|
}, 600);
|
|
4576
4830
|
return;
|
|
4577
4831
|
}
|
|
4832
|
+
// Done button
|
|
4833
|
+
if (target.closest('.cv-done-btn')) {
|
|
4834
|
+
this.closeModal();
|
|
4835
|
+
return;
|
|
4836
|
+
}
|
|
4578
4837
|
// Overlay click to close
|
|
4579
4838
|
if (e.target === this.stateModal) {
|
|
4580
4839
|
this.closeModal();
|
|
@@ -4613,10 +4872,11 @@ class CustomViewsWidget {
|
|
|
4613
4872
|
if (groupId && tabId) {
|
|
4614
4873
|
const currentTabs = this.core.getCurrentActiveTabs();
|
|
4615
4874
|
currentTabs[groupId] = tabId;
|
|
4616
|
-
const
|
|
4875
|
+
const currentState = this.core.getCurrentState();
|
|
4617
4876
|
const newState = {
|
|
4618
|
-
|
|
4619
|
-
|
|
4877
|
+
shownToggles: currentState.shownToggles || [],
|
|
4878
|
+
peekToggles: currentState.peekToggles || [], // Preserve peek state, fallback to empty array
|
|
4879
|
+
tabs: currentTabs,
|
|
4620
4880
|
};
|
|
4621
4881
|
this.core.applyState(newState, { source: 'widget' });
|
|
4622
4882
|
}
|
|
@@ -4635,10 +4895,10 @@ class CustomViewsWidget {
|
|
|
4635
4895
|
navIcon.innerHTML = isVisible ? getNavHeadingOnIcon() : getNavHeadingOffIcon();
|
|
4636
4896
|
}
|
|
4637
4897
|
};
|
|
4638
|
-
navHeaderCard.addEventListener('mouseenter', () => updateIcon(tabNavToggle.checked, true));
|
|
4639
|
-
navHeaderCard.addEventListener('mouseleave', () => updateIcon(tabNavToggle.checked, false));
|
|
4898
|
+
navHeaderCard.addEventListener('mouseenter', () => updateIcon(!tabNavToggle.checked, true));
|
|
4899
|
+
navHeaderCard.addEventListener('mouseleave', () => updateIcon(!tabNavToggle.checked, false));
|
|
4640
4900
|
tabNavToggle.addEventListener('change', () => {
|
|
4641
|
-
const visible = tabNavToggle.checked;
|
|
4901
|
+
const visible = !tabNavToggle.checked;
|
|
4642
4902
|
updateIcon(visible, false);
|
|
4643
4903
|
this.core.persistTabNavVisibility(visible);
|
|
4644
4904
|
try {
|
|
@@ -4649,6 +4909,48 @@ class CustomViewsWidget {
|
|
|
4649
4909
|
}
|
|
4650
4910
|
});
|
|
4651
4911
|
}
|
|
4912
|
+
// Tab switching
|
|
4913
|
+
const tabs = this.stateModal.querySelectorAll('.cv-modal-tab');
|
|
4914
|
+
tabs.forEach(tab => {
|
|
4915
|
+
tab.addEventListener('click', () => {
|
|
4916
|
+
const tabId = tab.dataset.tab;
|
|
4917
|
+
if (tabId === 'customize' || tabId === 'share') {
|
|
4918
|
+
this.currentTab = tabId;
|
|
4919
|
+
// Update UI without full re-render
|
|
4920
|
+
tabs.forEach(t => t.classList.remove('active'));
|
|
4921
|
+
tab.classList.add('active');
|
|
4922
|
+
const contents = this.stateModal?.querySelectorAll('.cv-tab-content');
|
|
4923
|
+
contents?.forEach(c => {
|
|
4924
|
+
c.classList.remove('active');
|
|
4925
|
+
if (c.dataset.content === tabId) {
|
|
4926
|
+
c.classList.add('active');
|
|
4927
|
+
}
|
|
4928
|
+
});
|
|
4929
|
+
}
|
|
4930
|
+
});
|
|
4931
|
+
});
|
|
4932
|
+
// Share buttons (inside content)
|
|
4933
|
+
const startShareBtn = this.stateModal.querySelector('.cv-start-share-btn');
|
|
4934
|
+
if (startShareBtn) {
|
|
4935
|
+
startShareBtn.addEventListener('click', () => {
|
|
4936
|
+
this.closeModal();
|
|
4937
|
+
this.core.toggleShareMode();
|
|
4938
|
+
});
|
|
4939
|
+
}
|
|
4940
|
+
const copyUrlBtn = this.stateModal.querySelector('.cv-copy-url-btn');
|
|
4941
|
+
if (copyUrlBtn) {
|
|
4942
|
+
copyUrlBtn.addEventListener('click', () => {
|
|
4943
|
+
this.copyShareableURL();
|
|
4944
|
+
const iconContainer = copyUrlBtn.querySelector('.cv-btn-icon');
|
|
4945
|
+
if (iconContainer) {
|
|
4946
|
+
const originalIcon = iconContainer.innerHTML;
|
|
4947
|
+
iconContainer.innerHTML = getTickIcon();
|
|
4948
|
+
setTimeout(() => {
|
|
4949
|
+
iconContainer.innerHTML = originalIcon;
|
|
4950
|
+
}, 2000);
|
|
4951
|
+
}
|
|
4952
|
+
});
|
|
4953
|
+
}
|
|
4652
4954
|
}
|
|
4653
4955
|
/**
|
|
4654
4956
|
* Apply theme class to the modal overlay based on options
|
|
@@ -4672,25 +4974,33 @@ class CustomViewsWidget {
|
|
|
4672
4974
|
}
|
|
4673
4975
|
// Collect toggle values
|
|
4674
4976
|
const toggles = [];
|
|
4675
|
-
const
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
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
|
+
}
|
|
4689
4990
|
}
|
|
4690
4991
|
});
|
|
4691
|
-
const result = { toggles };
|
|
4692
|
-
|
|
4693
|
-
|
|
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
|
+
});
|
|
4694
5004
|
}
|
|
4695
5005
|
return result;
|
|
4696
5006
|
}
|
|
@@ -4710,18 +5020,29 @@ class CustomViewsWidget {
|
|
|
4710
5020
|
loadCurrentStateIntoForm() {
|
|
4711
5021
|
if (!this.stateModal)
|
|
4712
5022
|
return;
|
|
4713
|
-
//
|
|
4714
|
-
const
|
|
4715
|
-
|
|
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)
|
|
4716
5028
|
const allToggleInputs = this.stateModal.querySelectorAll('.cv-toggle-input');
|
|
4717
|
-
|
|
4718
|
-
|
|
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);
|
|
4719
5034
|
});
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
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;
|
|
4725
5046
|
}
|
|
4726
5047
|
});
|
|
4727
5048
|
// Load tab group selections
|
|
@@ -4744,7 +5065,7 @@ class CustomViewsWidget {
|
|
|
4744
5065
|
const tabNavToggle = this.stateModal.querySelector('.cv-nav-pref-input');
|
|
4745
5066
|
const navIcon = this.stateModal?.querySelector('#cv-nav-icon');
|
|
4746
5067
|
if (tabNavToggle) {
|
|
4747
|
-
tabNavToggle.checked = navPref;
|
|
5068
|
+
tabNavToggle.checked = !navPref;
|
|
4748
5069
|
// Ensure UI matches actual visibility
|
|
4749
5070
|
TabManager.setNavsVisibility(document.body, navPref);
|
|
4750
5071
|
// Update the nav icon to reflect the current state
|