@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
|
@@ -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
|
*/
|
|
@@ -165,8 +165,12 @@ class URLStateManager {
|
|
|
165
165
|
// Create a compact representation
|
|
166
166
|
const compact = {};
|
|
167
167
|
// Add toggles if present and non-empty
|
|
168
|
-
if (state.
|
|
169
|
-
compact.t = state.
|
|
168
|
+
if (state.shownToggles && state.shownToggles.length > 0) {
|
|
169
|
+
compact.t = state.shownToggles;
|
|
170
|
+
}
|
|
171
|
+
// Add peek toggles if present and non-empty
|
|
172
|
+
if (state.peekToggles && state.peekToggles.length > 0) {
|
|
173
|
+
compact.p = state.peekToggles;
|
|
170
174
|
}
|
|
171
175
|
// Add tab groups if present
|
|
172
176
|
if (state.tabs && Object.keys(state.tabs).length > 0) {
|
|
@@ -227,7 +231,8 @@ class URLStateManager {
|
|
|
227
231
|
// Reconstruct State from compact format
|
|
228
232
|
// Reconstruct Toggles
|
|
229
233
|
const state = {
|
|
230
|
-
|
|
234
|
+
shownToggles: Array.isArray(compact.t) ? compact.t : [],
|
|
235
|
+
peekToggles: Array.isArray(compact.p) ? compact.p : []
|
|
231
236
|
};
|
|
232
237
|
// Reconstruct Tabs
|
|
233
238
|
if (Array.isArray(compact.g)) {
|
|
@@ -432,6 +437,20 @@ function getShareIcon() {
|
|
|
432
437
|
<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"/>
|
|
433
438
|
</svg>`;
|
|
434
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* GitHub icon for footer link
|
|
442
|
+
*/
|
|
443
|
+
function getGitHubIcon() {
|
|
444
|
+
return `<svg viewBox="0 0 98 96" width="16" height="16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
|
|
445
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"/>
|
|
446
|
+
</svg>`;
|
|
447
|
+
}
|
|
448
|
+
function getChevronDownIcon() {
|
|
449
|
+
return `<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>`;
|
|
450
|
+
}
|
|
451
|
+
function getChevronUpIcon() {
|
|
452
|
+
return `<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"></polyline></svg>`;
|
|
453
|
+
}
|
|
435
454
|
|
|
436
455
|
// Constants for selectors
|
|
437
456
|
const TABGROUP_SELECTOR$1 = 'cv-tabgroup';
|
|
@@ -1002,20 +1021,33 @@ function renderAssetInto(el, assetId, assetsManager) {
|
|
|
1002
1021
|
* ToggleManager handles discovery, visibility, and asset rendering for toggle elements
|
|
1003
1022
|
*/
|
|
1004
1023
|
class ToggleManager {
|
|
1024
|
+
/**
|
|
1025
|
+
* Track locally expanded elements (that were in peek mode but user expanded them)
|
|
1026
|
+
*/
|
|
1027
|
+
static expandedPeekElements = new WeakSet();
|
|
1005
1028
|
/**
|
|
1006
1029
|
* Apply toggle visibility to a given list of toggle elements
|
|
1007
1030
|
*/
|
|
1008
|
-
static
|
|
1009
|
-
|
|
1031
|
+
static applyTogglesVisibility(allToggleElements, activeToggles, peekToggles = []) {
|
|
1032
|
+
allToggleElements.forEach(el => {
|
|
1010
1033
|
const categories = this.getToggleCategories(el);
|
|
1011
1034
|
const shouldShow = categories.some(cat => activeToggles.includes(cat));
|
|
1012
|
-
|
|
1035
|
+
const shouldPeek = !shouldShow && categories.some(cat => peekToggles.includes(cat));
|
|
1036
|
+
if (!shouldPeek) {
|
|
1037
|
+
this.expandedPeekElements.delete(el);
|
|
1038
|
+
}
|
|
1039
|
+
// If locally expanded, treat as shown (override peek)
|
|
1040
|
+
// Note: If neither show nor peek is active (i.e. hidden), local expansion is ignored/cleared effectively
|
|
1041
|
+
this.applyToggleVisibility(el, shouldShow || (shouldPeek && this.expandedPeekElements.has(el)), shouldPeek && !this.expandedPeekElements.has(el));
|
|
1013
1042
|
});
|
|
1014
1043
|
}
|
|
1015
1044
|
/**
|
|
1016
1045
|
* Render assets into a given list of toggle elements that are currently visible
|
|
1046
|
+
* Toggles that have a toggleId and are currently visible will have their assets rendered (if any)
|
|
1017
1047
|
*/
|
|
1018
|
-
static
|
|
1048
|
+
static renderToggleAssets(elements, activeToggles, assetsManager) {
|
|
1049
|
+
// TO DO: (gerteck) Enable for peek toggles as well
|
|
1050
|
+
// Also, rework the rendering logic again to make it more user friendly.
|
|
1019
1051
|
elements.forEach(el => {
|
|
1020
1052
|
const categories = this.getToggleCategories(el);
|
|
1021
1053
|
const toggleId = this.getToggleId(el);
|
|
@@ -1028,6 +1060,7 @@ class ToggleManager {
|
|
|
1028
1060
|
}
|
|
1029
1061
|
/**
|
|
1030
1062
|
* Get toggle categories from an element (supports both data attributes and cv-toggle elements)
|
|
1063
|
+
* Note: a toggle can have multiple categories.
|
|
1031
1064
|
*/
|
|
1032
1065
|
static getToggleCategories(el) {
|
|
1033
1066
|
if (el.tagName.toLowerCase() === 'cv-toggle') {
|
|
@@ -1048,14 +1081,101 @@ class ToggleManager {
|
|
|
1048
1081
|
/**
|
|
1049
1082
|
* Apply simple class-based visibility to a toggle element
|
|
1050
1083
|
*/
|
|
1051
|
-
static applyToggleVisibility(
|
|
1084
|
+
static applyToggleVisibility(toggleElement, visible, peek = false) {
|
|
1085
|
+
const isLocallyExpanded = this.expandedPeekElements.has(toggleElement);
|
|
1052
1086
|
if (visible) {
|
|
1053
|
-
|
|
1054
|
-
|
|
1087
|
+
toggleElement.classList.remove('cv-hidden', 'cv-peek');
|
|
1088
|
+
toggleElement.classList.add('cv-visible');
|
|
1089
|
+
// Show collapse button ONLY if locally expanded (meaning we are actually in peek mode but expanded).
|
|
1090
|
+
// If globally visible (because of 'Show' state), isLocallyExpanded should have been cleared by applyTogglesVisibility,
|
|
1091
|
+
// so this will be false, and button will be removed.
|
|
1092
|
+
this.manageExpandButton(toggleElement, false, isLocallyExpanded);
|
|
1093
|
+
}
|
|
1094
|
+
else if (peek) {
|
|
1095
|
+
toggleElement.classList.remove('cv-hidden', 'cv-visible');
|
|
1096
|
+
toggleElement.classList.add('cv-peek');
|
|
1097
|
+
// Show/create expand button if peeked
|
|
1098
|
+
this.manageExpandButton(toggleElement, true, false);
|
|
1055
1099
|
}
|
|
1056
1100
|
else {
|
|
1057
|
-
|
|
1058
|
-
|
|
1101
|
+
toggleElement.classList.add('cv-hidden');
|
|
1102
|
+
toggleElement.classList.remove('cv-visible', 'cv-peek');
|
|
1103
|
+
// Ensure button is gone/hidden
|
|
1104
|
+
this.manageExpandButton(toggleElement, false, false);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Manage the presence of the inline Expand/Collapse button using a wrapper approach
|
|
1109
|
+
*/
|
|
1110
|
+
static manageExpandButton(toggleElement, showExpand, showCollapse = false) {
|
|
1111
|
+
// 1. Ensure wrapper exists
|
|
1112
|
+
let wrapper = toggleElement.parentElement;
|
|
1113
|
+
if (!wrapper || !wrapper.classList.contains('cv-wrapper')) {
|
|
1114
|
+
wrapper = document.createElement('div');
|
|
1115
|
+
wrapper.className = 'cv-wrapper';
|
|
1116
|
+
toggleElement.parentNode?.insertBefore(wrapper, toggleElement);
|
|
1117
|
+
wrapper.appendChild(toggleElement);
|
|
1118
|
+
}
|
|
1119
|
+
const btn = wrapper.querySelector('.cv-expand-btn');
|
|
1120
|
+
// 2. Handle "No Button" case (neither expand nor collapse)
|
|
1121
|
+
if (!showExpand && !showCollapse) {
|
|
1122
|
+
if (btn)
|
|
1123
|
+
btn.style.display = 'none';
|
|
1124
|
+
// If content is visible globally (not hidden), ensure wrapper has 'cv-expanded'
|
|
1125
|
+
// to hide the peek fade effect (since fade is for peek state only).
|
|
1126
|
+
if (!toggleElement.classList.contains('cv-hidden')) {
|
|
1127
|
+
wrapper.classList.add('cv-expanded');
|
|
1128
|
+
}
|
|
1129
|
+
else {
|
|
1130
|
+
wrapper.classList.remove('cv-expanded');
|
|
1131
|
+
}
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
// 3. Handle Button Needed (Expand or Collapse)
|
|
1135
|
+
const action = showExpand ? 'expand' : 'collapse';
|
|
1136
|
+
// Update Wrapper Class Logic
|
|
1137
|
+
// If showExpand (Peek state) -> remove cv-expanded (show fade)
|
|
1138
|
+
// If showCollapse (Expanded peek) -> add cv-expanded (hide fade)
|
|
1139
|
+
if (showExpand) {
|
|
1140
|
+
wrapper.classList.remove('cv-expanded');
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
if (!wrapper.classList.contains('cv-expanded'))
|
|
1144
|
+
wrapper.classList.add('cv-expanded');
|
|
1145
|
+
}
|
|
1146
|
+
// Check if existing button matches desired state
|
|
1147
|
+
const currentAction = btn?.getAttribute('data-action');
|
|
1148
|
+
if (btn && currentAction === action) {
|
|
1149
|
+
btn.style.display = 'flex';
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
// 4. Create New Button (if missing or state changed)
|
|
1153
|
+
const iconSvg = showExpand ? getChevronDownIcon() : getChevronUpIcon();
|
|
1154
|
+
const newBtn = document.createElement('button');
|
|
1155
|
+
newBtn.className = 'cv-expand-btn';
|
|
1156
|
+
newBtn.innerHTML = iconSvg;
|
|
1157
|
+
newBtn.setAttribute('aria-label', showExpand ? 'Expand content' : 'Collapse content');
|
|
1158
|
+
newBtn.setAttribute('data-action', action); // Track state
|
|
1159
|
+
newBtn.style.display = 'flex';
|
|
1160
|
+
newBtn.addEventListener('click', (e) => {
|
|
1161
|
+
e.stopPropagation();
|
|
1162
|
+
// Logic: Toggle expansion state
|
|
1163
|
+
if (showExpand) {
|
|
1164
|
+
wrapper.classList.add('cv-expanded');
|
|
1165
|
+
this.expandedPeekElements.add(toggleElement);
|
|
1166
|
+
this.applyToggleVisibility(toggleElement, true, false);
|
|
1167
|
+
}
|
|
1168
|
+
else {
|
|
1169
|
+
wrapper.classList.remove('cv-expanded');
|
|
1170
|
+
this.expandedPeekElements.delete(toggleElement);
|
|
1171
|
+
this.applyToggleVisibility(toggleElement, false, true);
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
if (btn) {
|
|
1175
|
+
btn.replaceWith(newBtn);
|
|
1176
|
+
}
|
|
1177
|
+
else {
|
|
1178
|
+
wrapper.appendChild(newBtn);
|
|
1059
1179
|
}
|
|
1060
1180
|
}
|
|
1061
1181
|
/**
|
|
@@ -1063,15 +1183,15 @@ class ToggleManager {
|
|
|
1063
1183
|
* This includes applying visibility and rendering assets.
|
|
1064
1184
|
*/
|
|
1065
1185
|
static initializeToggles(root, activeToggles, assetsManager) {
|
|
1066
|
-
const
|
|
1186
|
+
const allToggleElements = [];
|
|
1067
1187
|
if (root.matches('[data-cv-toggle], [data-customviews-toggle], cv-toggle')) {
|
|
1068
|
-
|
|
1188
|
+
allToggleElements.push(root);
|
|
1069
1189
|
}
|
|
1070
|
-
root.querySelectorAll('[data-cv-toggle], [data-customviews-toggle], cv-toggle').forEach(el =>
|
|
1071
|
-
if (
|
|
1190
|
+
root.querySelectorAll('[data-cv-toggle], [data-customviews-toggle], cv-toggle').forEach(el => allToggleElements.push(el));
|
|
1191
|
+
if (allToggleElements.length === 0)
|
|
1072
1192
|
return;
|
|
1073
|
-
this.
|
|
1074
|
-
this.
|
|
1193
|
+
this.applyTogglesVisibility(allToggleElements, activeToggles);
|
|
1194
|
+
this.renderToggleAssets(allToggleElements, activeToggles, assetsManager);
|
|
1075
1195
|
}
|
|
1076
1196
|
}
|
|
1077
1197
|
|
|
@@ -1179,17 +1299,16 @@ class ScrollManager {
|
|
|
1179
1299
|
const TOGGLE_STYLES = `
|
|
1180
1300
|
/* Core toggle visibility transitions */
|
|
1181
1301
|
[data-cv-toggle], [data-customviews-toggle], cv-toggle {
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
margin 150ms ease;
|
|
1186
|
-
will-change: opacity, transform, max-height, margin;
|
|
1302
|
+
display: block;
|
|
1303
|
+
overflow: hidden;
|
|
1304
|
+
/* Removed transitions for instant toggling */
|
|
1187
1305
|
}
|
|
1188
1306
|
|
|
1307
|
+
/* Open State */
|
|
1189
1308
|
.cv-visible {
|
|
1190
1309
|
opacity: 1 !important;
|
|
1191
1310
|
transform: translateY(0) !important;
|
|
1192
|
-
max-height:
|
|
1311
|
+
max-height: none !important;
|
|
1193
1312
|
}
|
|
1194
1313
|
|
|
1195
1314
|
.cv-hidden {
|
|
@@ -1205,6 +1324,61 @@ const TOGGLE_STYLES = `
|
|
|
1205
1324
|
margin-bottom: 0 !important;
|
|
1206
1325
|
overflow: hidden !important;
|
|
1207
1326
|
}
|
|
1327
|
+
|
|
1328
|
+
/* Close/Peek State */
|
|
1329
|
+
.cv-peek {
|
|
1330
|
+
display: block !important;
|
|
1331
|
+
max-height: 70px !important;
|
|
1332
|
+
overflow: hidden !important;
|
|
1333
|
+
opacity: 1 !important;
|
|
1334
|
+
transform: translateY(0) !important;
|
|
1335
|
+
mask-image: linear-gradient(to bottom, black 50%, transparent 100%);
|
|
1336
|
+
-webkit-mask-image: linear-gradient(to bottom, black 50%, transparent 100%);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.cv-wrapper {
|
|
1340
|
+
position: relative;
|
|
1341
|
+
width: 100%;
|
|
1342
|
+
display: block;
|
|
1343
|
+
margin-bottom: 24px; /* Space for the button */
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
.cv-expand-btn {
|
|
1347
|
+
position: absolute;
|
|
1348
|
+
bottom: -28px; /* Mostly outside, slight overlap */
|
|
1349
|
+
left: 50%;
|
|
1350
|
+
transform: translateX(-50%);
|
|
1351
|
+
display: flex;
|
|
1352
|
+
background: transparent;
|
|
1353
|
+
border: none;
|
|
1354
|
+
border-radius: 50%;
|
|
1355
|
+
padding: 4px;
|
|
1356
|
+
width: 32px;
|
|
1357
|
+
height: 32px;
|
|
1358
|
+
cursor: pointer;
|
|
1359
|
+
z-index: 100;
|
|
1360
|
+
align-items: center;
|
|
1361
|
+
justify-content: center;
|
|
1362
|
+
color: #888;
|
|
1363
|
+
transition: all 0.2s ease;
|
|
1364
|
+
box-shadow: none;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
.cv-expand-btn:hover {
|
|
1368
|
+
background: rgba(0, 0, 0, 0.05);
|
|
1369
|
+
color: #000;
|
|
1370
|
+
transform: translateX(-50%) scale(1.1);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
.cv-expand-btn svg {
|
|
1374
|
+
display: block;
|
|
1375
|
+
opacity: 0.6;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
.cv-expand-btn:hover svg {
|
|
1379
|
+
opacity: 1;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1208
1382
|
`;
|
|
1209
1383
|
|
|
1210
1384
|
/**
|
|
@@ -2135,114 +2309,6 @@ class ShareManager {
|
|
|
2135
2309
|
}
|
|
2136
2310
|
}
|
|
2137
2311
|
|
|
2138
|
-
const SHARE_BUTTON_ID = 'cv-share-button';
|
|
2139
|
-
const SHARE_BUTTON_STYLES = `
|
|
2140
|
-
#${SHARE_BUTTON_ID} {
|
|
2141
|
-
position: fixed;
|
|
2142
|
-
bottom: 20px;
|
|
2143
|
-
right: 100px;
|
|
2144
|
-
width: 50px;
|
|
2145
|
-
height: 50px;
|
|
2146
|
-
border-radius: 50%;
|
|
2147
|
-
background-color: #007bff; /* Primary Blue */
|
|
2148
|
-
color: white;
|
|
2149
|
-
border: none;
|
|
2150
|
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); /* Drop shadow */
|
|
2151
|
-
z-index: 9998; /* Below modals (9999) but above content */
|
|
2152
|
-
cursor: pointer;
|
|
2153
|
-
display: flex;
|
|
2154
|
-
align-items: center;
|
|
2155
|
-
justify-content: center;
|
|
2156
|
-
transition: opacity 0.3s ease, transform 0.3s ease, background-color 0.2s;
|
|
2157
|
-
opacity: 1;
|
|
2158
|
-
transform: scale(1);
|
|
2159
|
-
padding: 0;
|
|
2160
|
-
margin: 0;
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
#${SHARE_BUTTON_ID}:hover {
|
|
2164
|
-
background-color: #0056b3; /* Darker blue on hover */
|
|
2165
|
-
transform: scale(1.05);
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
#${SHARE_BUTTON_ID}:active {
|
|
2169
|
-
transform: scale(0.95);
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
#${SHARE_BUTTON_ID}.cv-hidden {
|
|
2173
|
-
opacity: 0;
|
|
2174
|
-
pointer-events: none;
|
|
2175
|
-
transform: scale(0.8);
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
@media print {
|
|
2179
|
-
#${SHARE_BUTTON_ID} {
|
|
2180
|
-
display: none !important;
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
#${SHARE_BUTTON_ID} svg {
|
|
2185
|
-
width: 24px;
|
|
2186
|
-
height: 24px;
|
|
2187
|
-
fill: currentColor;
|
|
2188
|
-
}
|
|
2189
|
-
`;
|
|
2190
|
-
|
|
2191
|
-
class ShareButton {
|
|
2192
|
-
shareManager;
|
|
2193
|
-
button = null;
|
|
2194
|
-
boundHandleClick;
|
|
2195
|
-
constructor(shareManager) {
|
|
2196
|
-
this.shareManager = shareManager;
|
|
2197
|
-
this.boundHandleClick = () => this.shareManager.toggleShareMode();
|
|
2198
|
-
}
|
|
2199
|
-
init() {
|
|
2200
|
-
if (this.button)
|
|
2201
|
-
return;
|
|
2202
|
-
this.injectStyles();
|
|
2203
|
-
this.button = this.createButton();
|
|
2204
|
-
document.body.appendChild(this.button);
|
|
2205
|
-
// Subscribe to share manager state changes
|
|
2206
|
-
this.shareManager.addStateChangeListener((isActive) => {
|
|
2207
|
-
this.setShareModeActive(isActive);
|
|
2208
|
-
});
|
|
2209
|
-
}
|
|
2210
|
-
destroy() {
|
|
2211
|
-
if (this.button) {
|
|
2212
|
-
this.button.remove();
|
|
2213
|
-
this.button = null;
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
createButton() {
|
|
2217
|
-
const btn = document.createElement('button');
|
|
2218
|
-
btn.id = SHARE_BUTTON_ID;
|
|
2219
|
-
btn.setAttribute('aria-label', 'Share specific sections');
|
|
2220
|
-
btn.title = 'Select sections to share';
|
|
2221
|
-
// Using the link icon from utils, ensuring it's white via CSS currentColor
|
|
2222
|
-
btn.innerHTML = getShareIcon();
|
|
2223
|
-
btn.addEventListener('click', this.boundHandleClick);
|
|
2224
|
-
return btn;
|
|
2225
|
-
}
|
|
2226
|
-
injectStyles() {
|
|
2227
|
-
if (!document.getElementById('cv-share-button-styles')) {
|
|
2228
|
-
const style = document.createElement('style');
|
|
2229
|
-
style.id = 'cv-share-button-styles';
|
|
2230
|
-
style.textContent = SHARE_BUTTON_STYLES;
|
|
2231
|
-
document.head.appendChild(style);
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
setShareModeActive(isActive) {
|
|
2235
|
-
if (!this.button)
|
|
2236
|
-
return;
|
|
2237
|
-
if (isActive) {
|
|
2238
|
-
this.button.classList.add('cv-hidden');
|
|
2239
|
-
}
|
|
2240
|
-
else {
|
|
2241
|
-
this.button.classList.remove('cv-hidden');
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
|
|
2246
2312
|
const FOCUS_MODE_STYLE_ID = 'cv-focus-mode-styles';
|
|
2247
2313
|
const BODY_FOCUS_CLASS = 'cv-focus-mode';
|
|
2248
2314
|
const HIDDEN_CLASS = 'cv-focus-hidden';
|
|
@@ -2584,7 +2650,6 @@ class CustomViewsCore {
|
|
|
2584
2650
|
visibilityManager;
|
|
2585
2651
|
observer = null;
|
|
2586
2652
|
shareManager;
|
|
2587
|
-
shareButton;
|
|
2588
2653
|
focusManager;
|
|
2589
2654
|
componentRegistry = {
|
|
2590
2655
|
toggles: new Set(),
|
|
@@ -2607,7 +2672,6 @@ class CustomViewsCore {
|
|
|
2607
2672
|
const excludedIds = [...DEFAULT_EXCLUDED_IDS, ...(this.config.shareExclusions?.ids || [])];
|
|
2608
2673
|
const commonOptions = { excludedTags, excludedIds };
|
|
2609
2674
|
this.shareManager = new ShareManager(commonOptions);
|
|
2610
|
-
this.shareButton = new ShareButton(this.shareManager);
|
|
2611
2675
|
this.focusManager = new FocusManager(this.rootEl, commonOptions);
|
|
2612
2676
|
}
|
|
2613
2677
|
getShareManager() {
|
|
@@ -2704,7 +2768,7 @@ class CustomViewsCore {
|
|
|
2704
2768
|
});
|
|
2705
2769
|
}
|
|
2706
2770
|
const computedState = {
|
|
2707
|
-
|
|
2771
|
+
shownToggles: this.config.toggles?.map(t => t.id) || [],
|
|
2708
2772
|
tabs
|
|
2709
2773
|
};
|
|
2710
2774
|
return computedState;
|
|
@@ -2758,7 +2822,6 @@ class CustomViewsCore {
|
|
|
2758
2822
|
});
|
|
2759
2823
|
this.loadAndCallApplyState();
|
|
2760
2824
|
this.focusManager.init();
|
|
2761
|
-
this.shareButton.init();
|
|
2762
2825
|
this.initObserver();
|
|
2763
2826
|
}
|
|
2764
2827
|
initializeNewComponents() {
|
|
@@ -2777,9 +2840,9 @@ class CustomViewsCore {
|
|
|
2777
2840
|
const initialTop = anchorElement.getBoundingClientRect().top;
|
|
2778
2841
|
const currentTabs = this.getCurrentActiveTabs();
|
|
2779
2842
|
currentTabs[groupId] = tabId;
|
|
2780
|
-
const
|
|
2843
|
+
const currentState = this.getCurrentState();
|
|
2781
2844
|
const newState = {
|
|
2782
|
-
|
|
2845
|
+
...currentState,
|
|
2783
2846
|
tabs: currentTabs,
|
|
2784
2847
|
};
|
|
2785
2848
|
// 2. Apply state with scroll anchor information
|
|
@@ -2835,7 +2898,8 @@ class CustomViewsCore {
|
|
|
2835
2898
|
// 1. URL State
|
|
2836
2899
|
const urlState = URLStateManager.parseURL();
|
|
2837
2900
|
if (urlState) {
|
|
2838
|
-
|
|
2901
|
+
// Apply URL state temporarily (do not persist until interaction)
|
|
2902
|
+
this.applyState(urlState, { persist: false });
|
|
2839
2903
|
return;
|
|
2840
2904
|
}
|
|
2841
2905
|
// 2. Persisted State
|
|
@@ -2849,9 +2913,10 @@ class CustomViewsCore {
|
|
|
2849
2913
|
}
|
|
2850
2914
|
/**
|
|
2851
2915
|
* Apply a custom state, saves to localStorage and updates the URL
|
|
2852
|
-
*
|
|
2916
|
+
* 'source' in options indicates the origin of the state change
|
|
2853
2917
|
* (e.g., 'widget' to trigger scroll behavior)
|
|
2854
|
-
*
|
|
2918
|
+
* 'scrollAnchor' in options indicates the element to maintain scroll position of
|
|
2919
|
+
* 'persist' (default true) to control whether to save to localStorage
|
|
2855
2920
|
*/
|
|
2856
2921
|
applyState(state, options) {
|
|
2857
2922
|
// console.log(`[Core] applyState called with source: ${options?.source}`, state);
|
|
@@ -2861,7 +2926,10 @@ class CustomViewsCore {
|
|
|
2861
2926
|
}
|
|
2862
2927
|
const snapshot = this.cloneState(state);
|
|
2863
2928
|
this.renderState(snapshot);
|
|
2864
|
-
|
|
2929
|
+
// Only persist if explicitly requested (default true)
|
|
2930
|
+
if (options?.persist !== false) {
|
|
2931
|
+
this.persistenceManager.persistState(snapshot);
|
|
2932
|
+
}
|
|
2865
2933
|
if (this.showUrlEnabled) {
|
|
2866
2934
|
URLStateManager.updateURL(snapshot);
|
|
2867
2935
|
}
|
|
@@ -2888,14 +2956,13 @@ class CustomViewsCore {
|
|
|
2888
2956
|
renderState(state) {
|
|
2889
2957
|
this.observer?.disconnect();
|
|
2890
2958
|
this.lastAppliedState = this.cloneState(state);
|
|
2891
|
-
const toggles = state?.
|
|
2959
|
+
const toggles = state?.shownToggles || [];
|
|
2892
2960
|
const finalToggles = this.visibilityManager.filterVisibleToggles(toggles);
|
|
2893
|
-
const
|
|
2961
|
+
const allToggleElements = Array.from(this.componentRegistry.toggles);
|
|
2894
2962
|
const tabGroupElements = Array.from(this.componentRegistry.tabGroups);
|
|
2895
|
-
|
|
2896
|
-
ToggleManager.applyToggles(toggleElements, finalToggles);
|
|
2963
|
+
ToggleManager.applyTogglesVisibility(allToggleElements, finalToggles, state.peekToggles);
|
|
2897
2964
|
// Render assets into toggles
|
|
2898
|
-
ToggleManager.
|
|
2965
|
+
ToggleManager.renderToggleAssets(allToggleElements, finalToggles, this.assetsManager);
|
|
2899
2966
|
// Apply tab selections
|
|
2900
2967
|
TabManager.applyTabSelections(tabGroupElements, state.tabs || {}, this.config.tabGroups);
|
|
2901
2968
|
// Update nav active states (without rebuilding)
|
|
@@ -2926,16 +2993,16 @@ class CustomViewsCore {
|
|
|
2926
2993
|
URLStateManager.clearURL();
|
|
2927
2994
|
}
|
|
2928
2995
|
/**
|
|
2929
|
-
* Get the
|
|
2996
|
+
* Get the full current state including active toggles, peek toggles, and tabs
|
|
2930
2997
|
*/
|
|
2931
|
-
|
|
2998
|
+
getCurrentState() {
|
|
2932
2999
|
if (this.lastAppliedState) {
|
|
2933
|
-
return this.lastAppliedState
|
|
3000
|
+
return this.cloneState(this.lastAppliedState);
|
|
2934
3001
|
}
|
|
2935
3002
|
if (this.config) {
|
|
2936
|
-
return this.getComputedDefaultState()
|
|
3003
|
+
return this.cloneState(this.getComputedDefaultState());
|
|
2937
3004
|
}
|
|
2938
|
-
return
|
|
3005
|
+
return {};
|
|
2939
3006
|
}
|
|
2940
3007
|
/**
|
|
2941
3008
|
* Clear all persistence and reset to default
|
|
@@ -3631,10 +3698,6 @@ const WIDGET_STYLES = `
|
|
|
3631
3698
|
color: rgba(255, 255, 255, 0.6);
|
|
3632
3699
|
}
|
|
3633
3700
|
|
|
3634
|
-
.cv-widget-theme-dark .cv-toggle-slider {
|
|
3635
|
-
background: rgba(255, 255, 255, 0.2);
|
|
3636
|
-
}
|
|
3637
|
-
|
|
3638
3701
|
.cv-widget-theme-dark .cv-tab-group-description {
|
|
3639
3702
|
color: rgba(255, 255, 255, 0.8);
|
|
3640
3703
|
}
|
|
@@ -3755,40 +3818,32 @@ const WIDGET_STYLES = `
|
|
|
3755
3818
|
}
|
|
3756
3819
|
|
|
3757
3820
|
.cv-toggle-input {
|
|
3821
|
+
/* Only hide if it is part of a custom slider toggle */
|
|
3822
|
+
}
|
|
3823
|
+
.cv-toggle-label .cv-toggle-input {
|
|
3758
3824
|
opacity: 0;
|
|
3759
3825
|
width: 0;
|
|
3760
3826
|
height: 0;
|
|
3761
3827
|
}
|
|
3762
3828
|
|
|
3763
|
-
.cv-toggle-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
left: 0;
|
|
3767
|
-
right: 0;
|
|
3768
|
-
bottom: 0;
|
|
3769
|
-
background: rgba(0, 0, 0, 0.2);
|
|
3770
|
-
border-radius: 9999px;
|
|
3771
|
-
transition: background-color 0.2s ease;
|
|
3772
|
-
}
|
|
3773
|
-
|
|
3774
|
-
.cv-toggle-slider:before {
|
|
3775
|
-
position: absolute;
|
|
3776
|
-
content: "";
|
|
3777
|
-
height: 1rem;
|
|
3778
|
-
width: 1rem;
|
|
3779
|
-
left: 0.25rem;
|
|
3780
|
-
bottom: 0.25rem;
|
|
3781
|
-
background: white;
|
|
3782
|
-
border-radius: 50%;
|
|
3783
|
-
transition: transform 0.2s ease;
|
|
3829
|
+
.cv-toggle-radios {
|
|
3830
|
+
display: flex;
|
|
3831
|
+
gap: 8px;
|
|
3784
3832
|
}
|
|
3785
3833
|
|
|
3786
|
-
.cv-
|
|
3787
|
-
|
|
3834
|
+
.cv-radio-label {
|
|
3835
|
+
display: flex;
|
|
3836
|
+
align-items: center;
|
|
3837
|
+
gap: 4px;
|
|
3838
|
+
font-size: 0.85rem;
|
|
3839
|
+
cursor: pointer;
|
|
3788
3840
|
}
|
|
3789
3841
|
|
|
3790
|
-
.cv-
|
|
3791
|
-
|
|
3842
|
+
.cv-radio-label input {
|
|
3843
|
+
margin: 0;
|
|
3844
|
+
opacity: 1;
|
|
3845
|
+
width: auto;
|
|
3846
|
+
height: auto;
|
|
3792
3847
|
}
|
|
3793
3848
|
|
|
3794
3849
|
/* Dark theme toggle switch styles */
|
|
@@ -4067,6 +4122,33 @@ const WIDGET_STYLES = `
|
|
|
4067
4122
|
padding: 0.75rem;
|
|
4068
4123
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
|
4069
4124
|
}
|
|
4125
|
+
|
|
4126
|
+
.cv-footer-link {
|
|
4127
|
+
display: flex;
|
|
4128
|
+
align-items: center;
|
|
4129
|
+
justify-content: center;
|
|
4130
|
+
gap: 0.5rem;
|
|
4131
|
+
font-size: 0.75rem;
|
|
4132
|
+
color: rgba(0, 0, 0, 0.5);
|
|
4133
|
+
text-decoration: none;
|
|
4134
|
+
transition: color 0.2s ease;
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4137
|
+
.cv-footer-link:hover {
|
|
4138
|
+
color: #3e84f4;
|
|
4139
|
+
}
|
|
4140
|
+
|
|
4141
|
+
.cv-footer-link svg {
|
|
4142
|
+
opacity: 0.8;
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4145
|
+
.cv-widget-theme-dark .cv-footer-link {
|
|
4146
|
+
color: rgba(255, 255, 255, 0.4);
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
.cv-widget-theme-dark .cv-footer-link:hover {
|
|
4150
|
+
color: #60a5fa;
|
|
4151
|
+
}
|
|
4070
4152
|
|
|
4071
4153
|
.cv-reset-btn,
|
|
4072
4154
|
.cv-share-btn {
|
|
@@ -4258,6 +4340,154 @@ const WIDGET_STYLES = `
|
|
|
4258
4340
|
display: none !important;
|
|
4259
4341
|
}
|
|
4260
4342
|
}
|
|
4343
|
+
/* Widget Modal Tabs */
|
|
4344
|
+
.cv-modal-tabs {
|
|
4345
|
+
display: flex;
|
|
4346
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
4347
|
+
margin-bottom: 0.5rem;
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
.cv-tab-content > .cv-content-section + .cv-content-section {
|
|
4351
|
+
margin-top: 1.5rem;
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
.cv-modal-tab {
|
|
4355
|
+
padding: 0.75rem 1.5rem;
|
|
4356
|
+
font-size: 0.875rem;
|
|
4357
|
+
font-weight: 500;
|
|
4358
|
+
color: rgba(0, 0, 0, 0.6);
|
|
4359
|
+
background: none;
|
|
4360
|
+
border: none;
|
|
4361
|
+
border-bottom: 2px solid transparent;
|
|
4362
|
+
cursor: pointer;
|
|
4363
|
+
transition: all 0.2s ease;
|
|
4364
|
+
}
|
|
4365
|
+
|
|
4366
|
+
.cv-modal-tab:hover {
|
|
4367
|
+
color: rgba(0, 0, 0, 0.9);
|
|
4368
|
+
}
|
|
4369
|
+
|
|
4370
|
+
.cv-modal-tab.active {
|
|
4371
|
+
color: #3e84f4;
|
|
4372
|
+
border-bottom-color: #3e84f4;
|
|
4373
|
+
}
|
|
4374
|
+
|
|
4375
|
+
.cv-tab-content {
|
|
4376
|
+
display: none;
|
|
4377
|
+
animation: fadeIn 0.3s ease;
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
.cv-tab-content.active {
|
|
4381
|
+
display: block;
|
|
4382
|
+
}
|
|
4383
|
+
|
|
4384
|
+
/* Share Tab Content */
|
|
4385
|
+
.cv-share-content {
|
|
4386
|
+
display: flex;
|
|
4387
|
+
flex-direction: column;
|
|
4388
|
+
gap: 1rem;
|
|
4389
|
+
padding: 1rem 0;
|
|
4390
|
+
align-items: center;
|
|
4391
|
+
text-align: center;
|
|
4392
|
+
}
|
|
4393
|
+
|
|
4394
|
+
.cv-share-instruction {
|
|
4395
|
+
font-size: 0.9rem;
|
|
4396
|
+
color: rgba(0, 0, 0, 0.7);
|
|
4397
|
+
margin-bottom: 1rem;
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
.cv-share-action-btn {
|
|
4401
|
+
display: flex;
|
|
4402
|
+
align-items: center;
|
|
4403
|
+
justify-content: center;
|
|
4404
|
+
gap: 0.5rem;
|
|
4405
|
+
width: 100%;
|
|
4406
|
+
padding: 12px 16px;
|
|
4407
|
+
background: white;
|
|
4408
|
+
color: #333;
|
|
4409
|
+
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
4410
|
+
border-radius: 6px;
|
|
4411
|
+
cursor: pointer;
|
|
4412
|
+
font-size: 0.9rem;
|
|
4413
|
+
font-weight: 500;
|
|
4414
|
+
transition: all 0.2s ease;
|
|
4415
|
+
}
|
|
4416
|
+
|
|
4417
|
+
.cv-share-action-btn:hover {
|
|
4418
|
+
background: #f8f9fa;
|
|
4419
|
+
border-color: rgba(0, 0, 0, 0.25);
|
|
4420
|
+
transform: translateY(-1px);
|
|
4421
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
4422
|
+
}
|
|
4423
|
+
|
|
4424
|
+
.cv-share-action-btn.primary {
|
|
4425
|
+
background: #3e84f4;
|
|
4426
|
+
color: white;
|
|
4427
|
+
border-color: #3e84f4;
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4430
|
+
.cv-share-action-btn.primary:hover {
|
|
4431
|
+
background: #2b74e6;
|
|
4432
|
+
border-color: #2b74e6;
|
|
4433
|
+
}
|
|
4434
|
+
|
|
4435
|
+
.cv-done-btn {
|
|
4436
|
+
padding: 0.375rem 1rem;
|
|
4437
|
+
background: #3e84f4;
|
|
4438
|
+
color: white;
|
|
4439
|
+
border: none;
|
|
4440
|
+
border-radius: 0.5rem;
|
|
4441
|
+
font-weight: 600;
|
|
4442
|
+
font-size: 0.875rem;
|
|
4443
|
+
cursor: pointer;
|
|
4444
|
+
transition: all 0.2s ease;
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
.cv-done-btn:hover {
|
|
4448
|
+
background: #2b74e6;
|
|
4449
|
+
}
|
|
4450
|
+
|
|
4451
|
+
/* Dark Theme Adjustments */
|
|
4452
|
+
.cv-widget-theme-dark .cv-modal-tabs {
|
|
4453
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
.cv-widget-theme-dark .cv-modal-tab {
|
|
4457
|
+
color: rgba(255, 255, 255, 0.6);
|
|
4458
|
+
}
|
|
4459
|
+
|
|
4460
|
+
.cv-widget-theme-dark .cv-modal-tab:hover {
|
|
4461
|
+
color: rgba(255, 255, 255, 0.9);
|
|
4462
|
+
}
|
|
4463
|
+
|
|
4464
|
+
.cv-widget-theme-dark .cv-modal-tab.active {
|
|
4465
|
+
color: #60a5fa;
|
|
4466
|
+
border-bottom-color: #60a5fa;
|
|
4467
|
+
}
|
|
4468
|
+
|
|
4469
|
+
.cv-widget-theme-dark .cv-share-instruction {
|
|
4470
|
+
color: rgba(255, 255, 255, 0.7);
|
|
4471
|
+
}
|
|
4472
|
+
|
|
4473
|
+
.cv-widget-theme-dark .cv-share-action-btn {
|
|
4474
|
+
background: #1a202c;
|
|
4475
|
+
color: white;
|
|
4476
|
+
border-color: rgba(255, 255, 255, 0.15);
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
.cv-widget-theme-dark .cv-share-action-btn:hover {
|
|
4480
|
+
background: #2d3748;
|
|
4481
|
+
}
|
|
4482
|
+
|
|
4483
|
+
.cv-widget-theme-dark .cv-share-action-btn.primary {
|
|
4484
|
+
background: #3e84f4;
|
|
4485
|
+
border-color: #3e84f4;
|
|
4486
|
+
}
|
|
4487
|
+
|
|
4488
|
+
.cv-widget-theme-dark .cv-share-action-btn.primary:hover {
|
|
4489
|
+
background: #2b74e6;
|
|
4490
|
+
}
|
|
4261
4491
|
`;
|
|
4262
4492
|
/**
|
|
4263
4493
|
* Inject widget styles into the document head
|
|
@@ -4280,6 +4510,7 @@ class CustomViewsWidget {
|
|
|
4280
4510
|
_hasVisibleConfig = false;
|
|
4281
4511
|
pageToggleIds = new Set();
|
|
4282
4512
|
pageTabIds = new Set();
|
|
4513
|
+
currentTab = 'customize';
|
|
4283
4514
|
// Modal state
|
|
4284
4515
|
stateModal = null;
|
|
4285
4516
|
constructor(options) {
|
|
@@ -4429,10 +4660,20 @@ class CustomViewsWidget {
|
|
|
4429
4660
|
<div>
|
|
4430
4661
|
<p class="cv-toggle-title">${toggle.label || toggle.id}</p>
|
|
4431
4662
|
</div>
|
|
4432
|
-
<
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4663
|
+
<div class="cv-toggle-radios">
|
|
4664
|
+
<label class="cv-radio-label" title="Hide">
|
|
4665
|
+
<input class="cv-toggle-input" type="radio" name="cv-toggle-${toggle.id}" value="hide" data-toggle="${toggle.id}"/>
|
|
4666
|
+
<span>Hide</span>
|
|
4667
|
+
</label>
|
|
4668
|
+
<label class="cv-radio-label" title="Peek">
|
|
4669
|
+
<input class="cv-toggle-input" type="radio" name="cv-toggle-${toggle.id}" value="peek" data-toggle="${toggle.id}"/>
|
|
4670
|
+
<span>Peek</span>
|
|
4671
|
+
</label>
|
|
4672
|
+
<label class="cv-radio-label" title="Show">
|
|
4673
|
+
<input class="cv-toggle-input" type="radio" name="cv-toggle-${toggle.id}" value="show" data-toggle="${toggle.id}"/>
|
|
4674
|
+
<span>Show</span>
|
|
4675
|
+
</label>
|
|
4676
|
+
</div>
|
|
4436
4677
|
</div>
|
|
4437
4678
|
</div>
|
|
4438
4679
|
`).join('');
|
|
@@ -4455,12 +4696,12 @@ class CustomViewsWidget {
|
|
|
4455
4696
|
</div>
|
|
4456
4697
|
<div class="cv-tabgroup-info">
|
|
4457
4698
|
<div class="cv-tabgroup-title-container">
|
|
4458
|
-
<p class="cv-tabgroup-title">
|
|
4699
|
+
<p class="cv-tabgroup-title">Show only the selected tab</p>
|
|
4459
4700
|
</div>
|
|
4460
|
-
<p class="cv-tabgroup-description">
|
|
4701
|
+
<p class="cv-tabgroup-description">Hide the navigation headers</p>
|
|
4461
4702
|
</div>
|
|
4462
4703
|
<label class="cv-toggle-switch cv-nav-toggle">
|
|
4463
|
-
<input class="cv-nav-pref-input" type="checkbox" ${initialNavsVisible ? '
|
|
4704
|
+
<input class="cv-nav-pref-input" type="checkbox" ${initialNavsVisible ? '' : 'checked'} aria-label="Show only the selected tab" />
|
|
4464
4705
|
<span class="cv-switch-bg"></span>
|
|
4465
4706
|
<span class="cv-switch-knob"></span>
|
|
4466
4707
|
</label>
|
|
@@ -4496,37 +4737,64 @@ class CustomViewsWidget {
|
|
|
4496
4737
|
<main class="cv-modal-main">
|
|
4497
4738
|
${this.options.description ? `<p class="cv-modal-description">${this.options.description}</p>` : ''}
|
|
4498
4739
|
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
<
|
|
4502
|
-
|
|
4503
|
-
|
|
4740
|
+
<div class="cv-modal-tabs">
|
|
4741
|
+
<button class="cv-modal-tab ${this.currentTab === 'customize' ? 'active' : ''}" data-tab="customize">Customize</button>
|
|
4742
|
+
<button class="cv-modal-tab ${this.currentTab === 'share' ? 'active' : ''}" data-tab="share">Share</button>
|
|
4743
|
+
</div>
|
|
4744
|
+
|
|
4745
|
+
<div class="cv-tab-content ${this.currentTab === 'customize' ? 'active' : ''}" data-content="customize">
|
|
4746
|
+
${visibleToggles.length ? `
|
|
4747
|
+
<div class="cv-content-section">
|
|
4748
|
+
<div class="cv-section-heading">Toggles</div>
|
|
4749
|
+
<div class="cv-toggles-container">
|
|
4750
|
+
${toggleControlsHtml}
|
|
4751
|
+
</div>
|
|
4752
|
+
</div>
|
|
4753
|
+
` : ''}
|
|
4754
|
+
|
|
4755
|
+
${this.options.showTabGroups && tabGroups && tabGroups.length > 0 ? `
|
|
4756
|
+
<div class="cv-content-section">
|
|
4757
|
+
<div class="cv-section-heading">Tab Groups</div>
|
|
4758
|
+
<div class="cv-tabgroups-container">
|
|
4759
|
+
${tabGroupControlsHTML}
|
|
4760
|
+
</div>
|
|
4504
4761
|
</div>
|
|
4762
|
+
` : ''}
|
|
4505
4763
|
</div>
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4764
|
+
|
|
4765
|
+
<div class="cv-tab-content ${this.currentTab === 'share' ? 'active' : ''}" data-content="share">
|
|
4766
|
+
<div class="cv-share-content">
|
|
4767
|
+
<div class="cv-share-instruction">
|
|
4768
|
+
Create a shareable link for your current customization, or select specific parts of the page to share.
|
|
4769
|
+
</div>
|
|
4770
|
+
|
|
4771
|
+
<button class="cv-share-action-btn primary cv-start-share-btn">
|
|
4772
|
+
<span class="cv-btn-icon">${getShareIcon()}</span>
|
|
4773
|
+
<span>Select elements to share</span>
|
|
4774
|
+
</button>
|
|
4775
|
+
|
|
4776
|
+
<button class="cv-share-action-btn cv-copy-url-btn">
|
|
4777
|
+
<span class="cv-btn-icon">${getCopyIcon()}</span>
|
|
4778
|
+
<span>Copy Shareable URL of Settings</span>
|
|
4779
|
+
</button>
|
|
4513
4780
|
</div>
|
|
4514
4781
|
</div>
|
|
4515
|
-
` : ''}
|
|
4516
4782
|
</main>
|
|
4517
4783
|
|
|
4518
4784
|
<footer class="cv-modal-footer">
|
|
4519
4785
|
${this.options.showReset ? `
|
|
4520
|
-
<button class="cv-reset-btn">
|
|
4786
|
+
<button class="cv-reset-btn" title="Reset to Default">
|
|
4521
4787
|
<span class="cv-reset-btn-icon">${getResetIcon()}</span>
|
|
4522
|
-
<span>Reset
|
|
4523
|
-
</button>
|
|
4524
|
-
` : ''}
|
|
4525
|
-
<button class="cv-share-btn">
|
|
4526
|
-
<span>Copy Shareable URL</span>
|
|
4527
|
-
<span class="cv-share-btn-icon">${getCopyIcon()}</span>
|
|
4788
|
+
<span>Reset</span>
|
|
4528
4789
|
</button>
|
|
4790
|
+
` : '<div></div>'}
|
|
4791
|
+
|
|
4792
|
+
<a href="https://github.com/customviews-js/customviews" target="_blank" class="cv-footer-link">
|
|
4793
|
+
${getGitHubIcon()}
|
|
4794
|
+
<span>View on GitHub</span>
|
|
4795
|
+
</a>
|
|
4529
4796
|
|
|
4797
|
+
<button class="cv-done-btn">Done</button>
|
|
4530
4798
|
</footer>
|
|
4531
4799
|
</div>
|
|
4532
4800
|
`;
|
|
@@ -4547,20 +4815,6 @@ class CustomViewsWidget {
|
|
|
4547
4815
|
this.closeModal();
|
|
4548
4816
|
return;
|
|
4549
4817
|
}
|
|
4550
|
-
// Copy URL button
|
|
4551
|
-
if (target.closest('.cv-share-btn')) {
|
|
4552
|
-
this.copyShareableURL();
|
|
4553
|
-
const copyUrlBtn = target.closest('.cv-share-btn');
|
|
4554
|
-
const iconContainer = copyUrlBtn?.querySelector('.cv-share-btn-icon');
|
|
4555
|
-
if (iconContainer) {
|
|
4556
|
-
const originalIcon = iconContainer.innerHTML;
|
|
4557
|
-
iconContainer.innerHTML = getTickIcon();
|
|
4558
|
-
setTimeout(() => {
|
|
4559
|
-
iconContainer.innerHTML = originalIcon;
|
|
4560
|
-
}, 3000);
|
|
4561
|
-
}
|
|
4562
|
-
return;
|
|
4563
|
-
}
|
|
4564
4818
|
// Reset to default button
|
|
4565
4819
|
if (target.closest('.cv-reset-btn')) {
|
|
4566
4820
|
const resetBtn = target.closest('.cv-reset-btn');
|
|
@@ -4577,6 +4831,11 @@ class CustomViewsWidget {
|
|
|
4577
4831
|
}, 600);
|
|
4578
4832
|
return;
|
|
4579
4833
|
}
|
|
4834
|
+
// Done button
|
|
4835
|
+
if (target.closest('.cv-done-btn')) {
|
|
4836
|
+
this.closeModal();
|
|
4837
|
+
return;
|
|
4838
|
+
}
|
|
4580
4839
|
// Overlay click to close
|
|
4581
4840
|
if (e.target === this.stateModal) {
|
|
4582
4841
|
this.closeModal();
|
|
@@ -4615,10 +4874,11 @@ class CustomViewsWidget {
|
|
|
4615
4874
|
if (groupId && tabId) {
|
|
4616
4875
|
const currentTabs = this.core.getCurrentActiveTabs();
|
|
4617
4876
|
currentTabs[groupId] = tabId;
|
|
4618
|
-
const
|
|
4877
|
+
const currentState = this.core.getCurrentState();
|
|
4619
4878
|
const newState = {
|
|
4620
|
-
|
|
4621
|
-
|
|
4879
|
+
shownToggles: currentState.shownToggles || [],
|
|
4880
|
+
peekToggles: currentState.peekToggles || [], // Preserve peek state, fallback to empty array
|
|
4881
|
+
tabs: currentTabs,
|
|
4622
4882
|
};
|
|
4623
4883
|
this.core.applyState(newState, { source: 'widget' });
|
|
4624
4884
|
}
|
|
@@ -4637,10 +4897,10 @@ class CustomViewsWidget {
|
|
|
4637
4897
|
navIcon.innerHTML = isVisible ? getNavHeadingOnIcon() : getNavHeadingOffIcon();
|
|
4638
4898
|
}
|
|
4639
4899
|
};
|
|
4640
|
-
navHeaderCard.addEventListener('mouseenter', () => updateIcon(tabNavToggle.checked, true));
|
|
4641
|
-
navHeaderCard.addEventListener('mouseleave', () => updateIcon(tabNavToggle.checked, false));
|
|
4900
|
+
navHeaderCard.addEventListener('mouseenter', () => updateIcon(!tabNavToggle.checked, true));
|
|
4901
|
+
navHeaderCard.addEventListener('mouseleave', () => updateIcon(!tabNavToggle.checked, false));
|
|
4642
4902
|
tabNavToggle.addEventListener('change', () => {
|
|
4643
|
-
const visible = tabNavToggle.checked;
|
|
4903
|
+
const visible = !tabNavToggle.checked;
|
|
4644
4904
|
updateIcon(visible, false);
|
|
4645
4905
|
this.core.persistTabNavVisibility(visible);
|
|
4646
4906
|
try {
|
|
@@ -4651,6 +4911,48 @@ class CustomViewsWidget {
|
|
|
4651
4911
|
}
|
|
4652
4912
|
});
|
|
4653
4913
|
}
|
|
4914
|
+
// Tab switching
|
|
4915
|
+
const tabs = this.stateModal.querySelectorAll('.cv-modal-tab');
|
|
4916
|
+
tabs.forEach(tab => {
|
|
4917
|
+
tab.addEventListener('click', () => {
|
|
4918
|
+
const tabId = tab.dataset.tab;
|
|
4919
|
+
if (tabId === 'customize' || tabId === 'share') {
|
|
4920
|
+
this.currentTab = tabId;
|
|
4921
|
+
// Update UI without full re-render
|
|
4922
|
+
tabs.forEach(t => t.classList.remove('active'));
|
|
4923
|
+
tab.classList.add('active');
|
|
4924
|
+
const contents = this.stateModal?.querySelectorAll('.cv-tab-content');
|
|
4925
|
+
contents?.forEach(c => {
|
|
4926
|
+
c.classList.remove('active');
|
|
4927
|
+
if (c.dataset.content === tabId) {
|
|
4928
|
+
c.classList.add('active');
|
|
4929
|
+
}
|
|
4930
|
+
});
|
|
4931
|
+
}
|
|
4932
|
+
});
|
|
4933
|
+
});
|
|
4934
|
+
// Share buttons (inside content)
|
|
4935
|
+
const startShareBtn = this.stateModal.querySelector('.cv-start-share-btn');
|
|
4936
|
+
if (startShareBtn) {
|
|
4937
|
+
startShareBtn.addEventListener('click', () => {
|
|
4938
|
+
this.closeModal();
|
|
4939
|
+
this.core.toggleShareMode();
|
|
4940
|
+
});
|
|
4941
|
+
}
|
|
4942
|
+
const copyUrlBtn = this.stateModal.querySelector('.cv-copy-url-btn');
|
|
4943
|
+
if (copyUrlBtn) {
|
|
4944
|
+
copyUrlBtn.addEventListener('click', () => {
|
|
4945
|
+
this.copyShareableURL();
|
|
4946
|
+
const iconContainer = copyUrlBtn.querySelector('.cv-btn-icon');
|
|
4947
|
+
if (iconContainer) {
|
|
4948
|
+
const originalIcon = iconContainer.innerHTML;
|
|
4949
|
+
iconContainer.innerHTML = getTickIcon();
|
|
4950
|
+
setTimeout(() => {
|
|
4951
|
+
iconContainer.innerHTML = originalIcon;
|
|
4952
|
+
}, 2000);
|
|
4953
|
+
}
|
|
4954
|
+
});
|
|
4955
|
+
}
|
|
4654
4956
|
}
|
|
4655
4957
|
/**
|
|
4656
4958
|
* Apply theme class to the modal overlay based on options
|
|
@@ -4674,25 +4976,33 @@ class CustomViewsWidget {
|
|
|
4674
4976
|
}
|
|
4675
4977
|
// Collect toggle values
|
|
4676
4978
|
const toggles = [];
|
|
4677
|
-
const
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
tabs[groupId] = select.value;
|
|
4979
|
+
const peekToggles = [];
|
|
4980
|
+
// Get all radio inputs
|
|
4981
|
+
const radios = this.stateModal.querySelectorAll('input[type="radio"]:checked');
|
|
4982
|
+
radios.forEach(radio => {
|
|
4983
|
+
const input = radio;
|
|
4984
|
+
const toggleId = input.getAttribute('data-toggle');
|
|
4985
|
+
if (toggleId) {
|
|
4986
|
+
if (input.value === 'show') {
|
|
4987
|
+
toggles.push(toggleId);
|
|
4988
|
+
}
|
|
4989
|
+
else if (input.value === 'peek') {
|
|
4990
|
+
peekToggles.push(toggleId);
|
|
4991
|
+
}
|
|
4691
4992
|
}
|
|
4692
4993
|
});
|
|
4693
|
-
const result = { toggles };
|
|
4694
|
-
|
|
4695
|
-
|
|
4994
|
+
const result = { shownToggles: toggles, peekToggles };
|
|
4995
|
+
// Get active tabs from selects
|
|
4996
|
+
const selects = this.stateModal.querySelectorAll('select[data-group-id]');
|
|
4997
|
+
if (selects.length > 0) {
|
|
4998
|
+
result.tabs = {};
|
|
4999
|
+
selects.forEach(select => {
|
|
5000
|
+
const el = select;
|
|
5001
|
+
const groupId = el.getAttribute('data-group-id');
|
|
5002
|
+
if (groupId) {
|
|
5003
|
+
result.tabs[groupId] = el.value;
|
|
5004
|
+
}
|
|
5005
|
+
});
|
|
4696
5006
|
}
|
|
4697
5007
|
return result;
|
|
4698
5008
|
}
|
|
@@ -4712,18 +5022,29 @@ class CustomViewsWidget {
|
|
|
4712
5022
|
loadCurrentStateIntoForm() {
|
|
4713
5023
|
if (!this.stateModal)
|
|
4714
5024
|
return;
|
|
4715
|
-
//
|
|
4716
|
-
const
|
|
4717
|
-
|
|
5025
|
+
// We need complete state for both shown and peek toggles
|
|
5026
|
+
const currentState = this.core.getCurrentState();
|
|
5027
|
+
const currentToggles = currentState.shownToggles || [];
|
|
5028
|
+
const currentPeekToggles = currentState.peekToggles || [];
|
|
5029
|
+
// Reset all inputs first (optional, but good for clarity)
|
|
4718
5030
|
const allToggleInputs = this.stateModal.querySelectorAll('.cv-toggle-input');
|
|
4719
|
-
|
|
4720
|
-
|
|
5031
|
+
// Identify unique toggles present in the modal
|
|
5032
|
+
const uniqueToggles = new Set();
|
|
5033
|
+
allToggleInputs.forEach(input => {
|
|
5034
|
+
if (input.dataset.toggle)
|
|
5035
|
+
uniqueToggles.add(input.dataset.toggle);
|
|
4721
5036
|
});
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
5037
|
+
uniqueToggles.forEach(toggleId => {
|
|
5038
|
+
let valueToSelect = 'hide';
|
|
5039
|
+
if (currentToggles.includes(toggleId)) {
|
|
5040
|
+
valueToSelect = 'show';
|
|
5041
|
+
}
|
|
5042
|
+
else if (currentPeekToggles.includes(toggleId)) {
|
|
5043
|
+
valueToSelect = 'peek';
|
|
5044
|
+
}
|
|
5045
|
+
const input = this.stateModal.querySelector(`input[name="cv-toggle-${toggleId}"][value="${valueToSelect}"]`);
|
|
5046
|
+
if (input) {
|
|
5047
|
+
input.checked = true;
|
|
4727
5048
|
}
|
|
4728
5049
|
});
|
|
4729
5050
|
// Load tab group selections
|
|
@@ -4746,7 +5067,7 @@ class CustomViewsWidget {
|
|
|
4746
5067
|
const tabNavToggle = this.stateModal.querySelector('.cv-nav-pref-input');
|
|
4747
5068
|
const navIcon = this.stateModal?.querySelector('#cv-nav-icon');
|
|
4748
5069
|
if (tabNavToggle) {
|
|
4749
|
-
tabNavToggle.checked = navPref;
|
|
5070
|
+
tabNavToggle.checked = !navPref;
|
|
4750
5071
|
// Ensure UI matches actual visibility
|
|
4751
5072
|
TabManager.setNavsVisibility(document.body, navPref);
|
|
4752
5073
|
// Update the nav icon to reflect the current state
|