@customviews-js/customviews 1.1.11 → 1.2.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 +360 -196
- package/dist/custom-views.core.cjs.js.map +1 -1
- package/dist/custom-views.core.esm.js +360 -196
- package/dist/custom-views.core.esm.js.map +1 -1
- package/dist/custom-views.esm.js +360 -196
- package/dist/custom-views.esm.js.map +1 -1
- package/dist/custom-views.js +360 -196
- 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 +18 -1
- package/dist/types/core/core.d.ts.map +1 -1
- package/dist/types/core/tab-manager.d.ts +6 -6
- package/dist/types/core/tab-manager.d.ts.map +1 -1
- package/dist/types/core/toggle-manager.d.ts +9 -4
- package/dist/types/core/toggle-manager.d.ts.map +1 -1
- package/dist/types/core/widget.d.ts +20 -11
- package/dist/types/core/widget.d.ts.map +1 -1
- package/dist/types/lib/custom-views.d.ts +1 -1
- package/dist/types/lib/custom-views.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 +15 -2
- package/dist/types/types/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/custom-views.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @customviews-js/customviews v1.
|
|
2
|
+
* @customviews-js/customviews v1.2.0
|
|
3
3
|
* (c) 2025 Chan Ger Teck
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -420,7 +420,7 @@
|
|
|
420
420
|
}
|
|
421
421
|
|
|
422
422
|
// Constants for selectors
|
|
423
|
-
const TABGROUP_SELECTOR = 'cv-tabgroup';
|
|
423
|
+
const TABGROUP_SELECTOR$1 = 'cv-tabgroup';
|
|
424
424
|
const TAB_SELECTOR = 'cv-tab';
|
|
425
425
|
const NAV_AUTO_SELECTOR = 'cv-tabgroup[nav="auto"], cv-tabgroup:not([nav])';
|
|
426
426
|
const NAV_CONTAINER_CLASS = 'cv-tabs-nav';
|
|
@@ -436,11 +436,9 @@
|
|
|
436
436
|
return trimmedIds;
|
|
437
437
|
}
|
|
438
438
|
/**
|
|
439
|
-
* Apply tab selections to
|
|
439
|
+
* Apply tab selections to a given list of tab group elements
|
|
440
440
|
*/
|
|
441
|
-
static applyTabSelections(
|
|
442
|
-
// Find all cv-tabgroup elements
|
|
443
|
-
const tabGroups = rootEl.querySelectorAll(TABGROUP_SELECTOR);
|
|
441
|
+
static applyTabSelections(tabGroups, tabs, cfgGroups) {
|
|
444
442
|
tabGroups.forEach((groupEl) => {
|
|
445
443
|
const groupId = groupEl.getAttribute('id');
|
|
446
444
|
// Determine the active tab for this group
|
|
@@ -545,10 +543,18 @@
|
|
|
545
543
|
/**
|
|
546
544
|
* Build navigation for tab groups (one-time setup)
|
|
547
545
|
*/
|
|
548
|
-
static buildNavs(
|
|
549
|
-
|
|
550
|
-
const tabGroups = rootEl.querySelectorAll(NAV_AUTO_SELECTOR);
|
|
546
|
+
static buildNavs(tabGroups, cfgGroups, onTabClick, onTabDoubleClick) {
|
|
547
|
+
const rootEl = document.body; // Needed for NAV_HIDE_ROOT_CLASS check
|
|
551
548
|
tabGroups.forEach((groupEl) => {
|
|
549
|
+
// Prevent re-initialization
|
|
550
|
+
if (groupEl.hasAttribute('data-cv-initialized')) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
groupEl.setAttribute('data-cv-initialized', 'true');
|
|
554
|
+
// Filter to only build for groups with nav="auto" or no nav attribute
|
|
555
|
+
if (!groupEl.matches(NAV_AUTO_SELECTOR)) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
552
558
|
const groupId = groupEl.getAttribute('id') || null;
|
|
553
559
|
// Note: groupId can be null for standalone tabgroups
|
|
554
560
|
// These won't sync with other groups or persist state
|
|
@@ -752,8 +758,7 @@
|
|
|
752
758
|
/**
|
|
753
759
|
* Update active states for all tab groups based on current state
|
|
754
760
|
*/
|
|
755
|
-
static updateAllNavActiveStates(
|
|
756
|
-
const tabGroups = rootEl.querySelectorAll(TABGROUP_SELECTOR);
|
|
761
|
+
static updateAllNavActiveStates(tabGroups, tabs, cfgGroups) {
|
|
757
762
|
tabGroups.forEach((groupEl) => {
|
|
758
763
|
const groupId = groupEl.getAttribute('id');
|
|
759
764
|
if (!groupId)
|
|
@@ -816,7 +821,7 @@
|
|
|
816
821
|
*/
|
|
817
822
|
static getTabgroupsWithId(rootEl, sourceGroupId, tabId) {
|
|
818
823
|
const syncedGroupEls = [];
|
|
819
|
-
const allGroupEls = Array.from(rootEl.querySelectorAll(`${TABGROUP_SELECTOR}[id="${sourceGroupId}"]`));
|
|
824
|
+
const allGroupEls = Array.from(rootEl.querySelectorAll(`${TABGROUP_SELECTOR$1}[id="${sourceGroupId}"]`));
|
|
820
825
|
allGroupEls.forEach((targetGroupEl) => {
|
|
821
826
|
// Only sync if target group actually contains this tab
|
|
822
827
|
if (this.groupHasTab(targetGroupEl, tabId)) {
|
|
@@ -828,9 +833,8 @@
|
|
|
828
833
|
/**
|
|
829
834
|
* Update pin icon visibility for all tab groups based on current state.
|
|
830
835
|
* Shows pin icon for tabs that are in the persisted state (i.e., have been double-clicked).
|
|
831
|
-
|
|
832
|
-
static updatePinIcons(
|
|
833
|
-
const tabGroups = rootEl.querySelectorAll(TABGROUP_SELECTOR);
|
|
836
|
+
*/
|
|
837
|
+
static updatePinIcons(tabGroups, tabs) {
|
|
834
838
|
tabGroups.forEach((groupEl) => {
|
|
835
839
|
const groupId = groupEl.getAttribute('id');
|
|
836
840
|
if (!groupId)
|
|
@@ -980,33 +984,31 @@
|
|
|
980
984
|
}
|
|
981
985
|
}
|
|
982
986
|
|
|
983
|
-
// Constants for selectors
|
|
984
|
-
const TOGGLE_DATA_SELECTOR = "[data-cv-toggle], [data-customviews-toggle]";
|
|
985
|
-
const TOGGLE_ELEMENT_SELECTOR = "cv-toggle";
|
|
986
|
-
const TOGGLE_SELECTOR = `${TOGGLE_DATA_SELECTOR}, ${TOGGLE_ELEMENT_SELECTOR}`;
|
|
987
987
|
/**
|
|
988
988
|
* ToggleManager handles discovery, visibility, and asset rendering for toggle elements
|
|
989
989
|
*/
|
|
990
990
|
class ToggleManager {
|
|
991
991
|
/**
|
|
992
|
-
* Apply toggle visibility to
|
|
992
|
+
* Apply toggle visibility to a given list of toggle elements
|
|
993
993
|
*/
|
|
994
|
-
static applyToggles(
|
|
995
|
-
|
|
994
|
+
static applyToggles(elements, activeToggles) {
|
|
995
|
+
elements.forEach(el => {
|
|
996
996
|
const categories = this.getToggleCategories(el);
|
|
997
997
|
const shouldShow = categories.some(cat => activeToggles.includes(cat));
|
|
998
998
|
this.applyToggleVisibility(el, shouldShow);
|
|
999
999
|
});
|
|
1000
1000
|
}
|
|
1001
1001
|
/**
|
|
1002
|
-
* Render assets into toggle elements that are currently visible
|
|
1002
|
+
* Render assets into a given list of toggle elements that are currently visible
|
|
1003
1003
|
*/
|
|
1004
|
-
static renderAssets(
|
|
1005
|
-
|
|
1004
|
+
static renderAssets(elements, activeToggles, assetsManager) {
|
|
1005
|
+
elements.forEach(el => {
|
|
1006
1006
|
const categories = this.getToggleCategories(el);
|
|
1007
1007
|
const toggleId = this.getToggleId(el);
|
|
1008
|
-
|
|
1008
|
+
const isRendered = el.dataset.cvRendered === 'true';
|
|
1009
|
+
if (toggleId && !isRendered && categories.some(cat => activeToggles.includes(cat))) {
|
|
1009
1010
|
renderAssetInto(el, toggleId, assetsManager);
|
|
1011
|
+
el.dataset.cvRendered = 'true';
|
|
1010
1012
|
}
|
|
1011
1013
|
});
|
|
1012
1014
|
}
|
|
@@ -1042,6 +1044,21 @@
|
|
|
1042
1044
|
el.classList.remove('cv-visible');
|
|
1043
1045
|
}
|
|
1044
1046
|
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Scans a given DOM subtree for toggle elements and initializes them.
|
|
1049
|
+
* This includes applying visibility and rendering assets.
|
|
1050
|
+
*/
|
|
1051
|
+
static initializeToggles(root, activeToggles, assetsManager) {
|
|
1052
|
+
const elements = [];
|
|
1053
|
+
if (root.matches('[data-cv-toggle], [data-customviews-toggle], cv-toggle')) {
|
|
1054
|
+
elements.push(root);
|
|
1055
|
+
}
|
|
1056
|
+
root.querySelectorAll('[data-cv-toggle], [data-customviews-toggle], cv-toggle').forEach(el => elements.push(el));
|
|
1057
|
+
if (elements.length === 0)
|
|
1058
|
+
return;
|
|
1059
|
+
this.applyToggles(elements, activeToggles);
|
|
1060
|
+
this.renderAssets(elements, activeToggles, assetsManager);
|
|
1061
|
+
}
|
|
1045
1062
|
}
|
|
1046
1063
|
|
|
1047
1064
|
// src/utils/scroll-manager.ts
|
|
@@ -1365,11 +1382,18 @@ ${TAB_STYLES}
|
|
|
1365
1382
|
document.head.appendChild(style);
|
|
1366
1383
|
}
|
|
1367
1384
|
|
|
1385
|
+
const TOGGLE_SELECTOR = "[data-cv-toggle], [data-customviews-toggle], cv-toggle";
|
|
1386
|
+
const TABGROUP_SELECTOR = 'cv-tabgroup';
|
|
1368
1387
|
class CustomViewsCore {
|
|
1369
1388
|
rootEl;
|
|
1370
1389
|
assetsManager;
|
|
1371
1390
|
persistenceManager;
|
|
1372
1391
|
visibilityManager;
|
|
1392
|
+
observer = null;
|
|
1393
|
+
componentRegistry = {
|
|
1394
|
+
toggles: new Set(),
|
|
1395
|
+
tabGroups: new Set(),
|
|
1396
|
+
};
|
|
1373
1397
|
config;
|
|
1374
1398
|
stateChangeListeners = [];
|
|
1375
1399
|
showUrlEnabled;
|
|
@@ -1383,6 +1407,57 @@ ${TAB_STYLES}
|
|
|
1383
1407
|
this.showUrlEnabled = opt.showUrl ?? false;
|
|
1384
1408
|
this.lastAppliedState = this.cloneState(this.getComputedDefaultState());
|
|
1385
1409
|
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Scan the given element for toggles and tab groups, register them
|
|
1412
|
+
* Returns true if new components were found
|
|
1413
|
+
*/
|
|
1414
|
+
scan(element) {
|
|
1415
|
+
let newComponentsFound = false;
|
|
1416
|
+
// Scan for toggles
|
|
1417
|
+
const toggles = Array.from(element.querySelectorAll(TOGGLE_SELECTOR));
|
|
1418
|
+
if (element.matches(TOGGLE_SELECTOR)) {
|
|
1419
|
+
toggles.unshift(element);
|
|
1420
|
+
}
|
|
1421
|
+
toggles.forEach((toggle) => {
|
|
1422
|
+
if (!this.componentRegistry.toggles.has(toggle)) {
|
|
1423
|
+
this.componentRegistry.toggles.add(toggle);
|
|
1424
|
+
newComponentsFound = true;
|
|
1425
|
+
}
|
|
1426
|
+
});
|
|
1427
|
+
// Scan for tab groups
|
|
1428
|
+
const tabGroups = Array.from(element.querySelectorAll(TABGROUP_SELECTOR));
|
|
1429
|
+
if (element.matches(TABGROUP_SELECTOR)) {
|
|
1430
|
+
tabGroups.unshift(element);
|
|
1431
|
+
}
|
|
1432
|
+
tabGroups.forEach((tabGroup) => {
|
|
1433
|
+
if (!this.componentRegistry.tabGroups.has(tabGroup)) {
|
|
1434
|
+
this.componentRegistry.tabGroups.add(tabGroup);
|
|
1435
|
+
newComponentsFound = true;
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
return newComponentsFound;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Unscan the given element for toggles and tab groups, de-register them
|
|
1442
|
+
*/
|
|
1443
|
+
unscan(element) {
|
|
1444
|
+
// Unscan for toggles
|
|
1445
|
+
const toggles = Array.from(element.querySelectorAll(TOGGLE_SELECTOR));
|
|
1446
|
+
if (element.matches(TOGGLE_SELECTOR)) {
|
|
1447
|
+
toggles.unshift(element);
|
|
1448
|
+
}
|
|
1449
|
+
toggles.forEach((toggle) => {
|
|
1450
|
+
this.componentRegistry.toggles.delete(toggle);
|
|
1451
|
+
});
|
|
1452
|
+
// Unscan for tab groups
|
|
1453
|
+
const tabGroups = Array.from(element.querySelectorAll(TABGROUP_SELECTOR));
|
|
1454
|
+
if (element.matches(TABGROUP_SELECTOR)) {
|
|
1455
|
+
tabGroups.unshift(element);
|
|
1456
|
+
}
|
|
1457
|
+
tabGroups.forEach((tabGroup) => {
|
|
1458
|
+
this.componentRegistry.tabGroups.delete(tabGroup);
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1386
1461
|
getConfig() {
|
|
1387
1462
|
return this.config;
|
|
1388
1463
|
}
|
|
@@ -1417,7 +1492,7 @@ ${TAB_STYLES}
|
|
|
1417
1492
|
});
|
|
1418
1493
|
}
|
|
1419
1494
|
const computedState = {
|
|
1420
|
-
toggles:
|
|
1495
|
+
toggles: this.config.toggles?.map(t => t.id) || [],
|
|
1421
1496
|
tabs
|
|
1422
1497
|
};
|
|
1423
1498
|
return computedState;
|
|
@@ -1456,8 +1531,25 @@ ${TAB_STYLES}
|
|
|
1456
1531
|
// Inject styles, setup listeners and call rendering logic
|
|
1457
1532
|
async init() {
|
|
1458
1533
|
injectCoreStyles();
|
|
1459
|
-
|
|
1460
|
-
|
|
1534
|
+
this.scan(this.rootEl);
|
|
1535
|
+
// Initialize all components found on initial scan
|
|
1536
|
+
this.initializeNewComponents();
|
|
1537
|
+
// Apply stored nav visibility preference on page load
|
|
1538
|
+
const navPref = this.persistenceManager.getPersistedTabNavVisibility();
|
|
1539
|
+
if (navPref !== null) {
|
|
1540
|
+
TabManager.setNavsVisibility(this.rootEl, navPref);
|
|
1541
|
+
}
|
|
1542
|
+
// For session history, clicks on back/forward button
|
|
1543
|
+
window.addEventListener("popstate", () => {
|
|
1544
|
+
this.loadAndCallApplyState();
|
|
1545
|
+
});
|
|
1546
|
+
this.loadAndCallApplyState();
|
|
1547
|
+
this.initObserver();
|
|
1548
|
+
}
|
|
1549
|
+
initializeNewComponents() {
|
|
1550
|
+
// Build navigation for any newly added tab groups.
|
|
1551
|
+
// The `data-cv-initialized` attribute in `buildNavs` prevents re-initialization.
|
|
1552
|
+
TabManager.buildNavs(Array.from(this.componentRegistry.tabGroups), this.config.tabGroups,
|
|
1461
1553
|
// Single click: update clicked group only (local, no persistence)
|
|
1462
1554
|
(groupId, tabId, groupEl) => {
|
|
1463
1555
|
this.setActiveTab(groupId, tabId, groupEl);
|
|
@@ -1480,16 +1572,46 @@ ${TAB_STYLES}
|
|
|
1480
1572
|
scrollAnchor: { element: anchorElement, top: initialTop }
|
|
1481
1573
|
});
|
|
1482
1574
|
});
|
|
1483
|
-
//
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1575
|
+
// Future components (e.g., toggles, widgets) can be initialized here
|
|
1576
|
+
}
|
|
1577
|
+
initObserver() {
|
|
1578
|
+
this.observer = new MutationObserver((mutations) => {
|
|
1579
|
+
let newComponentsFound = false;
|
|
1580
|
+
for (const mutation of mutations) {
|
|
1581
|
+
if (mutation.type === 'childList') {
|
|
1582
|
+
mutation.addedNodes.forEach((node) => {
|
|
1583
|
+
if (node instanceof Element) {
|
|
1584
|
+
// Scan the new node for components and add them to the registry
|
|
1585
|
+
if (this.scan(node)) {
|
|
1586
|
+
newComponentsFound = true;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
mutation.removedNodes.forEach((node) => {
|
|
1591
|
+
if (node instanceof Element) {
|
|
1592
|
+
// Unscan the removed node to cleanup the registry
|
|
1593
|
+
this.unscan(node);
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
if (newComponentsFound) {
|
|
1599
|
+
// Initialize navs for new components.
|
|
1600
|
+
this.initializeNewComponents();
|
|
1601
|
+
// Re-apply the last known state. renderState will handle disconnecting
|
|
1602
|
+
// the observer to prevent infinite loops.
|
|
1603
|
+
if (this.lastAppliedState) {
|
|
1604
|
+
this.renderState(this.lastAppliedState);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1491
1607
|
});
|
|
1492
|
-
|
|
1608
|
+
// Observe only the root element to avoid performance issues on large pages.
|
|
1609
|
+
if (this.rootEl) {
|
|
1610
|
+
this.observer.observe(this.rootEl, {
|
|
1611
|
+
childList: true,
|
|
1612
|
+
subtree: true,
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1493
1615
|
}
|
|
1494
1616
|
// Priority: URL state > persisted state > config default > computed default
|
|
1495
1617
|
// Also filters using the visibility manager to persist selection
|
|
@@ -1543,23 +1665,34 @@ ${TAB_STYLES}
|
|
|
1543
1665
|
ScrollManager.handleScrollAnchor(options.scrollAnchor);
|
|
1544
1666
|
}
|
|
1545
1667
|
}
|
|
1546
|
-
/**
|
|
1668
|
+
/**
|
|
1669
|
+
* Renders state on components in ComponentRegistry
|
|
1670
|
+
* Applies the given state.
|
|
1671
|
+
* Disconnects the mutation observer during rendering to prevent loops
|
|
1672
|
+
**/
|
|
1547
1673
|
renderState(state) {
|
|
1674
|
+
this.observer?.disconnect();
|
|
1548
1675
|
this.lastAppliedState = this.cloneState(state);
|
|
1549
1676
|
const toggles = state?.toggles || [];
|
|
1550
1677
|
const finalToggles = this.visibilityManager.filterVisibleToggles(toggles);
|
|
1678
|
+
const toggleElements = Array.from(this.componentRegistry.toggles);
|
|
1679
|
+
const tabGroupElements = Array.from(this.componentRegistry.tabGroups);
|
|
1551
1680
|
// Apply toggle visibility
|
|
1552
|
-
ToggleManager.applyToggles(
|
|
1681
|
+
ToggleManager.applyToggles(toggleElements, finalToggles);
|
|
1553
1682
|
// Render assets into toggles
|
|
1554
|
-
ToggleManager.renderAssets(
|
|
1683
|
+
ToggleManager.renderAssets(toggleElements, finalToggles, this.assetsManager);
|
|
1555
1684
|
// Apply tab selections
|
|
1556
|
-
TabManager.applyTabSelections(
|
|
1685
|
+
TabManager.applyTabSelections(tabGroupElements, state.tabs || {}, this.config.tabGroups);
|
|
1557
1686
|
// Update nav active states (without rebuilding)
|
|
1558
|
-
TabManager.updateAllNavActiveStates(
|
|
1687
|
+
TabManager.updateAllNavActiveStates(tabGroupElements, state.tabs || {}, this.config.tabGroups);
|
|
1559
1688
|
// Update pin icons to show which tabs are persisted
|
|
1560
|
-
TabManager.updatePinIcons(
|
|
1689
|
+
TabManager.updatePinIcons(tabGroupElements, state.tabs || {});
|
|
1561
1690
|
// Notify state change listeners (like widgets)
|
|
1562
1691
|
this.notifyStateChangeListeners();
|
|
1692
|
+
this.observer?.observe(document.body, {
|
|
1693
|
+
childList: true,
|
|
1694
|
+
subtree: true,
|
|
1695
|
+
});
|
|
1563
1696
|
}
|
|
1564
1697
|
/**
|
|
1565
1698
|
* Reset to default state
|
|
@@ -1811,7 +1944,7 @@ ${TAB_STYLES}
|
|
|
1811
1944
|
else {
|
|
1812
1945
|
console.error("No config provided, using minimal default config");
|
|
1813
1946
|
// Create a minimal default config
|
|
1814
|
-
config = {
|
|
1947
|
+
config = { toggles: [], defaultState: {} };
|
|
1815
1948
|
}
|
|
1816
1949
|
const coreOptions = {
|
|
1817
1950
|
assetsManager,
|
|
@@ -2068,6 +2201,10 @@ ${TAB_STYLES}
|
|
|
2068
2201
|
animation: fadeIn 0.2s ease;
|
|
2069
2202
|
}
|
|
2070
2203
|
|
|
2204
|
+
.cv-widget-modal-overlay.cv-hidden {
|
|
2205
|
+
display: none;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2071
2208
|
@keyframes fadeIn {
|
|
2072
2209
|
from { opacity: 0; }
|
|
2073
2210
|
to { opacity: 1; }
|
|
@@ -2924,8 +3061,11 @@ ${TAB_STYLES}
|
|
|
2924
3061
|
container;
|
|
2925
3062
|
widgetIcon = null;
|
|
2926
3063
|
options;
|
|
3064
|
+
_hasVisibleConfig = false;
|
|
3065
|
+
pageToggleIds = new Set();
|
|
3066
|
+
pageTabIds = new Set();
|
|
2927
3067
|
// Modal state
|
|
2928
|
-
|
|
3068
|
+
stateModal = null;
|
|
2929
3069
|
constructor(options) {
|
|
2930
3070
|
this.core = options.core;
|
|
2931
3071
|
this.container = options.container || document.body;
|
|
@@ -2943,12 +3083,41 @@ ${TAB_STYLES}
|
|
|
2943
3083
|
welcomeMessage: options.welcomeMessage || 'This site is powered by Custom Views. Use the widget on the side (⚙) to customize your experience. Your preferences will be saved and can be shared via URL.<br><br>Learn more at <a href="https://github.com/customviews-js/customviews" target="_blank">customviews GitHub</a>.',
|
|
2944
3084
|
showTabGroups: options.showTabGroups ?? true
|
|
2945
3085
|
};
|
|
2946
|
-
//
|
|
3086
|
+
// Determine if there are any configurations to show
|
|
3087
|
+
const config = this.core.getConfig();
|
|
3088
|
+
const allToggles = config?.toggles || [];
|
|
3089
|
+
const visibleToggles = allToggles.filter(toggle => {
|
|
3090
|
+
if (toggle.isLocal) {
|
|
3091
|
+
return !!document.querySelector(`[data-cv-toggle="${toggle.id}"], [data-cv-toggle-group-id="${toggle.id}"]`);
|
|
3092
|
+
}
|
|
3093
|
+
return true;
|
|
3094
|
+
});
|
|
3095
|
+
const allTabGroups = this.core.getTabGroups() || [];
|
|
3096
|
+
const visibleTabGroups = allTabGroups.filter(group => {
|
|
3097
|
+
if (group.isLocal) {
|
|
3098
|
+
return !!document.querySelector(`cv-tabgroup[id="${group.id}"]`);
|
|
3099
|
+
}
|
|
3100
|
+
return true;
|
|
3101
|
+
});
|
|
3102
|
+
if (visibleToggles.length > 0 || (this.options.showTabGroups && visibleTabGroups.length > 0)) {
|
|
3103
|
+
this._hasVisibleConfig = true;
|
|
3104
|
+
}
|
|
3105
|
+
// Scan for page-declared local components and cache them
|
|
3106
|
+
// Do this on initialization to avoid querying DOM repeatedly
|
|
3107
|
+
const pageTogglesAttr = document.querySelector('[data-cv-page-local-toggles]')?.getAttribute('data-cv-page-local-toggles') || '';
|
|
3108
|
+
this.pageToggleIds = new Set(pageTogglesAttr.split(',').map(id => id.trim()).filter(id => id));
|
|
3109
|
+
const pageTabsAttr = document.querySelector('[data-cv-page-local-tabs]')?.getAttribute('data-cv-page-local-tabs') || '';
|
|
3110
|
+
this.pageTabIds = new Set(pageTabsAttr.split(',').map(id => id.trim()).filter(id => id));
|
|
2947
3111
|
}
|
|
2948
3112
|
/**
|
|
2949
|
-
* Render the widget
|
|
3113
|
+
* Render the widget modal icon
|
|
3114
|
+
*
|
|
3115
|
+
* Does not render if there are no visible toggles or tab groups.
|
|
2950
3116
|
*/
|
|
2951
|
-
|
|
3117
|
+
renderModalIcon() {
|
|
3118
|
+
if (!this._hasVisibleConfig) {
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
2952
3121
|
this.widgetIcon = this.createWidgetIcon();
|
|
2953
3122
|
this.attachEventListeners();
|
|
2954
3123
|
// Always append to body since it's a floating icon
|
|
@@ -2981,9 +3150,9 @@ ${TAB_STYLES}
|
|
|
2981
3150
|
this.widgetIcon = null;
|
|
2982
3151
|
}
|
|
2983
3152
|
// Clean up modal
|
|
2984
|
-
if (this.
|
|
2985
|
-
this.
|
|
2986
|
-
this.
|
|
3153
|
+
if (this.stateModal) {
|
|
3154
|
+
this.stateModal.remove();
|
|
3155
|
+
this.stateModal = null;
|
|
2987
3156
|
}
|
|
2988
3157
|
}
|
|
2989
3158
|
attachEventListeners() {
|
|
@@ -2996,49 +3165,71 @@ ${TAB_STYLES}
|
|
|
2996
3165
|
* Close the modal
|
|
2997
3166
|
*/
|
|
2998
3167
|
closeModal() {
|
|
2999
|
-
if (this.
|
|
3000
|
-
this.
|
|
3001
|
-
this.modal = null;
|
|
3168
|
+
if (this.stateModal) {
|
|
3169
|
+
this.stateModal.classList.add('cv-hidden');
|
|
3002
3170
|
}
|
|
3003
3171
|
}
|
|
3004
3172
|
/**
|
|
3005
3173
|
* Open the custom state creator
|
|
3006
3174
|
*/
|
|
3007
3175
|
openStateModal() {
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
this.
|
|
3176
|
+
if (!this.stateModal) {
|
|
3177
|
+
this._createStateModal();
|
|
3178
|
+
}
|
|
3179
|
+
this._updateStateModalContent();
|
|
3180
|
+
this.stateModal.classList.remove('cv-hidden');
|
|
3012
3181
|
}
|
|
3013
3182
|
/**
|
|
3014
|
-
* Create the custom state creator modal
|
|
3183
|
+
* Create the custom state creator modal shell and attach listeners
|
|
3015
3184
|
*/
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
this.
|
|
3019
|
-
this.modal = document.createElement('div');
|
|
3020
|
-
this.modal.className = 'cv-widget-modal-overlay';
|
|
3185
|
+
_createStateModal() {
|
|
3186
|
+
this.stateModal = document.createElement('div');
|
|
3187
|
+
this.stateModal.className = 'cv-widget-modal-overlay cv-hidden';
|
|
3021
3188
|
this.applyThemeToModal();
|
|
3022
|
-
|
|
3189
|
+
document.body.appendChild(this.stateModal);
|
|
3190
|
+
this._attachStateModalFrameEventListeners();
|
|
3191
|
+
}
|
|
3192
|
+
/**
|
|
3193
|
+
* Update the content of the state modal
|
|
3194
|
+
*/
|
|
3195
|
+
_updateStateModalContent() {
|
|
3196
|
+
if (!this.stateModal)
|
|
3197
|
+
return;
|
|
3198
|
+
const pageToggleIds = this.pageToggleIds;
|
|
3199
|
+
const pageTabIds = this.pageTabIds;
|
|
3200
|
+
// Get toggles from current configuration
|
|
3201
|
+
const config = this.core.getConfig();
|
|
3202
|
+
const allToggles = config?.toggles || [];
|
|
3203
|
+
// Filter toggles to only include global and visible/declared local toggles
|
|
3204
|
+
const visibleToggles = allToggles.filter(toggle => {
|
|
3205
|
+
if (toggle.isLocal) {
|
|
3206
|
+
return pageToggleIds.has(toggle.id) || !!document.querySelector(`[data-cv-toggle="${toggle.id}"], [data-cv-toggle-group-id="${toggle.id}"]`);
|
|
3207
|
+
}
|
|
3208
|
+
return true; // Keep global toggles
|
|
3209
|
+
});
|
|
3210
|
+
const toggleControlsHtml = visibleToggles.map(toggle => `
|
|
3023
3211
|
<div class="cv-toggle-card">
|
|
3024
3212
|
<div class="cv-toggle-content">
|
|
3025
3213
|
<div>
|
|
3026
|
-
<p class="cv-toggle-title">${
|
|
3214
|
+
<p class="cv-toggle-title">${toggle.label || toggle.id}</p>
|
|
3027
3215
|
</div>
|
|
3028
3216
|
<label class="cv-toggle-label">
|
|
3029
|
-
<input class="cv-toggle-input" type="checkbox" data-toggle="${toggle}"/>
|
|
3217
|
+
<input class="cv-toggle-input" type="checkbox" data-toggle="${toggle.id}"/>
|
|
3030
3218
|
<span class="cv-toggle-slider"></span>
|
|
3031
3219
|
</label>
|
|
3032
3220
|
</div>
|
|
3033
3221
|
</div>
|
|
3034
3222
|
`).join('');
|
|
3035
|
-
// Todo: Re-add description if needed (Line 168, add label field to toggles if needed change structure)
|
|
3036
|
-
// <p class="cv-toggle-description">Show or hide the ${this.formatToggleName(toggle).toLowerCase()} area </p>
|
|
3037
3223
|
// Get tab groups
|
|
3038
|
-
const
|
|
3224
|
+
const allTabGroups = this.core.getTabGroups() || [];
|
|
3225
|
+
const tabGroups = allTabGroups.filter(group => {
|
|
3226
|
+
if (group.isLocal) {
|
|
3227
|
+
return pageTabIds.has(group.id) || !!document.querySelector(`cv-tabgroup[id="${group.id}"]`);
|
|
3228
|
+
}
|
|
3229
|
+
return true; // Keep global tab groups
|
|
3230
|
+
});
|
|
3039
3231
|
let tabGroupControlsHTML = '';
|
|
3040
3232
|
if (this.options.showTabGroups && tabGroups && tabGroups.length > 0) {
|
|
3041
|
-
// Determine initial nav visibility state
|
|
3042
3233
|
const initialNavsVisible = TabManager.areNavsVisible(document.body);
|
|
3043
3234
|
tabGroupControlsHTML = `
|
|
3044
3235
|
<div class="cv-tabgroup-card cv-tabgroup-header">
|
|
@@ -3073,7 +3264,7 @@ ${TAB_STYLES}
|
|
|
3073
3264
|
</div>
|
|
3074
3265
|
`;
|
|
3075
3266
|
}
|
|
3076
|
-
this.
|
|
3267
|
+
this.stateModal.innerHTML = `
|
|
3077
3268
|
<div class="cv-widget-modal cv-custom-state-modal">
|
|
3078
3269
|
<header class="cv-modal-header">
|
|
3079
3270
|
<div class="cv-modal-header-content">
|
|
@@ -3089,7 +3280,7 @@ ${TAB_STYLES}
|
|
|
3089
3280
|
<main class="cv-modal-main">
|
|
3090
3281
|
${this.options.description ? `<p class="cv-modal-description">${this.options.description}</p>` : ''}
|
|
3091
3282
|
|
|
3092
|
-
${
|
|
3283
|
+
${visibleToggles.length ? `
|
|
3093
3284
|
<div class="cv-content-section">
|
|
3094
3285
|
<div class="cv-section-heading">Toggles</div>
|
|
3095
3286
|
<div class="cv-toggles-container">
|
|
@@ -3122,62 +3313,76 @@ ${TAB_STYLES}
|
|
|
3122
3313
|
</footer>
|
|
3123
3314
|
</div>
|
|
3124
3315
|
`;
|
|
3125
|
-
|
|
3126
|
-
this.attachStateModalEventListeners();
|
|
3127
|
-
// Load current state into form if we're already in a custom state
|
|
3316
|
+
this._attachStateModalContentEventListeners();
|
|
3128
3317
|
this.loadCurrentStateIntoForm();
|
|
3129
3318
|
}
|
|
3130
3319
|
/**
|
|
3131
|
-
* Attach event listeners for
|
|
3320
|
+
* Attach event listeners for the modal frame (delegated events)
|
|
3132
3321
|
*/
|
|
3133
|
-
|
|
3134
|
-
if (!this.
|
|
3322
|
+
_attachStateModalFrameEventListeners() {
|
|
3323
|
+
if (!this.stateModal)
|
|
3135
3324
|
return;
|
|
3136
|
-
//
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3325
|
+
// Delegated click events
|
|
3326
|
+
this.stateModal.addEventListener('click', (e) => {
|
|
3327
|
+
const target = e.target;
|
|
3328
|
+
// Close button
|
|
3329
|
+
if (target.closest('.cv-modal-close')) {
|
|
3140
3330
|
this.closeModal();
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
if (copyUrlBtn) {
|
|
3146
|
-
copyUrlBtn.addEventListener('click', () => {
|
|
3331
|
+
return;
|
|
3332
|
+
}
|
|
3333
|
+
// Copy URL button
|
|
3334
|
+
if (target.closest('.cv-share-btn')) {
|
|
3147
3335
|
this.copyShareableURL();
|
|
3148
|
-
|
|
3149
|
-
const iconContainer = copyUrlBtn
|
|
3336
|
+
const copyUrlBtn = target.closest('.cv-share-btn');
|
|
3337
|
+
const iconContainer = copyUrlBtn?.querySelector('.cv-share-btn-icon');
|
|
3150
3338
|
if (iconContainer) {
|
|
3151
3339
|
const originalIcon = iconContainer.innerHTML;
|
|
3152
3340
|
iconContainer.innerHTML = getTickIcon();
|
|
3153
|
-
// Revert after 3 seconds
|
|
3154
3341
|
setTimeout(() => {
|
|
3155
3342
|
iconContainer.innerHTML = originalIcon;
|
|
3156
3343
|
}, 3000);
|
|
3157
3344
|
}
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
// Add spinning animation to icon
|
|
3165
|
-
const resetIcon = resetBtn.querySelector('.cv-reset-btn-icon');
|
|
3345
|
+
return;
|
|
3346
|
+
}
|
|
3347
|
+
// Reset to default button
|
|
3348
|
+
if (target.closest('.cv-reset-btn')) {
|
|
3349
|
+
const resetBtn = target.closest('.cv-reset-btn');
|
|
3350
|
+
const resetIcon = resetBtn?.querySelector('.cv-reset-btn-icon');
|
|
3166
3351
|
if (resetIcon) {
|
|
3167
3352
|
resetIcon.classList.add('cv-spinning');
|
|
3168
3353
|
}
|
|
3169
3354
|
this.core.resetToDefault();
|
|
3170
3355
|
this.loadCurrentStateIntoForm();
|
|
3171
|
-
// Remove spinning animation after it completes
|
|
3172
3356
|
setTimeout(() => {
|
|
3173
3357
|
if (resetIcon) {
|
|
3174
3358
|
resetIcon.classList.remove('cv-spinning');
|
|
3175
3359
|
}
|
|
3176
|
-
}, 600);
|
|
3177
|
-
|
|
3178
|
-
|
|
3360
|
+
}, 600);
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3363
|
+
// Overlay click to close
|
|
3364
|
+
if (e.target === this.stateModal) {
|
|
3365
|
+
this.closeModal();
|
|
3366
|
+
}
|
|
3367
|
+
});
|
|
3368
|
+
// Escape key to close
|
|
3369
|
+
const handleEscape = (e) => {
|
|
3370
|
+
if (e.key === 'Escape') {
|
|
3371
|
+
this.closeModal();
|
|
3372
|
+
}
|
|
3373
|
+
};
|
|
3374
|
+
// We can't remove this listener easily if it's anonymous, so we attach it to the document
|
|
3375
|
+
// and it will stay for the lifetime of the widget. This is acceptable.
|
|
3376
|
+
document.addEventListener('keydown', handleEscape);
|
|
3377
|
+
}
|
|
3378
|
+
/**
|
|
3379
|
+
* Attach event listeners for custom state creator's dynamic content
|
|
3380
|
+
*/
|
|
3381
|
+
_attachStateModalContentEventListeners() {
|
|
3382
|
+
if (!this.stateModal)
|
|
3383
|
+
return;
|
|
3179
3384
|
// Listen to toggle switches
|
|
3180
|
-
const toggleInputs = this.
|
|
3385
|
+
const toggleInputs = this.stateModal.querySelectorAll('.cv-toggle-input');
|
|
3181
3386
|
toggleInputs.forEach(toggleInput => {
|
|
3182
3387
|
toggleInput.addEventListener('change', () => {
|
|
3183
3388
|
const state = this.getCurrentCustomStateFromModal();
|
|
@@ -3185,17 +3390,14 @@ ${TAB_STYLES}
|
|
|
3185
3390
|
});
|
|
3186
3391
|
});
|
|
3187
3392
|
// Listen to tab group selects
|
|
3188
|
-
const tabGroupSelects = this.
|
|
3393
|
+
const tabGroupSelects = this.stateModal.querySelectorAll('.cv-tabgroup-select');
|
|
3189
3394
|
tabGroupSelects.forEach(select => {
|
|
3190
3395
|
select.addEventListener('change', () => {
|
|
3191
3396
|
const groupId = select.dataset.groupId;
|
|
3192
3397
|
const tabId = select.value;
|
|
3193
3398
|
if (groupId && tabId) {
|
|
3194
|
-
// Get current state and update the tab for this group, then apply globally
|
|
3195
|
-
// This triggers sync behavior and persistence
|
|
3196
3399
|
const currentTabs = this.core.getCurrentActiveTabs();
|
|
3197
3400
|
currentTabs[groupId] = tabId;
|
|
3198
|
-
// Apply state globally for persistence and sync
|
|
3199
3401
|
const currentToggles = this.core.getCurrentActiveToggles();
|
|
3200
3402
|
const newState = {
|
|
3201
3403
|
toggles: currentToggles,
|
|
@@ -3206,84 +3408,56 @@ ${TAB_STYLES}
|
|
|
3206
3408
|
});
|
|
3207
3409
|
});
|
|
3208
3410
|
// Listener for show/hide tab navs
|
|
3209
|
-
const tabNavToggle = this.
|
|
3210
|
-
const navIcon = this.
|
|
3211
|
-
const navHeaderCard = this.
|
|
3411
|
+
const tabNavToggle = this.stateModal.querySelector('.cv-nav-pref-input');
|
|
3412
|
+
const navIcon = this.stateModal?.querySelector('#cv-nav-icon');
|
|
3413
|
+
const navHeaderCard = this.stateModal?.querySelector('.cv-tabgroup-card.cv-tabgroup-header');
|
|
3212
3414
|
if (tabNavToggle && navIcon && navHeaderCard) {
|
|
3213
|
-
// Helper to update icon based on state
|
|
3214
3415
|
const updateIcon = (isVisible, isHovering = false) => {
|
|
3215
3416
|
if (isHovering) {
|
|
3216
|
-
// On hover, show the transition icon
|
|
3217
3417
|
navIcon.innerHTML = getNavDashed();
|
|
3218
3418
|
}
|
|
3219
3419
|
else {
|
|
3220
|
-
// Normal state, show the status icon (on if visible, off if hidden)
|
|
3221
3420
|
navIcon.innerHTML = isVisible ? getNavHeadingOnIcon() : getNavHeadingOffIcon();
|
|
3222
3421
|
}
|
|
3223
3422
|
};
|
|
3224
|
-
|
|
3225
|
-
navHeaderCard.addEventListener('
|
|
3226
|
-
updateIcon(tabNavToggle.checked, true);
|
|
3227
|
-
});
|
|
3228
|
-
navHeaderCard.addEventListener('mouseleave', () => {
|
|
3229
|
-
updateIcon(tabNavToggle.checked, false);
|
|
3230
|
-
});
|
|
3231
|
-
// Add change listener
|
|
3423
|
+
navHeaderCard.addEventListener('mouseenter', () => updateIcon(tabNavToggle.checked, true));
|
|
3424
|
+
navHeaderCard.addEventListener('mouseleave', () => updateIcon(tabNavToggle.checked, false));
|
|
3232
3425
|
tabNavToggle.addEventListener('change', () => {
|
|
3233
3426
|
const visible = tabNavToggle.checked;
|
|
3234
|
-
// Update the icon based on new state (not hovering)
|
|
3235
3427
|
updateIcon(visible, false);
|
|
3236
|
-
// Persist preference via core
|
|
3237
3428
|
this.core.persistTabNavVisibility(visible);
|
|
3238
|
-
// Apply to DOM using TabManager via core
|
|
3239
3429
|
try {
|
|
3240
|
-
|
|
3241
|
-
TabManager.setNavsVisibility(rootEl, visible);
|
|
3430
|
+
TabManager.setNavsVisibility(document.body, visible);
|
|
3242
3431
|
}
|
|
3243
3432
|
catch (e) {
|
|
3244
|
-
// ignore errors
|
|
3245
3433
|
console.error('Failed to set tab nav visibility:', e);
|
|
3246
3434
|
}
|
|
3247
3435
|
});
|
|
3248
3436
|
}
|
|
3249
|
-
// Overlay click to close
|
|
3250
|
-
this.modal.addEventListener('click', (e) => {
|
|
3251
|
-
if (e.target === this.modal) {
|
|
3252
|
-
this.closeModal();
|
|
3253
|
-
}
|
|
3254
|
-
});
|
|
3255
|
-
// Escape key to close
|
|
3256
|
-
const handleEscape = (e) => {
|
|
3257
|
-
if (e.key === 'Escape') {
|
|
3258
|
-
this.closeModal();
|
|
3259
|
-
document.removeEventListener('keydown', handleEscape);
|
|
3260
|
-
}
|
|
3261
|
-
};
|
|
3262
|
-
document.addEventListener('keydown', handleEscape);
|
|
3263
3437
|
}
|
|
3264
3438
|
/**
|
|
3265
3439
|
* Apply theme class to the modal overlay based on options
|
|
3266
3440
|
*/
|
|
3267
3441
|
applyThemeToModal() {
|
|
3268
|
-
if (!this.
|
|
3442
|
+
if (!this.stateModal)
|
|
3269
3443
|
return;
|
|
3270
3444
|
if (this.options.theme === 'dark') {
|
|
3271
|
-
this.
|
|
3445
|
+
this.stateModal.classList.add('cv-widget-theme-dark');
|
|
3272
3446
|
}
|
|
3273
3447
|
else {
|
|
3274
|
-
this.
|
|
3448
|
+
this.stateModal.classList.remove('cv-widget-theme-dark');
|
|
3275
3449
|
}
|
|
3276
3450
|
}
|
|
3277
3451
|
/**
|
|
3278
3452
|
* Get current state from form values
|
|
3279
3453
|
*/
|
|
3280
3454
|
getCurrentCustomStateFromModal() {
|
|
3281
|
-
if (!this.
|
|
3455
|
+
if (!this.stateModal) {
|
|
3282
3456
|
return {};
|
|
3283
3457
|
}
|
|
3284
3458
|
// Collect toggle values
|
|
3285
3459
|
const toggles = [];
|
|
3286
|
-
const toggleInputs = this.
|
|
3460
|
+
const toggleInputs = this.stateModal.querySelectorAll('.cv-toggle-input');
|
|
3287
3461
|
toggleInputs.forEach(toggleInput => {
|
|
3288
3462
|
const toggle = toggleInput.dataset.toggle;
|
|
3289
3463
|
if (toggle && toggleInput.checked) {
|
|
@@ -3291,7 +3465,7 @@ ${TAB_STYLES}
|
|
|
3291
3465
|
}
|
|
3292
3466
|
});
|
|
3293
3467
|
// Collect tab selections
|
|
3294
|
-
const tabGroupSelects = this.
|
|
3468
|
+
const tabGroupSelects = this.stateModal.querySelectorAll('.cv-tabgroup-select');
|
|
3295
3469
|
const tabs = {};
|
|
3296
3470
|
tabGroupSelects.forEach(select => {
|
|
3297
3471
|
const groupId = select.dataset.groupId;
|
|
@@ -3319,25 +3493,25 @@ ${TAB_STYLES}
|
|
|
3319
3493
|
* Load current state into form based on currently active toggles
|
|
3320
3494
|
*/
|
|
3321
3495
|
loadCurrentStateIntoForm() {
|
|
3322
|
-
if (!this.
|
|
3496
|
+
if (!this.stateModal)
|
|
3323
3497
|
return;
|
|
3324
3498
|
// Get currently active toggles (from custom state or default configuration)
|
|
3325
3499
|
const activeToggles = this.core.getCurrentActiveToggles();
|
|
3326
3500
|
// First, uncheck all toggle inputs
|
|
3327
|
-
const allToggleInputs = this.
|
|
3501
|
+
const allToggleInputs = this.stateModal.querySelectorAll('.cv-toggle-input');
|
|
3328
3502
|
allToggleInputs.forEach(toggleInput => {
|
|
3329
3503
|
toggleInput.checked = false;
|
|
3330
3504
|
});
|
|
3331
3505
|
// Then check the ones that should be active
|
|
3332
3506
|
activeToggles.forEach(toggle => {
|
|
3333
|
-
const toggleInput = this.
|
|
3507
|
+
const toggleInput = this.stateModal?.querySelector(`[data-toggle="${toggle}"]`);
|
|
3334
3508
|
if (toggleInput) {
|
|
3335
3509
|
toggleInput.checked = true;
|
|
3336
3510
|
}
|
|
3337
3511
|
});
|
|
3338
3512
|
// Load tab group selections
|
|
3339
3513
|
const activeTabs = this.core.getCurrentActiveTabs();
|
|
3340
|
-
const tabGroupSelects = this.
|
|
3514
|
+
const tabGroupSelects = this.stateModal.querySelectorAll('.cv-tabgroup-select');
|
|
3341
3515
|
tabGroupSelects.forEach(select => {
|
|
3342
3516
|
const groupId = select.dataset.groupId;
|
|
3343
3517
|
if (groupId && activeTabs[groupId]) {
|
|
@@ -3352,8 +3526,8 @@ ${TAB_STYLES}
|
|
|
3352
3526
|
}
|
|
3353
3527
|
return TabManager.areNavsVisible(document.body);
|
|
3354
3528
|
})();
|
|
3355
|
-
const tabNavToggle = this.
|
|
3356
|
-
const navIcon = this.
|
|
3529
|
+
const tabNavToggle = this.stateModal.querySelector('.cv-nav-pref-input');
|
|
3530
|
+
const navIcon = this.stateModal?.querySelector('#cv-nav-icon');
|
|
3357
3531
|
if (tabNavToggle) {
|
|
3358
3532
|
tabNavToggle.checked = navPref;
|
|
3359
3533
|
// Ensure UI matches actual visibility
|
|
@@ -3364,33 +3538,22 @@ ${TAB_STYLES}
|
|
|
3364
3538
|
}
|
|
3365
3539
|
}
|
|
3366
3540
|
}
|
|
3367
|
-
/**
|
|
3368
|
-
* Format toggle name for display
|
|
3369
|
-
*/
|
|
3370
|
-
formatToggleName(toggle) {
|
|
3371
|
-
return toggle.charAt(0).toUpperCase() + toggle.slice(1);
|
|
3372
|
-
}
|
|
3373
3541
|
/**
|
|
3374
3542
|
* Check if this is the first visit and show welcome modal
|
|
3375
3543
|
*/
|
|
3376
3544
|
showWelcomeModalIfFirstVisit() {
|
|
3545
|
+
if (!this._hasVisibleConfig)
|
|
3546
|
+
return;
|
|
3377
3547
|
const STORAGE_KEY = 'cv-welcome-shown';
|
|
3378
3548
|
// Check if welcome has been shown before
|
|
3379
3549
|
const hasSeenWelcome = localStorage.getItem(STORAGE_KEY);
|
|
3380
3550
|
if (!hasSeenWelcome) {
|
|
3381
|
-
//
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
// Show welcome modal after a short delay to let the page settle
|
|
3388
|
-
setTimeout(() => {
|
|
3389
|
-
this.createWelcomeModal();
|
|
3390
|
-
}, 500);
|
|
3391
|
-
// Mark as shown
|
|
3392
|
-
localStorage.setItem(STORAGE_KEY, 'true');
|
|
3393
|
-
}
|
|
3551
|
+
// Show welcome modal after a short delay to let the page settle
|
|
3552
|
+
setTimeout(() => {
|
|
3553
|
+
this.createWelcomeModal();
|
|
3554
|
+
}, 500);
|
|
3555
|
+
// Mark as shown
|
|
3556
|
+
localStorage.setItem(STORAGE_KEY, 'true');
|
|
3394
3557
|
}
|
|
3395
3558
|
}
|
|
3396
3559
|
/**
|
|
@@ -3398,12 +3561,14 @@ ${TAB_STYLES}
|
|
|
3398
3561
|
*/
|
|
3399
3562
|
createWelcomeModal() {
|
|
3400
3563
|
// Don't show if there's already a modal open
|
|
3401
|
-
if (this.
|
|
3564
|
+
if (this.stateModal && !this.stateModal.classList.contains('cv-hidden'))
|
|
3402
3565
|
return;
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
this.
|
|
3406
|
-
|
|
3566
|
+
const welcomeModal = document.createElement('div');
|
|
3567
|
+
welcomeModal.className = 'cv-widget-modal-overlay cv-welcome-modal-overlay';
|
|
3568
|
+
if (this.options.theme === 'dark') {
|
|
3569
|
+
welcomeModal.classList.add('cv-widget-theme-dark');
|
|
3570
|
+
}
|
|
3571
|
+
welcomeModal.innerHTML = `
|
|
3407
3572
|
<div class="cv-widget-modal cv-welcome-modal">
|
|
3408
3573
|
<header class="cv-modal-header">
|
|
3409
3574
|
<div class="cv-modal-header-content">
|
|
@@ -3427,33 +3592,32 @@ ${TAB_STYLES}
|
|
|
3427
3592
|
</div>
|
|
3428
3593
|
</div>
|
|
3429
3594
|
`;
|
|
3430
|
-
document.body.appendChild(
|
|
3431
|
-
this.attachWelcomeModalEventListeners();
|
|
3595
|
+
document.body.appendChild(welcomeModal);
|
|
3596
|
+
this.attachWelcomeModalEventListeners(welcomeModal);
|
|
3432
3597
|
}
|
|
3433
3598
|
/**
|
|
3434
3599
|
* Attach event listeners for welcome modal
|
|
3435
3600
|
*/
|
|
3436
|
-
attachWelcomeModalEventListeners() {
|
|
3437
|
-
|
|
3438
|
-
|
|
3601
|
+
attachWelcomeModalEventListeners(welcomeModal) {
|
|
3602
|
+
const closeModal = () => {
|
|
3603
|
+
welcomeModal.remove();
|
|
3604
|
+
document.removeEventListener('keydown', handleEscape);
|
|
3605
|
+
};
|
|
3439
3606
|
// Got it button
|
|
3440
|
-
const gotItBtn =
|
|
3607
|
+
const gotItBtn = welcomeModal.querySelector('.cv-welcome-got-it');
|
|
3441
3608
|
if (gotItBtn) {
|
|
3442
|
-
gotItBtn.addEventListener('click',
|
|
3443
|
-
this.closeModal();
|
|
3444
|
-
});
|
|
3609
|
+
gotItBtn.addEventListener('click', closeModal);
|
|
3445
3610
|
}
|
|
3446
3611
|
// Overlay click to close
|
|
3447
|
-
|
|
3448
|
-
if (e.target ===
|
|
3449
|
-
|
|
3612
|
+
welcomeModal.addEventListener('click', (e) => {
|
|
3613
|
+
if (e.target === welcomeModal) {
|
|
3614
|
+
closeModal();
|
|
3450
3615
|
}
|
|
3451
3616
|
});
|
|
3452
3617
|
// Escape key to close
|
|
3453
3618
|
const handleEscape = (e) => {
|
|
3454
3619
|
if (e.key === 'Escape') {
|
|
3455
|
-
|
|
3456
|
-
document.removeEventListener('keydown', handleEscape);
|
|
3620
|
+
closeModal();
|
|
3457
3621
|
}
|
|
3458
3622
|
};
|
|
3459
3623
|
document.addEventListener('keydown', handleEscape);
|
|
@@ -3523,7 +3687,7 @@ ${TAB_STYLES}
|
|
|
3523
3687
|
console.warn(`[CustomViews] Config file not found at ${fullConfigPath}. Using defaults.`);
|
|
3524
3688
|
// Provide minimal default config structure
|
|
3525
3689
|
configFile = {
|
|
3526
|
-
config: {
|
|
3690
|
+
config: { toggles: [], defaultState: {} },
|
|
3527
3691
|
widget: { enabled: true }
|
|
3528
3692
|
};
|
|
3529
3693
|
}
|
|
@@ -3561,7 +3725,7 @@ ${TAB_STYLES}
|
|
|
3561
3725
|
core,
|
|
3562
3726
|
...configFile.widget
|
|
3563
3727
|
});
|
|
3564
|
-
widget.
|
|
3728
|
+
widget.renderModalIcon();
|
|
3565
3729
|
// Store widget instance
|
|
3566
3730
|
window.customViewsInstance.widget = widget;
|
|
3567
3731
|
console.log('[CustomViews] Widget initialized and rendered');
|