Repository navigation
Expand file tree
/
Copy pathmain-v4.js
More file actions
158 lines (143 loc) · 6.53 KB
/
Copy pathmain-v4.js
File metadata and controls
158 lines (143 loc) · 6.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Gentelella 2026 v4 — entry
// Self-contained dashboard skin. Loads only the v4 design system.
import './scss/v4/main.scss';
import { mountShell } from './v4/shell.js';
import { initCharts } from './v4/charts.js';
import { initTables } from './v4/tables.js';
import { openMenu, DEFAULT_CARD_MENU } from './v4/menus.js';
import { initCommandPalette } from './v4/command-palette.js';
import { initPageActions } from './v4/page-actions.js';
mountShell();
initCharts();
initTables();
initCommandPalette();
initPageActions();
// Service worker — only in production builds (skip on dev so HMR isn't fought
// by the cache). Path uses Vite's BASE_URL so subpath deploys (e.g.
// preview.colorlib.com/theme/foo/) register the SW at the right scope.
//
// A host that bundles this entry without shipping sw.js — a Laravel or Django
// app serving the design system from node_modules, say — opts out with
// <html data-sw="off">, so it doesn't take a 404 on every page load for a
// file it never had.
if ('serviceWorker' in navigator && import.meta.env.PROD
&& document.documentElement.dataset.sw !== 'off') {
window.addEventListener('load', () => {
const swPath = `${import.meta.env.BASE_URL}sw.js`;
navigator.serviceWorker.register(swPath).catch(() => { /* ignore */ });
});
}
// Lazy-load page-specific modules only when their host element is on the page.
if (document.getElementById('inbox-root')) {
import('./v4/inbox.js').then((m) => m.initInbox());
}
if (document.querySelector('.calendar-grid')) {
import('./v4/calendar.js').then((m) => m.initCalendar());
}
if (document.querySelector('.settings-content')) {
import('./v4/settings.js').then((m) => m.initSettings());
}
if (document.querySelector('[data-date-range], [data-rich-text], [data-multi-select]')) {
import('./v4/form-controls.js').then((m) => m.initFormControls());
}
// ────────────────────────
// Delegated interactions
// ────────────────────────
// Toggle switches
document.addEventListener('click', (e) => {
const toggle = e.target.closest('.toggle');
if (toggle) {toggle.classList.toggle('on');}
});
// Todo checkboxes — toggle .done on the cb + row, then refresh any
// `[data-todo-counter]` element inside the parent card so the "X of Y
// remaining" subtitle stays in sync with the actual checkboxes.
document.addEventListener('click', (e) => {
const cb = e.target.closest('.todo-cb');
if (!cb) {return;}
cb.classList.toggle('done');
const row = cb.closest('.todo-row');
if (row) {row.classList.toggle('done');}
// Update counter text within the same card.
const card = cb.closest('.card');
if (!card) {return;}
const counter = card.querySelector('[data-todo-counter]');
if (!counter) {return;}
const all = card.querySelectorAll('.todo-row');
const done = card.querySelectorAll('.todo-row.done');
const remaining = all.length - done.length;
// Format: "<remaining> of <total> remaining" — matches existing copy.
counter.textContent = `${remaining} of ${all.length} remaining`;
});
// Tab groups: works for any container of .chart-tab buttons (chart cards,
// calendar view switcher, generic tab strips). Adds .active to the clicked
// tab and removes it from siblings in the same parent.
document.addEventListener('click', (e) => {
const tab = e.target.closest('.chart-tab');
if (!tab) {return;}
tab.parentElement.querySelectorAll('.chart-tab').forEach((t) => t.classList.remove('active'));
tab.classList.add('active');
});
// Button groups with [data-group]: radio-style single-select. Click sets
// .active on the clicked button and clears its siblings in the same group.
// Opt-in via data-group so plain action button groups are unaffected.
document.addEventListener('click', (e) => {
const btn = e.target.closest('.btn-group[data-group] > .btn');
if (!btn) {return;}
btn.parentElement.querySelectorAll('.btn').forEach((b) => {
b.classList.remove('active');
b.setAttribute('aria-pressed', 'false');
});
btn.classList.add('active');
btn.setAttribute('aria-pressed', 'true');
});
// Card option buttons → popover menu.
// Calendar nav buttons (prev/next month) are also .card-opt-btn but live
// inside .calendar-toolbar; calendar.js stops propagation on those clicks
// so they never reach this handler.
document.addEventListener('click', (e) => {
const btn = e.target.closest('.card-opt-btn');
if (!btn) {return;}
// Skip if the click was already handled (e.g. calendar prev/next).
if (e.defaultPrevented) {return;}
e.preventDefault();
openMenu(btn, DEFAULT_CARD_MENU);
});
// Chip dismiss (× icon) and chip toggle.
document.addEventListener('click', (e) => {
const closer = e.target.closest('.chip-close');
if (closer) {
const chip = closer.closest('.chip');
if (chip) {
chip.style.transition = 'opacity 150ms, transform 150ms';
chip.style.opacity = '0';
chip.style.transform = 'scale(0.85)';
setTimeout(() => chip.remove(), 160);
}
return;
}
const chip = e.target.closest('.chip');
if (chip) {chip.classList.toggle('active');}
});
// Form submit — let HTML5 validation run, then fake-submit on valid forms.
// Lazy-imports the toast helper so the dependency stays out of the entry chunk.
document.addEventListener('submit', (e) => {
const form = e.target;
if (!(form instanceof HTMLFormElement)) {return;}
// Only this template's own demo forms are faked. A form that says where it
// posts is a real one, and swallowing its submit would break every form in a
// server-rendered app — sign-in, registration, and every create or edit
// screen. None of the demo forms here carries an action, so the distinction
// costs nothing. `data-demo-submit="false"` opts a form out explicitly.
const action = (form.getAttribute('action') || '').trim();
if (action !== '' && action !== '#') {return;}
if (form.dataset.demoSubmit === 'false') {return;}
// Native :invalid forms still get the browser's validation UI before we
// see the submit event, so reaching here means the form is already valid.
e.preventDefault();
const submitBtn = form.querySelector('button[type="submit"], input[type="submit"]');
const label = (submitBtn?.textContent || submitBtn?.value || 'Saved').trim();
import('./v4/toast.js').then(({ showToast }) => showToast(`${label} ✓`, { variant: 'success' }));
if (form.dataset.resetOnSubmit !== 'false') {form.reset();}
});
// Topbar search box opens the command palette — wired by initCommandPalette.
// Page-actions (Print / Export / Compose / Add / etc.) wired via initPageActions.