daub-ui 2.6.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/daub.js ADDED
@@ -0,0 +1,1782 @@
1
+ /* ============================================================
2
+ DAUB UI KIT — Interactive Behaviors
3
+ Version 2.2
4
+ IIFE module exposing window.DAUB = { init, toast, theme API }
5
+ ============================================================ */
6
+ ;(function() {
7
+ 'use strict';
8
+
9
+ /* ----------------------------------------------------------
10
+ Theme Manager
11
+ ---------------------------------------------------------- */
12
+ var THEMES = [
13
+ 'light','dark','grunge-light','grunge-dark','solarized','solarized-dark','ink-light','ink','ember-light','ember','bone','bone-dark',
14
+ 'dracula','dracula-light','nord','nord-light','one-dark','one-dark-light','monokai','monokai-light','gruvbox','gruvbox-light',
15
+ 'night-owl','night-owl-light','github','github-dark','catppuccin','catppuccin-dark','tokyo-night','tokyo-night-light','material','material-light',
16
+ 'synthwave','synthwave-light','shades-of-purple','shades-of-purple-light','ayu','ayu-dark','horizon','horizon-light'
17
+ ];
18
+
19
+ var THEME_FAMILIES = {
20
+ 'default': { light: 'light', dark: 'dark' },
21
+ 'grunge': { light: 'grunge-light', dark: 'grunge-dark' },
22
+ 'solarized': { light: 'solarized', dark: 'solarized-dark' },
23
+ 'ink': { light: 'ink-light', dark: 'ink' },
24
+ 'ember': { light: 'ember-light', dark: 'ember' },
25
+ 'bone': { light: 'bone', dark: 'bone-dark' },
26
+ 'dracula': { light: 'dracula-light', dark: 'dracula' },
27
+ 'nord': { light: 'nord-light', dark: 'nord' },
28
+ 'one-dark': { light: 'one-dark-light',dark: 'one-dark' },
29
+ 'monokai': { light: 'monokai-light', dark: 'monokai' },
30
+ 'gruvbox': { light: 'gruvbox-light', dark: 'gruvbox' },
31
+ 'night-owl': { light: 'night-owl-light',dark: 'night-owl' },
32
+ 'github': { light: 'github', dark: 'github-dark' },
33
+ 'catppuccin': { light: 'catppuccin', dark: 'catppuccin-dark' },
34
+ 'tokyo-night':{ light: 'tokyo-night-light',dark: 'tokyo-night' },
35
+ 'material': { light: 'material-light', dark: 'material' },
36
+ 'synthwave': { light: 'synthwave-light',dark: 'synthwave' },
37
+ 'shades-of-purple':{ light: 'shades-of-purple-light',dark: 'shades-of-purple' },
38
+ 'ayu': { light: 'ayu', dark: 'ayu-dark' },
39
+ 'horizon': { light: 'horizon-light', dark: 'horizon' }
40
+ };
41
+ var FAMILY_NAMES = [
42
+ 'default','grunge','solarized','ink','ember','bone',
43
+ 'dracula','nord','one-dark','monokai','gruvbox',
44
+ 'night-owl','github','catppuccin','tokyo-night','material',
45
+ 'synthwave','shades-of-purple','ayu','horizon'
46
+ ];
47
+
48
+ var THEME_CATEGORIES = {
49
+ 'originals': ['default','grunge','solarized','ink','ember','bone'],
50
+ 'classics': ['dracula','nord','one-dark','monokai','gruvbox'],
51
+ 'modern': ['night-owl','github','catppuccin','tokyo-night','material'],
52
+ 'trending': ['synthwave','shades-of-purple','ayu','horizon']
53
+ };
54
+ var CATEGORY_NAMES = ['originals','classics','modern','trending'];
55
+
56
+ function getCategory(family) {
57
+ for (var i = 0; i < CATEGORY_NAMES.length; i++) {
58
+ if (THEME_CATEGORIES[CATEGORY_NAMES[i]].indexOf(family) !== -1) return CATEGORY_NAMES[i];
59
+ }
60
+ return 'originals';
61
+ }
62
+
63
+ // Reverse lookup: theme name → { family, mode }
64
+ var THEME_TO_FAMILY = {};
65
+ Object.keys(THEME_FAMILIES).forEach(function(f) {
66
+ THEME_TO_FAMILY[THEME_FAMILIES[f].light] = { family: f, mode: 'light' };
67
+ THEME_TO_FAMILY[THEME_FAMILIES[f].dark] = { family: f, mode: 'dark' };
68
+ });
69
+
70
+ var _grungeFontLoaded = false;
71
+ var _userExplicitTheme = false;
72
+ var _scheme = 'auto';
73
+
74
+ function getTheme() {
75
+ return document.documentElement.getAttribute('data-theme') || 'light';
76
+ }
77
+
78
+ function setTheme(theme) {
79
+ if (THEMES.indexOf(theme) === -1) return;
80
+ document.documentElement.setAttribute('data-theme', theme);
81
+ try { localStorage.setItem('db-theme', theme); } catch(e) {}
82
+ _userExplicitTheme = true;
83
+ if (theme.indexOf('grunge') !== -1) loadGrungeFont();
84
+ updateSwitcherUI();
85
+ requestAnimationFrame(function() { fixNestedRadius(); });
86
+ }
87
+
88
+ function cycleTheme() {
89
+ var fam = getFamily();
90
+ var idx = FAMILY_NAMES.indexOf(fam);
91
+ var next = FAMILY_NAMES[(idx + 1) % FAMILY_NAMES.length];
92
+ setFamily(next);
93
+ return getTheme();
94
+ }
95
+
96
+ function initTheme() {
97
+ var stored = null;
98
+ try { stored = localStorage.getItem('db-theme'); } catch(e) {}
99
+
100
+ // Restore scheme
101
+ var storedScheme = null;
102
+ try { storedScheme = localStorage.getItem('db-scheme'); } catch(e) {}
103
+ if (storedScheme && ['auto','light','dark'].indexOf(storedScheme) !== -1) {
104
+ _scheme = storedScheme;
105
+ document.documentElement.setAttribute('data-scheme', storedScheme);
106
+ }
107
+
108
+ if (stored && THEMES.indexOf(stored) !== -1) {
109
+ document.documentElement.setAttribute('data-theme', stored);
110
+ _userExplicitTheme = true;
111
+ if (stored.indexOf('grunge') !== -1) loadGrungeFont();
112
+ } else if (!document.documentElement.hasAttribute('data-theme')) {
113
+ var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
114
+ document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
115
+ }
116
+
117
+ // Restore accent
118
+ var storedAccent = null;
119
+ try { storedAccent = localStorage.getItem('db-accent'); } catch(e) {}
120
+ if (storedAccent) setAccent(storedAccent);
121
+
122
+ // Listen for OS theme changes — only applies in auto scheme
123
+ if (window.matchMedia) {
124
+ var mq = window.matchMedia('(prefers-color-scheme: dark)');
125
+ var handler = function(e) {
126
+ if (_scheme === 'auto') {
127
+ var family = getFamily();
128
+ var target = THEME_FAMILIES[family][e.matches ? 'dark' : 'light'];
129
+ setTheme(target);
130
+ }
131
+ };
132
+ if (mq.addEventListener) mq.addEventListener('change', handler);
133
+ else if (mq.addListener) mq.addListener(handler);
134
+ }
135
+ }
136
+
137
+ function loadGrungeFont() {
138
+ if (_grungeFontLoaded) return;
139
+ _grungeFontLoaded = true;
140
+ var link = document.createElement('link');
141
+ link.rel = 'stylesheet';
142
+ link.href = 'https://fonts.googleapis.com/css2?family=Special+Elite&display=swap';
143
+ document.head.appendChild(link);
144
+ }
145
+
146
+ /* ----------------------------------------------------------
147
+ Scheme & Family API
148
+ ---------------------------------------------------------- */
149
+ function getScheme() { return _scheme; }
150
+
151
+ function getFamily() {
152
+ var info = THEME_TO_FAMILY[getTheme()];
153
+ return info ? info.family : 'default';
154
+ }
155
+
156
+ function getEffectiveMode() {
157
+ if (_scheme === 'auto')
158
+ return (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
159
+ return _scheme;
160
+ }
161
+
162
+ function setScheme(scheme) {
163
+ if (['auto','light','dark'].indexOf(scheme) === -1) return;
164
+ _scheme = scheme;
165
+ try { localStorage.setItem('db-scheme', scheme); } catch(e) {}
166
+ document.documentElement.setAttribute('data-scheme', scheme);
167
+ var family = getFamily();
168
+ var target = THEME_FAMILIES[family][getEffectiveMode()];
169
+ if (target !== getTheme()) setTheme(target);
170
+ updateSwitcherUI();
171
+ }
172
+
173
+ function setFamily(family) {
174
+ if (!THEME_FAMILIES[family]) return;
175
+ setTheme(THEME_FAMILIES[family][getEffectiveMode()]);
176
+ document.dispatchEvent(new CustomEvent('daub:familychange', {detail: {family: family}}));
177
+ }
178
+
179
+ /* ----------------------------------------------------------
180
+ Accent Color
181
+ ---------------------------------------------------------- */
182
+ function clamp(v) { return Math.max(0, Math.min(255, v)); }
183
+ function hexToHSL(hex) {
184
+ var r = parseInt(hex.slice(1,3),16)/255;
185
+ var g = parseInt(hex.slice(3,5),16)/255;
186
+ var b = parseInt(hex.slice(5,7),16)/255;
187
+ var max = Math.max(r,g,b), min = Math.min(r,g,b);
188
+ var h, s, l = (max+min)/2;
189
+ if (max===min) { h=s=0; }
190
+ else {
191
+ var d=max-min;
192
+ s=l>0.5?d/(2-max-min):d/(max+min);
193
+ if(max===r) h=((g-b)/d+(g<b?6:0))/6;
194
+ else if(max===g) h=((b-r)/d+2)/6;
195
+ else h=((r-g)/d+4)/6;
196
+ }
197
+ return [h*360,s*100,l*100];
198
+ }
199
+ function hslToHex(h,s,l) {
200
+ h/=360; s/=100; l/=100;
201
+ var r,g,b;
202
+ if(s===0){r=g=b=l;}
203
+ else {
204
+ var hue2rgb=function(p,q,t){if(t<0)t+=1;if(t>1)t-=1;if(t<1/6)return p+(q-p)*6*t;if(t<1/2)return q;if(t<2/3)return p+(q-p)*(2/3-t)*6;return p;};
205
+ var q=l<0.5?l*(1+s):l+s-l*s;var p=2*l-q;
206
+ r=hue2rgb(p,q,h+1/3);g=hue2rgb(p,q,h);b=hue2rgb(p,q,h-1/3);
207
+ }
208
+ return '#'+[r,g,b].map(function(x){var hex=Math.round(x*255).toString(16);return hex.length===1?'0'+hex:hex;}).join('');
209
+ }
210
+ function darken(hex, pct) {
211
+ var hsl=hexToHSL(hex);
212
+ return hslToHex(hsl[0],hsl[1],Math.max(0,hsl[2]-pct));
213
+ }
214
+ function lighten(hex, pct) {
215
+ var hsl=hexToHSL(hex);
216
+ return hslToHex(hsl[0],hsl[1],Math.min(100,hsl[2]+pct));
217
+ }
218
+
219
+ function setAccent(hex) {
220
+ var r=parseInt(hex.slice(1,3),16);
221
+ var g=parseInt(hex.slice(3,5),16);
222
+ var b=parseInt(hex.slice(5,7),16);
223
+ var root=document.documentElement;
224
+ root.style.setProperty('--db-terracotta', hex);
225
+ root.style.setProperty('--db-accent-rgb', r+','+g+','+b);
226
+ root.style.setProperty('--db-accent-hover', darken(hex, 10));
227
+ root.style.setProperty('--db-accent-pressed', darken(hex, 20));
228
+ root.style.setProperty('--db-accent-dark', darken(hex, 15));
229
+ root.style.setProperty('--db-accent-light', lighten(hex, 10));
230
+ root.style.setProperty('--db-terracotta-text', darken(hex, 20));
231
+ try { localStorage.setItem('db-accent', hex); } catch(e) {}
232
+ updateAccentPickerUI();
233
+ }
234
+
235
+ function resetAccent() {
236
+ var props=['--db-terracotta','--db-accent-rgb','--db-accent-hover',
237
+ '--db-accent-pressed','--db-accent-dark','--db-accent-light','--db-terracotta-text'];
238
+ props.forEach(function(p){document.documentElement.style.removeProperty(p);});
239
+ try { localStorage.removeItem('db-accent'); } catch(e) {}
240
+ updateAccentPickerUI();
241
+ }
242
+
243
+ function getAccent() {
244
+ return getComputedStyle(document.documentElement).getPropertyValue('--db-terracotta').trim();
245
+ }
246
+
247
+ function updateAccentPickerUI() {
248
+ var stored = null;
249
+ try { stored = localStorage.getItem('db-accent'); } catch(e) {}
250
+ document.querySelectorAll('.db-accent-picker__dot').forEach(function(btn) {
251
+ var accent = btn.getAttribute('data-accent');
252
+ var isActive = stored ? (accent === stored) : (accent === 'reset');
253
+ btn.setAttribute('aria-pressed', String(isActive));
254
+ });
255
+ }
256
+
257
+ /* ----------------------------------------------------------
258
+ Theme Switcher UI
259
+ ---------------------------------------------------------- */
260
+ var FAMILY_SWATCHES = {
261
+ 'default':{light:'#FAF8F0',dark:'#2C2824',accent:'#D48B6A'},
262
+ 'grunge':{light:'#EDE8DF',dark:'#1E1B17',accent:'#CC7F55'},
263
+ 'solarized':{light:'#fdf6e3',dark:'#073642',accent:'#2aa198'},
264
+ 'ink':{light:'#EEF0F5',dark:'#1C2030',accent:'#8B9DC3'},
265
+ 'ember':{light:'#F8F0E8',dark:'#201810',accent:'#D48B6A'},
266
+ 'bone':{light:'#FAFAFA',dark:'#1A1A1A',accent:'#A0A0A0'},
267
+ 'dracula':{light:'#FFFBEB',dark:'#282A36',accent:'#BD93F9'},
268
+ 'nord':{light:'#ECEFF4',dark:'#2E3440',accent:'#88C0D0'},
269
+ 'one-dark':{light:'#FAFAFA',dark:'#282C34',accent:'#61AFEF'},
270
+ 'monokai':{light:'#FAFAF8',dark:'#272822',accent:'#66D9EF'},
271
+ 'gruvbox':{light:'#FBF1C7',dark:'#282828',accent:'#FE8019'},
272
+ 'night-owl':{light:'#FBFBFB',dark:'#011627',accent:'#82AAFF'},
273
+ 'github':{light:'#FFFFFF',dark:'#0D1117',accent:'#58A6FF'},
274
+ 'catppuccin':{light:'#EFF1F5',dark:'#1E1E2E',accent:'#CBA6F7'},
275
+ 'tokyo-night':{light:'#D5D6DB',dark:'#1A1B26',accent:'#7AA2F7'},
276
+ 'material':{light:'#FAFAFA',dark:'#263238',accent:'#82AAFF'},
277
+ 'synthwave':{light:'#F5E6FF',dark:'#2B213A',accent:'#F92AAD'},
278
+ 'shades-of-purple':{light:'#F3EFFF',dark:'#2D2B55',accent:'#FAD000'},
279
+ 'ayu':{light:'#FAFAFA',dark:'#0B0E14',accent:'#FF8F40'},
280
+ 'horizon':{light:'#FDF0ED',dark:'#1C1E26',accent:'#E95678'}
281
+ };
282
+ var CATEGORY_LABELS = {originals:'Originals',classics:'Classics',modern:'Modern',trending:'Trending'};
283
+ var FAMILY_LABELS = {
284
+ 'default':'Default','grunge':'Grunge','solarized':'Solar','ink':'Ink','ember':'Ember','bone':'Bone',
285
+ 'dracula':'Dracula','nord':'Nord','one-dark':'One Dark','monokai':'Monokai','gruvbox':'Gruvbox',
286
+ 'night-owl':'Night Owl','github':'GitHub','catppuccin':'Catppuccin','tokyo-night':'Tokyo','material':'Material',
287
+ 'synthwave':'Synthwave','shades-of-purple':'Purple','ayu':'Ayu','horizon':'Horizon'
288
+ };
289
+
290
+ function buildPopoverContent(popover) {
291
+ while (popover.firstChild) popover.removeChild(popover.firstChild);
292
+ var isInline = popover.classList.contains('db-theme-switcher__popover--inline');
293
+ // Category tabs row
294
+ var tabRow = document.createElement('div');
295
+ tabRow.className = 'db-theme-switcher__tabs';
296
+ var panels = [];
297
+ for (var ci = 0; ci < CATEGORY_NAMES.length; ci++) {
298
+ var cat = CATEGORY_NAMES[ci];
299
+ var families = THEME_CATEGORIES[cat];
300
+ // Tab button
301
+ var tab = document.createElement('button');
302
+ tab.className = 'db-theme-switcher__tab';
303
+ tab.setAttribute('data-cat', cat);
304
+ tab.setAttribute('aria-pressed', ci === 0 ? 'true' : 'false');
305
+ tab.textContent = CATEGORY_LABELS[cat];
306
+ tabRow.appendChild(tab);
307
+ // Items panel
308
+ var row = document.createElement('div');
309
+ row.className = 'db-theme-switcher__category-items';
310
+ row.setAttribute('data-cat', cat);
311
+ if (ci > 0) row.style.display = 'none';
312
+ for (var fi = 0; fi < families.length; fi++) {
313
+ var fam = families[fi];
314
+ var sw = FAMILY_SWATCHES[fam] || {light:'#ccc',dark:'#333'};
315
+ var item = document.createElement('button');
316
+ item.className = 'db-theme-switcher__item';
317
+ item.setAttribute('data-family', fam);
318
+ item.setAttribute('aria-label', fam.replace(/-/g,' '));
319
+ item.setAttribute('aria-pressed', 'false');
320
+ item.title = (FAMILY_LABELS[fam] || fam);
321
+ var dot = document.createElement('span');
322
+ dot.className = 'db-theme-switcher__dot';
323
+ var halfL = document.createElement('span');
324
+ halfL.className = 'db-theme-switcher__dot-light';
325
+ halfL.style.background = sw.light;
326
+ var accentBar = document.createElement('span');
327
+ accentBar.className = 'db-theme-switcher__dot-accent';
328
+ accentBar.style.background = sw.accent;
329
+ var halfD = document.createElement('span');
330
+ halfD.className = 'db-theme-switcher__dot-dark';
331
+ halfD.style.background = sw.dark;
332
+ dot.appendChild(halfL);
333
+ dot.appendChild(accentBar);
334
+ dot.appendChild(halfD);
335
+ item.appendChild(dot);
336
+ var name = document.createElement('span');
337
+ name.className = 'db-theme-switcher__name';
338
+ name.textContent = FAMILY_LABELS[fam] || fam;
339
+ item.appendChild(name);
340
+ row.appendChild(item);
341
+ }
342
+ panels.push(row);
343
+ }
344
+ popover.appendChild(tabRow);
345
+ for (var pi = 0; pi < panels.length; pi++) popover.appendChild(panels[pi]);
346
+ // Tab click handler
347
+ tabRow.addEventListener('click', function(e) {
348
+ var tab = e.target.closest('.db-theme-switcher__tab');
349
+ if (!tab) return;
350
+ var activeCat = tab.getAttribute('data-cat');
351
+ tabRow.querySelectorAll('.db-theme-switcher__tab').forEach(function(t) {
352
+ t.setAttribute('aria-pressed', t.getAttribute('data-cat') === activeCat ? 'true' : 'false');
353
+ });
354
+ popover.querySelectorAll('.db-theme-switcher__category-items').forEach(function(p) {
355
+ p.style.display = p.getAttribute('data-cat') === activeCat ? '' : 'none';
356
+ });
357
+ });
358
+ if (!popover.classList.contains('db-theme-switcher__popover--inline')) {
359
+ var schemeRow = document.createElement('div');
360
+ schemeRow.className = 'db-theme-switcher__scheme';
361
+ ['auto','light','dark'].forEach(function(s) {
362
+ var btn = document.createElement('button');
363
+ btn.className = 'db-theme-switcher__scheme-btn';
364
+ btn.setAttribute('data-scheme', s);
365
+ btn.setAttribute('aria-label', s + ' mode');
366
+ btn.setAttribute('aria-pressed', 'false');
367
+ btn.textContent = s;
368
+ schemeRow.appendChild(btn);
369
+ });
370
+ popover.appendChild(schemeRow);
371
+ }
372
+ }
373
+
374
+ function _createPaletteIcon() {
375
+ var ns = 'http://www.w3.org/2000/svg';
376
+ var svg = document.createElementNS(ns, 'svg');
377
+ svg.setAttribute('viewBox', '0 0 24 24');
378
+ svg.setAttribute('fill', 'none');
379
+ svg.setAttribute('stroke', 'currentColor');
380
+ svg.setAttribute('stroke-width', '2');
381
+ svg.setAttribute('stroke-linecap', 'round');
382
+ svg.setAttribute('stroke-linejoin', 'round');
383
+ var c = document.createElementNS(ns, 'circle');
384
+ c.setAttribute('cx', '13.5'); c.setAttribute('cy', '6.5'); c.setAttribute('r', '.5'); c.setAttribute('fill', 'currentColor');
385
+ var c2 = document.createElementNS(ns, 'circle');
386
+ c2.setAttribute('cx', '17.5'); c2.setAttribute('cy', '10.5'); c2.setAttribute('r', '.5'); c2.setAttribute('fill', 'currentColor');
387
+ var c3 = document.createElementNS(ns, 'circle');
388
+ c3.setAttribute('cx', '8.5'); c3.setAttribute('cy', '7.5'); c3.setAttribute('r', '.5'); c3.setAttribute('fill', 'currentColor');
389
+ var c4 = document.createElementNS(ns, 'circle');
390
+ c4.setAttribute('cx', '6.5'); c4.setAttribute('cy', '12.5'); c4.setAttribute('r', '.5'); c4.setAttribute('fill', 'currentColor');
391
+ var p1 = document.createElementNS(ns, 'path');
392
+ p1.setAttribute('d', 'M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z');
393
+ svg.appendChild(p1); svg.appendChild(c); svg.appendChild(c2); svg.appendChild(c3); svg.appendChild(c4);
394
+ return svg;
395
+ }
396
+
397
+ function initThemeSwitcher() {
398
+ // Auto-populate empty theme switcher containers
399
+ document.querySelectorAll('.db-theme-switcher').forEach(function(sw) {
400
+ if (sw.querySelector('.db-theme-switcher__toggle')) return;
401
+ var toggle = document.createElement('button');
402
+ toggle.className = 'db-theme-switcher__toggle';
403
+ toggle.setAttribute('aria-label', 'Open theme picker');
404
+ toggle.setAttribute('aria-expanded', 'false');
405
+ toggle.appendChild(_createPaletteIcon());
406
+ var popover = document.createElement('div');
407
+ popover.className = 'db-theme-switcher__popover';
408
+ sw.appendChild(toggle);
409
+ sw.appendChild(popover);
410
+ });
411
+
412
+ // Build popover content
413
+ document.querySelectorAll('.db-theme-switcher__popover').forEach(function(pop) {
414
+ if (!pop._dbBuilt) { pop._dbBuilt = true; buildPopoverContent(pop); }
415
+ });
416
+
417
+ // Popover toggle
418
+ document.querySelectorAll('.db-theme-switcher__toggle').forEach(function(btn) {
419
+ if (btn._dbInit) return;
420
+ btn._dbInit = true;
421
+ btn.addEventListener('click', function(e) {
422
+ e.stopPropagation();
423
+ var popover = btn.parentElement.querySelector('.db-theme-switcher__popover');
424
+ if (!popover) return;
425
+ var open = popover.hasAttribute('data-open');
426
+ if (open) { popover.removeAttribute('data-open'); btn.setAttribute('aria-expanded','false'); }
427
+ else { popover.setAttribute('data-open',''); btn.setAttribute('aria-expanded','true'); }
428
+ });
429
+ });
430
+
431
+ // Close popover on outside click
432
+ if (!document._dbPopoverClose) {
433
+ document._dbPopoverClose = true;
434
+ document.addEventListener('click', function(e) {
435
+ document.querySelectorAll('.db-theme-switcher__popover[data-open]').forEach(function(pop) {
436
+ var toggle = pop.parentElement.querySelector('.db-theme-switcher__toggle');
437
+ if (!pop.contains(e.target) && (!toggle || !toggle.contains(e.target))) {
438
+ pop.removeAttribute('data-open');
439
+ if (toggle) toggle.setAttribute('aria-expanded','false');
440
+ }
441
+ });
442
+ });
443
+ }
444
+
445
+ // Legacy data-theme buttons
446
+ document.querySelectorAll('.db-showcase__theme-swatch[data-theme]').forEach(function(btn) {
447
+ if (btn._dbInit) return; btn._dbInit = true;
448
+ btn.addEventListener('click', function() { var t = btn.getAttribute('data-theme'); if (t) setTheme(t); });
449
+ });
450
+
451
+ // Family buttons (dots + configurator swatches)
452
+ document.querySelectorAll('[data-family]').forEach(function(btn) {
453
+ if (btn._dbInit) return; btn._dbInit = true;
454
+ btn.addEventListener('click', function() { var f = btn.getAttribute('data-family'); if (f) setFamily(f); });
455
+ });
456
+
457
+ // Scheme buttons
458
+ document.querySelectorAll('[data-scheme]').forEach(function(btn) {
459
+ if (btn._dbInit) return; btn._dbInit = true;
460
+ btn.addEventListener('click', function() { var s = btn.getAttribute('data-scheme'); if (s) setScheme(s); });
461
+ });
462
+
463
+ // Accent picker
464
+ document.querySelectorAll('.db-accent-picker__dot').forEach(function(btn) {
465
+ if (btn._dbInit) return; btn._dbInit = true;
466
+ btn.addEventListener('click', function() {
467
+ var accent = btn.getAttribute('data-accent');
468
+ if (accent === 'reset') resetAccent();
469
+ else if (accent) setAccent(accent);
470
+ });
471
+ });
472
+
473
+ updateSwitcherUI();
474
+ }
475
+
476
+ function updateSwitcherUI() {
477
+ var current = getTheme();
478
+ var currentFamily = getFamily();
479
+
480
+ // Legacy theme buttons
481
+ document.querySelectorAll('.db-showcase__theme-swatch[data-theme]').forEach(function(btn) {
482
+ btn.setAttribute('aria-pressed', String(btn.getAttribute('data-theme') === current));
483
+ });
484
+
485
+ // Family buttons (dots + swatches)
486
+ document.querySelectorAll('[data-family]').forEach(function(btn) {
487
+ btn.setAttribute('aria-pressed', String(btn.getAttribute('data-family') === currentFamily));
488
+ });
489
+
490
+ // Scheme buttons
491
+ document.querySelectorAll('[data-scheme]').forEach(function(btn) {
492
+ btn.setAttribute('aria-pressed', String(btn.getAttribute('data-scheme') === _scheme));
493
+ });
494
+
495
+ updateAccentPickerUI();
496
+ }
497
+
498
+ /* ----------------------------------------------------------
499
+ Switch / Toggle
500
+ ---------------------------------------------------------- */
501
+ function initSwitches(root) {
502
+ root.querySelectorAll('.db-switch').forEach(function(sw) {
503
+ if (sw._dbInit) return;
504
+ sw._dbInit = true;
505
+
506
+ if (!sw.hasAttribute('role')) sw.setAttribute('role', 'switch');
507
+ if (!sw.hasAttribute('tabindex')) sw.setAttribute('tabindex', '0');
508
+ if (!sw.hasAttribute('aria-checked')) sw.setAttribute('aria-checked', 'false');
509
+
510
+ sw.addEventListener('click', toggleSwitch);
511
+ sw.addEventListener('keydown', function(e) {
512
+ if (e.key === ' ' || e.key === 'Enter') {
513
+ e.preventDefault();
514
+ toggleSwitch.call(sw);
515
+ }
516
+ });
517
+ });
518
+ }
519
+
520
+ function toggleSwitch() {
521
+ var on = this.getAttribute('aria-checked') === 'true';
522
+ this.setAttribute('aria-checked', String(!on));
523
+ this.dispatchEvent(new CustomEvent('db:change', { detail: { checked: !on } }));
524
+ }
525
+
526
+ /* ----------------------------------------------------------
527
+ Tabs
528
+ ---------------------------------------------------------- */
529
+ function initTabs(root) {
530
+ root.querySelectorAll('.db-tabs').forEach(function(tabs) {
531
+ if (tabs._dbInit) return;
532
+ tabs._dbInit = true;
533
+
534
+ var tabList = tabs.querySelector('.db-tabs__list');
535
+ var tabBtns = Array.from(tabList.querySelectorAll('.db-tabs__tab'));
536
+ var panels = Array.from(tabs.querySelectorAll('.db-tabs__panel'));
537
+
538
+ tabList.setAttribute('role', 'tablist');
539
+
540
+ tabBtns.forEach(function(btn, i) {
541
+ btn.setAttribute('role', 'tab');
542
+ if (!btn.id) btn.id = 'db-tab-' + uid();
543
+ var panel = panels[i];
544
+ if (panel) {
545
+ panel.setAttribute('role', 'tabpanel');
546
+ if (!panel.id) panel.id = 'db-panel-' + uid();
547
+ btn.setAttribute('aria-controls', panel.id);
548
+ panel.setAttribute('aria-labelledby', btn.id);
549
+ }
550
+ var isSelected = btn.getAttribute('aria-selected') === 'true';
551
+ if (!isSelected && i === 0 && !tabBtns.some(function(b) { return b.getAttribute('aria-selected') === 'true'; })) {
552
+ isSelected = true;
553
+ }
554
+ setTabState(btn, panel, isSelected);
555
+
556
+ btn.addEventListener('click', function() {
557
+ selectTab(tabBtns, panels, i);
558
+ });
559
+ });
560
+
561
+ tabList.addEventListener('keydown', function(e) {
562
+ var idx = tabBtns.indexOf(document.activeElement);
563
+ if (idx === -1) return;
564
+ var next = -1;
565
+ if (e.key === 'ArrowRight') next = (idx + 1) % tabBtns.length;
566
+ else if (e.key === 'ArrowLeft') next = (idx - 1 + tabBtns.length) % tabBtns.length;
567
+ else if (e.key === 'Home') next = 0;
568
+ else if (e.key === 'End') next = tabBtns.length - 1;
569
+ if (next >= 0) {
570
+ e.preventDefault();
571
+ tabBtns[next].focus();
572
+ selectTab(tabBtns, panels, next);
573
+ }
574
+ });
575
+ });
576
+ }
577
+
578
+ function selectTab(tabBtns, panels, idx) {
579
+ tabBtns.forEach(function(btn, i) {
580
+ setTabState(btn, panels[i], i === idx);
581
+ });
582
+ }
583
+
584
+ function setTabState(btn, panel, selected) {
585
+ btn.setAttribute('aria-selected', String(selected));
586
+ btn.setAttribute('tabindex', selected ? '0' : '-1');
587
+ if (panel) {
588
+ if (selected) panel.removeAttribute('hidden');
589
+ else panel.setAttribute('hidden', '');
590
+ }
591
+ }
592
+
593
+ /* ----------------------------------------------------------
594
+ Modal
595
+ ---------------------------------------------------------- */
596
+ function initModals(root) {
597
+ root.querySelectorAll('[data-db-modal-trigger]').forEach(function(trigger) {
598
+ if (trigger._dbInit) return;
599
+ trigger._dbInit = true;
600
+
601
+ trigger.addEventListener('click', function() {
602
+ var id = trigger.getAttribute('data-db-modal-trigger');
603
+ openModal(id, trigger);
604
+ });
605
+ });
606
+
607
+ root.querySelectorAll('.db-modal-overlay').forEach(function(overlay) {
608
+ if (overlay._dbInit) return;
609
+ overlay._dbInit = true;
610
+
611
+ overlay.addEventListener('click', function(e) {
612
+ if (e.target === overlay) closeModal(overlay);
613
+ });
614
+
615
+ var closeBtn = overlay.querySelector('.db-modal__close');
616
+ if (closeBtn) {
617
+ closeBtn.addEventListener('click', function() {
618
+ closeModal(overlay);
619
+ });
620
+ }
621
+ });
622
+
623
+ document.addEventListener('keydown', function(e) {
624
+ if (e.key === 'Escape') {
625
+ var open = document.querySelector('.db-modal--open');
626
+ if (open) closeModal(open);
627
+ }
628
+ });
629
+ }
630
+
631
+ var _lastModalTrigger = null;
632
+
633
+ function openModal(id, trigger) {
634
+ var overlay = typeof id === 'string' ? document.getElementById(id) : id;
635
+ if (!overlay) return;
636
+ _lastModalTrigger = trigger || null;
637
+ overlay.classList.add('db-modal--open');
638
+ overlay.setAttribute('aria-hidden', 'false');
639
+
640
+ var modal = overlay.querySelector('.db-modal');
641
+ if (modal) {
642
+ var focusable = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
643
+ if (focusable.length) focusable[0].focus();
644
+
645
+ overlay._dbTrap = function(e) {
646
+ if (e.key !== 'Tab') return;
647
+ var first = focusable[0];
648
+ var last = focusable[focusable.length - 1];
649
+ if (e.shiftKey) {
650
+ if (document.activeElement === first) { e.preventDefault(); last.focus(); }
651
+ } else {
652
+ if (document.activeElement === last) { e.preventDefault(); first.focus(); }
653
+ }
654
+ };
655
+ overlay.addEventListener('keydown', overlay._dbTrap);
656
+ }
657
+
658
+ document.body.style.overflow = 'hidden';
659
+ }
660
+
661
+ function closeModal(overlay) {
662
+ overlay = typeof overlay === 'string' ? document.getElementById(overlay) : overlay;
663
+ if (!overlay) return;
664
+ overlay.classList.remove('db-modal--open');
665
+ overlay.setAttribute('aria-hidden', 'true');
666
+ if (overlay._dbTrap) {
667
+ overlay.removeEventListener('keydown', overlay._dbTrap);
668
+ overlay._dbTrap = null;
669
+ }
670
+ document.body.style.overflow = '';
671
+ if (_lastModalTrigger) {
672
+ _lastModalTrigger.focus();
673
+ _lastModalTrigger = null;
674
+ }
675
+ }
676
+
677
+ /* ----------------------------------------------------------
678
+ Toast — built with safe DOM methods (no innerHTML)
679
+ ---------------------------------------------------------- */
680
+ function getToastStack() {
681
+ var stack = document.querySelector('.db-toast-stack');
682
+ if (!stack) {
683
+ stack = document.createElement('div');
684
+ stack.className = 'db-toast-stack';
685
+ stack.setAttribute('aria-live', 'polite');
686
+ stack.setAttribute('role', 'status');
687
+ document.body.appendChild(stack);
688
+ }
689
+ return stack;
690
+ }
691
+
692
+ function createSvgIcon(type) {
693
+ var ns = 'http://www.w3.org/2000/svg';
694
+ var svg = document.createElementNS(ns, 'svg');
695
+ svg.setAttribute('viewBox', '0 0 24 24');
696
+ svg.setAttribute('fill', 'none');
697
+ svg.setAttribute('stroke', 'currentColor');
698
+ svg.setAttribute('stroke-width', '2');
699
+ svg.setAttribute('stroke-linecap', 'round');
700
+ svg.setAttribute('stroke-linejoin', 'round');
701
+
702
+ function el(tag, attrs) {
703
+ var e = document.createElementNS(ns, tag);
704
+ for (var k in attrs) e.setAttribute(k, attrs[k]);
705
+ return e;
706
+ }
707
+
708
+ if (type === 'success') {
709
+ svg.appendChild(el('path', { d: 'M22 11.08V12a10 10 0 1 1-5.93-9.14' }));
710
+ svg.appendChild(el('polyline', { points: '22 4 12 14.01 9 11.01' }));
711
+ } else if (type === 'error') {
712
+ svg.appendChild(el('circle', { cx: '12', cy: '12', r: '10' }));
713
+ svg.appendChild(el('line', { x1: '15', y1: '9', x2: '9', y2: '15' }));
714
+ svg.appendChild(el('line', { x1: '9', y1: '9', x2: '15', y2: '15' }));
715
+ } else if (type === 'warning') {
716
+ svg.appendChild(el('path', { d: 'M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z' }));
717
+ svg.appendChild(el('line', { x1: '12', y1: '9', x2: '12', y2: '13' }));
718
+ svg.appendChild(el('line', { x1: '12', y1: '17', x2: '12.01', y2: '17' }));
719
+ } else {
720
+ svg.appendChild(el('circle', { cx: '12', cy: '12', r: '10' }));
721
+ svg.appendChild(el('line', { x1: '12', y1: '16', x2: '12', y2: '12' }));
722
+ svg.appendChild(el('line', { x1: '12', y1: '8', x2: '12.01', y2: '8' }));
723
+ }
724
+ return svg;
725
+ }
726
+
727
+ function createCloseIcon() {
728
+ var ns = 'http://www.w3.org/2000/svg';
729
+ var svg = document.createElementNS(ns, 'svg');
730
+ svg.setAttribute('viewBox', '0 0 24 24');
731
+ svg.setAttribute('fill', 'none');
732
+ svg.setAttribute('stroke', 'currentColor');
733
+ svg.setAttribute('stroke-width', '2');
734
+ svg.setAttribute('stroke-linecap', 'round');
735
+ svg.setAttribute('stroke-linejoin', 'round');
736
+ svg.setAttribute('width', '16');
737
+ svg.setAttribute('height', '16');
738
+ var l1 = document.createElementNS(ns, 'line');
739
+ l1.setAttribute('x1', '18'); l1.setAttribute('y1', '6');
740
+ l1.setAttribute('x2', '6'); l1.setAttribute('y2', '18');
741
+ var l2 = document.createElementNS(ns, 'line');
742
+ l2.setAttribute('x1', '6'); l2.setAttribute('y1', '6');
743
+ l2.setAttribute('x2', '18'); l2.setAttribute('y2', '18');
744
+ svg.appendChild(l1);
745
+ svg.appendChild(l2);
746
+ return svg;
747
+ }
748
+
749
+ function toast(opts) {
750
+ if (typeof opts === 'string') opts = { message: opts };
751
+ opts = opts || {};
752
+ var type = opts.type || 'info';
753
+ var title = opts.title || '';
754
+ var message = opts.message || '';
755
+ var duration = opts.duration !== undefined ? opts.duration : 4000;
756
+
757
+ var el = document.createElement('div');
758
+ el.className = 'db-toast db-toast--' + type;
759
+
760
+ // Icon
761
+ var iconWrap = document.createElement('span');
762
+ iconWrap.className = 'db-toast__icon';
763
+ iconWrap.appendChild(createSvgIcon(type));
764
+ el.appendChild(iconWrap);
765
+
766
+ // Content
767
+ var content = document.createElement('div');
768
+ content.className = 'db-toast__content';
769
+ if (title) {
770
+ var titleEl = document.createElement('div');
771
+ titleEl.className = 'db-toast__title';
772
+ titleEl.textContent = title;
773
+ content.appendChild(titleEl);
774
+ }
775
+ var msgEl = document.createElement('div');
776
+ msgEl.className = 'db-toast__message';
777
+ msgEl.textContent = message;
778
+ content.appendChild(msgEl);
779
+ el.appendChild(content);
780
+
781
+ // Close button
782
+ var closeBtn = document.createElement('button');
783
+ closeBtn.className = 'db-toast__close';
784
+ closeBtn.setAttribute('aria-label', 'Dismiss');
785
+ closeBtn.appendChild(createCloseIcon());
786
+ closeBtn.addEventListener('click', function() { removeToast(el); });
787
+ el.appendChild(closeBtn);
788
+
789
+ var stack = getToastStack();
790
+ stack.appendChild(el);
791
+
792
+ if (duration > 0) {
793
+ setTimeout(function() { removeToast(el); }, duration);
794
+ }
795
+
796
+ return el;
797
+ }
798
+
799
+ function removeToast(el) {
800
+ if (!el || !el.parentNode) return;
801
+ el.classList.add('db-toast--removing');
802
+ setTimeout(function() {
803
+ if (el.parentNode) el.parentNode.removeChild(el);
804
+ }, 200);
805
+ }
806
+
807
+ /* ----------------------------------------------------------
808
+ Stepper
809
+ ---------------------------------------------------------- */
810
+ function initSteppers(root) {
811
+ root.querySelectorAll('.db-stepper').forEach(function(stepper) {
812
+ if (stepper._dbInit) return;
813
+ stepper._dbInit = true;
814
+ });
815
+ }
816
+
817
+ /* ----------------------------------------------------------
818
+ Tooltip
819
+ ---------------------------------------------------------- */
820
+ function initTooltips(root) {
821
+ root.querySelectorAll('.db-tooltip-wrap').forEach(function(wrap) {
822
+ if (wrap._dbInit) return;
823
+ wrap._dbInit = true;
824
+
825
+ var tip = wrap.querySelector('.db-tooltip');
826
+ if (!tip) return;
827
+
828
+ if (!tip.id) tip.id = 'db-tip-' + uid();
829
+ var trigger = wrap.querySelector('[data-db-tooltip]') || wrap.children[0];
830
+ if (trigger) trigger.setAttribute('aria-describedby', tip.id);
831
+ tip.setAttribute('role', 'tooltip');
832
+ });
833
+ }
834
+
835
+ /* ----------------------------------------------------------
836
+ Slider — sync value display
837
+ ---------------------------------------------------------- */
838
+ function initSliders(root) {
839
+ root.querySelectorAll('.db-slider').forEach(function(slider) {
840
+ if (slider._dbInit) return;
841
+ slider._dbInit = true;
842
+
843
+ var input = slider.querySelector('.db-slider__input');
844
+ var valueEl = slider.querySelector('.db-slider__value');
845
+ if (!input || !valueEl) return;
846
+
847
+ var update = function() { valueEl.textContent = input.value; };
848
+ input.addEventListener('input', update);
849
+ update();
850
+ });
851
+ }
852
+
853
+ /* ----------------------------------------------------------
854
+ Temperature — cold (-1) to neutral (0) to warm (+1)
855
+ ---------------------------------------------------------- */
856
+ function initTemperature() {
857
+ var saved = localStorage.getItem('db-temperature');
858
+ if (saved !== null) {
859
+ document.documentElement.style.setProperty('--db-temperature', saved);
860
+ }
861
+
862
+ document.querySelectorAll('[data-db-temperature]').forEach(function(slider) {
863
+ var input = slider.querySelector('.db-slider__input');
864
+ var valueEl = slider.querySelector('.db-slider__value');
865
+ if (!input) return;
866
+
867
+ if (saved !== null) {
868
+ input.value = Math.round(parseFloat(saved) * 100);
869
+ if (valueEl) valueEl.textContent = input.value;
870
+ }
871
+
872
+ input.addEventListener('input', function() {
873
+ var val = input.value / 100;
874
+ document.documentElement.style.setProperty('--db-temperature', val);
875
+ localStorage.setItem('db-temperature', val);
876
+ if (valueEl) valueEl.textContent = input.value;
877
+ });
878
+ });
879
+ }
880
+
881
+ /* ----------------------------------------------------------
882
+ Noise Control
883
+ CSS variable --db-noise (0-1) controls grain texture opacity.
884
+ Persists via localStorage.
885
+ ---------------------------------------------------------- */
886
+ // Non-linear curve for noise slider: gives fine control in the low range
887
+ // slider 0→0, 25→0.09, 50→0.18, 75→0.49, 100→1.0
888
+ function noiseSliderToCSS(v) { return Math.pow(v / 100, 2.5); }
889
+ function noiseCSSToSlider(c) { return Math.round(Math.pow(c, 1 / 2.5) * 100); }
890
+
891
+ function initNoise() {
892
+ var saved = localStorage.getItem('db-noise');
893
+ if (saved !== null) {
894
+ document.documentElement.style.setProperty('--db-noise', saved);
895
+ }
896
+
897
+ document.querySelectorAll('[data-db-noise]').forEach(function(slider) {
898
+ var input = slider.querySelector('.db-slider__input');
899
+ var valueEl = slider.querySelector('.db-slider__value');
900
+ if (!input) return;
901
+
902
+ if (saved !== null) {
903
+ var sliderPos = noiseCSSToSlider(parseFloat(saved));
904
+ input.value = sliderPos;
905
+ if (valueEl) valueEl.textContent = sliderPos;
906
+ }
907
+
908
+ input.addEventListener('input', function() {
909
+ var cssVal = noiseSliderToCSS(parseInt(input.value));
910
+ var rounded = Math.round(cssVal * 1000) / 1000;
911
+ document.documentElement.style.setProperty('--db-noise', rounded);
912
+ localStorage.setItem('db-noise', rounded);
913
+ if (valueEl) valueEl.textContent = input.value;
914
+ });
915
+ });
916
+ }
917
+
918
+ /* ----------------------------------------------------------
919
+ Texture Type Control
920
+ Sets data-db-texture on <html>: grain (default), paper, metal, wood, glass, none.
921
+ Persists via localStorage.
922
+ ---------------------------------------------------------- */
923
+ function initTexture() {
924
+ var saved = localStorage.getItem('db-texture') || 'grain';
925
+ document.documentElement.setAttribute('data-db-texture', saved);
926
+
927
+ document.querySelectorAll('[data-db-texture-btn]').forEach(function(btn) {
928
+ var type = btn.getAttribute('data-db-texture-btn');
929
+ btn.setAttribute('aria-pressed', type === saved ? 'true' : 'false');
930
+
931
+ btn.addEventListener('click', function() {
932
+ document.documentElement.setAttribute('data-db-texture', type);
933
+ localStorage.setItem('db-texture', type);
934
+ document.querySelectorAll('[data-db-texture-btn]').forEach(function(b) {
935
+ b.setAttribute('aria-pressed', b.getAttribute('data-db-texture-btn') === type ? 'true' : 'false');
936
+ });
937
+ });
938
+ });
939
+ }
940
+
941
+ /* ----------------------------------------------------------
942
+ Nested Border Radius
943
+ innerRadius = outerRadius - padding
944
+ Auto-applies to elements with [data-db-radius] or known containers.
945
+ ---------------------------------------------------------- */
946
+ function fixNestedRadius(root) {
947
+ root = root || document;
948
+ var containers = root.querySelectorAll('.db-card, .db-modal, .db-sheet, .db-drawer, .db-alert-dialog, .db-showcase__frame, [data-db-radius]');
949
+ containers.forEach(function(el) {
950
+ var style = getComputedStyle(el);
951
+ var outerR = parseFloat(style.borderTopLeftRadius) || 0;
952
+ if (outerR < 2) return;
953
+ var padTop = parseFloat(style.paddingTop) || 0;
954
+ var padLeft = parseFloat(style.paddingLeft) || 0;
955
+ var gap = Math.max(padTop, padLeft);
956
+ if (gap < 1) return;
957
+ var innerR = Math.max(0, outerR - gap);
958
+ propagateRadius(el, innerR);
959
+ });
960
+ }
961
+
962
+ var RADIUS_SKIP = /\bdb-(btn|input|field|textarea|switch|slider|checkbox|radio|toggle|badge|avatar|alert|chip|kbd|spinner|select|custom-select|search|otp|progress|pagination|stepper|tabs|separator|divider|nav-menu|bottom-nav|breadcrumbs|carousel|calendar|popover|tooltip|hover-card|dropdown|context-menu|command|stat|chart-card)/;
963
+
964
+ function propagateRadius(parent, innerR) {
965
+ Array.from(parent.children).forEach(function(child) {
966
+ if (child.nodeType !== 1) return;
967
+ if (RADIUS_SKIP.test(child.className)) return;
968
+ var cs = getComputedStyle(child);
969
+ var hasBg = cs.backgroundColor !== 'rgba(0, 0, 0, 0)' && cs.backgroundColor !== 'transparent';
970
+ var hasBorder = cs.borderTopWidth !== '0px' && cs.borderTopStyle !== 'none';
971
+ var hasRadius = (parseFloat(cs.borderTopLeftRadius) || 0) > 0;
972
+ if (hasBg || hasBorder || hasRadius) {
973
+ child.style.borderRadius = innerR + 'px';
974
+ } else {
975
+ propagateRadius(child, innerR);
976
+ }
977
+ });
978
+ }
979
+
980
+ /* ----------------------------------------------------------
981
+ Checkbox (CSS handles visual sync via :checked)
982
+ ---------------------------------------------------------- */
983
+ function initCheckboxes(root) {
984
+ root.querySelectorAll('.db-checkbox').forEach(function(label) {
985
+ if (label._dbInit) return;
986
+ label._dbInit = true;
987
+ });
988
+ }
989
+
990
+ /* ----------------------------------------------------------
991
+ Radio (native inputs handle group management)
992
+ ---------------------------------------------------------- */
993
+ function initRadios(root) {
994
+ root.querySelectorAll('.db-radio-group').forEach(function(group) {
995
+ if (group._dbInit) return;
996
+ group._dbInit = true;
997
+ });
998
+ }
999
+
1000
+ /* ----------------------------------------------------------
1001
+ Helpers
1002
+ ---------------------------------------------------------- */
1003
+ var _uid = 0;
1004
+ function uid() { return 'db' + (++_uid) + '_' + Math.random().toString(36).slice(2, 6); }
1005
+
1006
+ /* ----------------------------------------------------------
1007
+ Accordion
1008
+ ---------------------------------------------------------- */
1009
+ var _dbAccordionInit = false;
1010
+ function initAccordions(root) {
1011
+ if (_dbAccordionInit && root === document) return;
1012
+ root.querySelectorAll('.db-accordion').forEach(function(acc) {
1013
+ if (acc._dbInit) return;
1014
+ acc._dbInit = true;
1015
+ acc.querySelectorAll('.db-accordion__trigger').forEach(function(trigger) {
1016
+ trigger.addEventListener('click', function() {
1017
+ var item = trigger.closest('.db-accordion__item');
1018
+ if (!item) return;
1019
+ var isOpen = item.classList.contains('db-accordion__item--open');
1020
+ // Close siblings if single mode (default)
1021
+ if (!acc.hasAttribute('data-multi')) {
1022
+ acc.querySelectorAll('.db-accordion__item--open').forEach(function(openItem) {
1023
+ openItem.classList.remove('db-accordion__item--open');
1024
+ openItem.querySelector('.db-accordion__trigger').setAttribute('aria-expanded', 'false');
1025
+ });
1026
+ }
1027
+ if (!isOpen) {
1028
+ item.classList.add('db-accordion__item--open');
1029
+ trigger.setAttribute('aria-expanded', 'true');
1030
+ } else {
1031
+ item.classList.remove('db-accordion__item--open');
1032
+ trigger.setAttribute('aria-expanded', 'false');
1033
+ }
1034
+ });
1035
+ });
1036
+ });
1037
+ if (root === document) _dbAccordionInit = true;
1038
+ }
1039
+
1040
+ /* ----------------------------------------------------------
1041
+ Collapsible
1042
+ ---------------------------------------------------------- */
1043
+ var _dbCollapsibleInit = false;
1044
+ function initCollapsibles(root) {
1045
+ if (_dbCollapsibleInit && root === document) return;
1046
+ root.querySelectorAll('.db-collapsible').forEach(function(col) {
1047
+ if (col._dbInit) return;
1048
+ col._dbInit = true;
1049
+ var trigger = col.querySelector('.db-collapsible__trigger');
1050
+ if (!trigger) return;
1051
+ trigger.addEventListener('click', function() {
1052
+ var isOpen = col.classList.contains('db-collapsible--open');
1053
+ col.classList.toggle('db-collapsible--open');
1054
+ trigger.setAttribute('aria-expanded', String(!isOpen));
1055
+ });
1056
+ });
1057
+ if (root === document) _dbCollapsibleInit = true;
1058
+ }
1059
+
1060
+ /* ----------------------------------------------------------
1061
+ Alert Dialog
1062
+ ---------------------------------------------------------- */
1063
+ function openAlertDialog(id) {
1064
+ var dialog = document.getElementById(id);
1065
+ if (!dialog) return;
1066
+ dialog.classList.add('db-alert-dialog--open');
1067
+ var cancel = dialog.querySelector('[data-action="cancel"]');
1068
+ if (cancel) cancel.focus();
1069
+ }
1070
+
1071
+ function closeAlertDialog(id) {
1072
+ var dialog = document.getElementById(id);
1073
+ if (!dialog) return;
1074
+ dialog.classList.remove('db-alert-dialog--open');
1075
+ }
1076
+
1077
+ function initAlertDialogs(root) {
1078
+ root.querySelectorAll('.db-alert-dialog').forEach(function(dialog) {
1079
+ if (dialog._dbInit) return;
1080
+ dialog._dbInit = true;
1081
+ dialog.querySelector('.db-alert-dialog__overlay')?.addEventListener('click', function() {
1082
+ dialog.classList.remove('db-alert-dialog--open');
1083
+ });
1084
+ dialog.querySelectorAll('[data-action="cancel"]').forEach(function(btn) {
1085
+ btn.addEventListener('click', function() {
1086
+ dialog.classList.remove('db-alert-dialog--open');
1087
+ });
1088
+ });
1089
+ });
1090
+ }
1091
+
1092
+ /* ----------------------------------------------------------
1093
+ Sheet
1094
+ ---------------------------------------------------------- */
1095
+ function openSheet(id) {
1096
+ var sheet = document.getElementById(id);
1097
+ if (!sheet) return;
1098
+ sheet.classList.add('db-sheet--open');
1099
+ }
1100
+
1101
+ function closeSheet(id) {
1102
+ var sheet = document.getElementById(id);
1103
+ if (!sheet) return;
1104
+ sheet.classList.remove('db-sheet--open');
1105
+ }
1106
+
1107
+ function initSheets(root) {
1108
+ root.querySelectorAll('.db-sheet').forEach(function(sheet) {
1109
+ if (sheet._dbInit) return;
1110
+ sheet._dbInit = true;
1111
+ sheet.querySelector('.db-sheet__overlay')?.addEventListener('click', function() {
1112
+ sheet.classList.remove('db-sheet--open');
1113
+ });
1114
+ sheet.querySelectorAll('.db-sheet__close').forEach(function(btn) {
1115
+ btn.addEventListener('click', function() {
1116
+ sheet.classList.remove('db-sheet--open');
1117
+ });
1118
+ });
1119
+ });
1120
+ }
1121
+
1122
+ /* ----------------------------------------------------------
1123
+ Drawer
1124
+ ---------------------------------------------------------- */
1125
+ function openDrawer(id) {
1126
+ var drawer = document.getElementById(id);
1127
+ if (!drawer) return;
1128
+ drawer.classList.add('db-drawer--open');
1129
+ }
1130
+
1131
+ function closeDrawer(id) {
1132
+ var drawer = document.getElementById(id);
1133
+ if (!drawer) return;
1134
+ drawer.classList.remove('db-drawer--open');
1135
+ }
1136
+
1137
+ function initDrawers(root) {
1138
+ root.querySelectorAll('.db-drawer').forEach(function(drawer) {
1139
+ if (drawer._dbInit) return;
1140
+ drawer._dbInit = true;
1141
+ drawer.querySelector('.db-drawer__overlay')?.addEventListener('click', function() {
1142
+ drawer.classList.remove('db-drawer--open');
1143
+ });
1144
+ });
1145
+ }
1146
+
1147
+ /* ----------------------------------------------------------
1148
+ Popover
1149
+ ---------------------------------------------------------- */
1150
+ var _dbPopoverInit = false;
1151
+ function initPopovers(root) {
1152
+ if (_dbPopoverInit && root === document) return;
1153
+ root.querySelectorAll('.db-popover').forEach(function(pop) {
1154
+ if (pop._dbInit) return;
1155
+ pop._dbInit = true;
1156
+ var trigger = pop.querySelector('.db-popover__trigger');
1157
+ if (!trigger) return;
1158
+ trigger.addEventListener('click', function(e) {
1159
+ e.stopPropagation();
1160
+ pop.classList.toggle('db-popover--open');
1161
+ });
1162
+ });
1163
+ document.addEventListener('click', function() {
1164
+ document.querySelectorAll('.db-popover--open').forEach(function(p) {
1165
+ p.classList.remove('db-popover--open');
1166
+ });
1167
+ });
1168
+ if (root === document) _dbPopoverInit = true;
1169
+ }
1170
+
1171
+ /* ----------------------------------------------------------
1172
+ Context Menu
1173
+ ---------------------------------------------------------- */
1174
+ var _dbCtxInit = false;
1175
+ function initContextMenus(root) {
1176
+ if (_dbCtxInit && root === document) return;
1177
+ root.querySelectorAll('[data-context-menu]').forEach(function(el) {
1178
+ if (el._dbCtx) return;
1179
+ el._dbCtx = true;
1180
+ var menuId = el.getAttribute('data-context-menu');
1181
+ el.addEventListener('contextmenu', function(e) {
1182
+ e.preventDefault();
1183
+ document.querySelectorAll('.db-context-menu--open').forEach(function(m) {
1184
+ m.classList.remove('db-context-menu--open');
1185
+ });
1186
+ var menu = document.getElementById(menuId);
1187
+ if (!menu) return;
1188
+ menu.style.left = e.clientX + 'px';
1189
+ menu.style.top = e.clientY + 'px';
1190
+ menu.classList.add('db-context-menu--open');
1191
+ });
1192
+ });
1193
+ document.addEventListener('click', function() {
1194
+ document.querySelectorAll('.db-context-menu--open').forEach(function(m) {
1195
+ m.classList.remove('db-context-menu--open');
1196
+ });
1197
+ });
1198
+ if (root === document) _dbCtxInit = true;
1199
+ }
1200
+
1201
+ /* ----------------------------------------------------------
1202
+ Dropdown Menu
1203
+ ---------------------------------------------------------- */
1204
+ var _dbDropInit = false;
1205
+ var _dbDropClickInit = false;
1206
+ function initDropdowns(root) {
1207
+ if (_dbDropInit && root === document) return;
1208
+ root.querySelectorAll('.db-dropdown').forEach(function(drop) {
1209
+ if (drop._dbInit) return;
1210
+ drop._dbInit = true;
1211
+ var trigger = drop.querySelector('.db-dropdown__trigger');
1212
+ if (!trigger) return;
1213
+ trigger.addEventListener('click', function(e) {
1214
+ e.stopPropagation();
1215
+ var wasOpen = drop.classList.contains('db-dropdown--open');
1216
+ document.querySelectorAll('.db-dropdown--open').forEach(function(d) {
1217
+ d.classList.remove('db-dropdown--open');
1218
+ });
1219
+ if (!wasOpen) drop.classList.add('db-dropdown--open');
1220
+ });
1221
+ });
1222
+ if (!_dbDropClickInit) {
1223
+ _dbDropClickInit = true;
1224
+ document.addEventListener('click', function() {
1225
+ document.querySelectorAll('.db-dropdown--open').forEach(function(d) {
1226
+ d.classList.remove('db-dropdown--open');
1227
+ });
1228
+ });
1229
+ }
1230
+ if (root === document) _dbDropInit = true;
1231
+ }
1232
+
1233
+ /* ----------------------------------------------------------
1234
+ Toggle / Toggle Group
1235
+ ---------------------------------------------------------- */
1236
+ var _dbToggleInit = false;
1237
+ function initToggles(root) {
1238
+ if (_dbToggleInit && root === document) return;
1239
+ root.querySelectorAll('.db-toggle').forEach(function(toggle) {
1240
+ if (toggle._dbInit) return;
1241
+ toggle._dbInit = true;
1242
+ toggle.addEventListener('click', function() {
1243
+ var group = toggle.closest('.db-toggle-group');
1244
+ if (group && !group.hasAttribute('data-multi')) {
1245
+ group.querySelectorAll('.db-toggle').forEach(function(t) {
1246
+ t.setAttribute('aria-pressed', 'false');
1247
+ t.classList.remove('db-toggle--active');
1248
+ });
1249
+ }
1250
+ var wasActive = toggle.getAttribute('aria-pressed') === 'true';
1251
+ toggle.setAttribute('aria-pressed', String(!wasActive));
1252
+ toggle.classList.toggle('db-toggle--active');
1253
+ });
1254
+ });
1255
+ if (root === document) _dbToggleInit = true;
1256
+ }
1257
+
1258
+ /* ----------------------------------------------------------
1259
+ Custom Select
1260
+ ---------------------------------------------------------- */
1261
+ var _dbCustomSelectInit = false;
1262
+ var _dbCustomSelectClickInit = false;
1263
+ function initCustomSelects(root) {
1264
+ if (_dbCustomSelectInit && root === document) return;
1265
+ root.querySelectorAll('.db-custom-select').forEach(function(sel) {
1266
+ if (sel._dbInit) return;
1267
+ sel._dbInit = true;
1268
+ var trigger = sel.querySelector('.db-custom-select__trigger');
1269
+ if (!trigger) return;
1270
+
1271
+ trigger.addEventListener('click', function(e) {
1272
+ e.stopPropagation();
1273
+ var wasOpen = sel.classList.contains('db-custom-select--open');
1274
+ document.querySelectorAll('.db-custom-select--open').forEach(function(s) {
1275
+ s.classList.remove('db-custom-select--open');
1276
+ });
1277
+ if (!wasOpen) {
1278
+ sel.classList.add('db-custom-select--open');
1279
+ var searchInput = sel.querySelector('.db-custom-select__search input');
1280
+ if (searchInput) searchInput.focus();
1281
+ }
1282
+ });
1283
+
1284
+ sel.querySelectorAll('.db-custom-select__option').forEach(function(opt) {
1285
+ if (opt.classList.contains('db-custom-select__option--disabled')) return;
1286
+ opt.addEventListener('click', function() {
1287
+ sel.querySelectorAll('.db-custom-select__option--selected').forEach(function(s) {
1288
+ s.classList.remove('db-custom-select__option--selected');
1289
+ });
1290
+ opt.classList.add('db-custom-select__option--selected');
1291
+ var valueEl = trigger.querySelector('.db-custom-select__value') || trigger.querySelector('.db-custom-select__placeholder');
1292
+ if (valueEl) {
1293
+ valueEl.textContent = opt.textContent.trim();
1294
+ valueEl.classList.remove('db-custom-select__placeholder');
1295
+ valueEl.classList.add('db-custom-select__value');
1296
+ }
1297
+ sel.classList.remove('db-custom-select--open');
1298
+ });
1299
+ });
1300
+
1301
+ // Search filter
1302
+ var searchInput = sel.querySelector('.db-custom-select__search input');
1303
+ if (searchInput) {
1304
+ searchInput.addEventListener('input', function() {
1305
+ var q = searchInput.value.toLowerCase();
1306
+ sel.querySelectorAll('.db-custom-select__option').forEach(function(opt) {
1307
+ opt.style.display = opt.textContent.toLowerCase().indexOf(q) !== -1 ? '' : 'none';
1308
+ });
1309
+ });
1310
+ searchInput.addEventListener('click', function(e) { e.stopPropagation(); });
1311
+ }
1312
+ });
1313
+
1314
+ if (!_dbCustomSelectClickInit) {
1315
+ _dbCustomSelectClickInit = true;
1316
+ document.addEventListener('click', function() {
1317
+ document.querySelectorAll('.db-custom-select--open').forEach(function(s) {
1318
+ s.classList.remove('db-custom-select--open');
1319
+ });
1320
+ });
1321
+ }
1322
+ if (root === document) _dbCustomSelectInit = true;
1323
+ }
1324
+
1325
+ /* ----------------------------------------------------------
1326
+ Command Palette
1327
+ ---------------------------------------------------------- */
1328
+ function openCommand(id) {
1329
+ var cmd = document.getElementById(id);
1330
+ if (!cmd) return;
1331
+ cmd.classList.add('db-command--open');
1332
+ var input = cmd.querySelector('.db-command__input');
1333
+ if (input) { input.value = ''; input.focus(); }
1334
+ }
1335
+
1336
+ function closeCommand(id) {
1337
+ var cmd = document.getElementById(id);
1338
+ if (!cmd) return;
1339
+ cmd.classList.remove('db-command--open');
1340
+ }
1341
+
1342
+ function initCommands(root) {
1343
+ root.querySelectorAll('.db-command').forEach(function(cmd) {
1344
+ if (cmd._dbInit) return;
1345
+ cmd._dbInit = true;
1346
+
1347
+ cmd.querySelector('.db-command__overlay')?.addEventListener('click', function() {
1348
+ cmd.classList.remove('db-command--open');
1349
+ });
1350
+
1351
+ var input = cmd.querySelector('.db-command__input');
1352
+ if (input) {
1353
+ input.addEventListener('input', function() {
1354
+ var q = input.value.toLowerCase();
1355
+ cmd.querySelectorAll('.db-command__item').forEach(function(item) {
1356
+ item.style.display = item.textContent.toLowerCase().indexOf(q) !== -1 ? '' : 'none';
1357
+ });
1358
+ var empty = cmd.querySelector('.db-command__empty');
1359
+ if (empty) {
1360
+ var anyVisible = cmd.querySelector('.db-command__item:not([style*="display: none"])');
1361
+ empty.style.display = anyVisible ? 'none' : '';
1362
+ }
1363
+ });
1364
+
1365
+ input.addEventListener('keydown', function(e) {
1366
+ if (e.key === 'Escape') cmd.classList.remove('db-command--open');
1367
+ });
1368
+ }
1369
+ });
1370
+
1371
+ // Ctrl+K / Cmd+K global shortcut
1372
+ document.addEventListener('keydown', function(e) {
1373
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
1374
+ e.preventDefault();
1375
+ var cmd = document.querySelector('.db-command');
1376
+ if (cmd) {
1377
+ if (cmd.classList.contains('db-command--open')) {
1378
+ cmd.classList.remove('db-command--open');
1379
+ } else {
1380
+ cmd.classList.add('db-command--open');
1381
+ var input = cmd.querySelector('.db-command__input');
1382
+ if (input) { input.value = ''; input.focus(); }
1383
+ }
1384
+ }
1385
+ }
1386
+ });
1387
+ }
1388
+
1389
+ /* ----------------------------------------------------------
1390
+ Menubar
1391
+ ---------------------------------------------------------- */
1392
+ var _dbMenubarInit = false;
1393
+ function initMenubars(root) {
1394
+ if (_dbMenubarInit && root === document) return;
1395
+ root.querySelectorAll('.db-menubar').forEach(function(bar) {
1396
+ if (bar._dbInit) return;
1397
+ bar._dbInit = true;
1398
+ bar.querySelectorAll('.db-menubar__item').forEach(function(item) {
1399
+ item.addEventListener('click', function(e) {
1400
+ e.stopPropagation();
1401
+ var wasOpen = item.classList.contains('db-menubar__item--open');
1402
+ bar.querySelectorAll('.db-menubar__item--open').forEach(function(i) {
1403
+ i.classList.remove('db-menubar__item--open');
1404
+ });
1405
+ if (!wasOpen) item.classList.add('db-menubar__item--open');
1406
+ });
1407
+ item.addEventListener('mouseenter', function() {
1408
+ if (bar.querySelector('.db-menubar__item--open')) {
1409
+ bar.querySelectorAll('.db-menubar__item--open').forEach(function(i) {
1410
+ i.classList.remove('db-menubar__item--open');
1411
+ });
1412
+ item.classList.add('db-menubar__item--open');
1413
+ }
1414
+ });
1415
+ });
1416
+ });
1417
+ document.addEventListener('click', function() {
1418
+ document.querySelectorAll('.db-menubar__item--open').forEach(function(i) {
1419
+ i.classList.remove('db-menubar__item--open');
1420
+ });
1421
+ });
1422
+ if (root === document) _dbMenubarInit = true;
1423
+ }
1424
+
1425
+ /* ----------------------------------------------------------
1426
+ Calendar / Date Picker
1427
+ ---------------------------------------------------------- */
1428
+ function initCalendars(root) {
1429
+ root.querySelectorAll('.db-calendar').forEach(function(cal) {
1430
+ if (cal._dbInit) return;
1431
+ cal._dbInit = true;
1432
+ cal.querySelectorAll('.db-calendar__day').forEach(function(day) {
1433
+ if (day.classList.contains('db-calendar__day--disabled') || day.classList.contains('db-calendar__day--outside')) return;
1434
+ day.addEventListener('click', function() {
1435
+ cal.querySelectorAll('.db-calendar__day--selected').forEach(function(d) {
1436
+ d.classList.remove('db-calendar__day--selected');
1437
+ });
1438
+ day.classList.add('db-calendar__day--selected');
1439
+ });
1440
+ });
1441
+ });
1442
+
1443
+ root.querySelectorAll('.db-date-picker').forEach(function(dp) {
1444
+ if (dp._dbInit) return;
1445
+ dp._dbInit = true;
1446
+ var trigger = dp.querySelector('.db-date-picker__trigger');
1447
+ if (trigger) {
1448
+ trigger.addEventListener('click', function(e) {
1449
+ e.stopPropagation();
1450
+ dp.classList.toggle('db-date-picker--open');
1451
+ });
1452
+ }
1453
+ });
1454
+
1455
+ document.addEventListener('click', function(e) {
1456
+ document.querySelectorAll('.db-date-picker--open').forEach(function(dp) {
1457
+ if (!dp.contains(e.target)) dp.classList.remove('db-date-picker--open');
1458
+ });
1459
+ });
1460
+ }
1461
+
1462
+ /* ----------------------------------------------------------
1463
+ Carousel
1464
+ ---------------------------------------------------------- */
1465
+ function initCarousels(root) {
1466
+ root.querySelectorAll('.db-carousel').forEach(function(car) {
1467
+ if (car._dbInit) return;
1468
+ car._dbInit = true;
1469
+ var track = car.querySelector('.db-carousel__track');
1470
+ var slides = car.querySelectorAll('.db-carousel__slide');
1471
+ var dots = car.querySelectorAll('.db-carousel__dot');
1472
+ var current = 0;
1473
+
1474
+ function goTo(idx) {
1475
+ if (idx < 0) idx = slides.length - 1;
1476
+ if (idx >= slides.length) idx = 0;
1477
+ current = idx;
1478
+ track.style.transform = 'translateX(-' + (current * 100) + '%)';
1479
+ dots.forEach(function(d, i) {
1480
+ d.classList.toggle('db-carousel__dot--active', i === current);
1481
+ });
1482
+ }
1483
+
1484
+ var prev = car.querySelector('.db-carousel__btn--prev');
1485
+ var next = car.querySelector('.db-carousel__btn--next');
1486
+ if (prev) prev.addEventListener('click', function() { goTo(current - 1); });
1487
+ if (next) next.addEventListener('click', function() { goTo(current + 1); });
1488
+ dots.forEach(function(d, i) {
1489
+ d.addEventListener('click', function() { goTo(i); });
1490
+ });
1491
+ });
1492
+ }
1493
+
1494
+ /* ----------------------------------------------------------
1495
+ Input OTP
1496
+ ---------------------------------------------------------- */
1497
+ function initOTP(root) {
1498
+ root.querySelectorAll('.db-otp').forEach(function(otp) {
1499
+ if (otp._dbInit) return;
1500
+ otp._dbInit = true;
1501
+ var inputs = otp.querySelectorAll('.db-otp__input');
1502
+ inputs.forEach(function(input, idx) {
1503
+ input.setAttribute('maxlength', '1');
1504
+ input.addEventListener('input', function() {
1505
+ if (input.value.length === 1 && idx < inputs.length - 1) {
1506
+ inputs[idx + 1].focus();
1507
+ }
1508
+ });
1509
+ input.addEventListener('keydown', function(e) {
1510
+ if (e.key === 'Backspace' && !input.value && idx > 0) {
1511
+ inputs[idx - 1].focus();
1512
+ }
1513
+ });
1514
+ input.addEventListener('paste', function(e) {
1515
+ e.preventDefault();
1516
+ var data = (e.clipboardData || window.clipboardData).getData('text').trim();
1517
+ for (var i = 0; i < Math.min(data.length, inputs.length); i++) {
1518
+ inputs[i].value = data[i];
1519
+ }
1520
+ var focusIdx = Math.min(data.length, inputs.length - 1);
1521
+ inputs[focusIdx].focus();
1522
+ });
1523
+ });
1524
+ });
1525
+ }
1526
+
1527
+ /* ----------------------------------------------------------
1528
+ Resizable
1529
+ ---------------------------------------------------------- */
1530
+ function initResizables(root) {
1531
+ root.querySelectorAll('.db-resizable').forEach(function(el) {
1532
+ if (el._dbInit) return;
1533
+ el._dbInit = true;
1534
+ el.querySelectorAll('.db-resizable__handle').forEach(function(handle) {
1535
+ var startX, startY, startW, startH;
1536
+ handle.addEventListener('mousedown', function(e) {
1537
+ e.preventDefault();
1538
+ startX = e.clientX;
1539
+ startY = e.clientY;
1540
+ startW = el.offsetWidth;
1541
+ startH = el.offsetHeight;
1542
+ function onMove(ev) {
1543
+ if (handle.classList.contains('db-resizable__handle--right') || handle.classList.contains('db-resizable__handle--corner')) {
1544
+ el.style.width = (startW + ev.clientX - startX) + 'px';
1545
+ }
1546
+ if (handle.classList.contains('db-resizable__handle--bottom') || handle.classList.contains('db-resizable__handle--corner')) {
1547
+ el.style.height = (startH + ev.clientY - startY) + 'px';
1548
+ }
1549
+ }
1550
+ function onUp() {
1551
+ document.removeEventListener('mousemove', onMove);
1552
+ document.removeEventListener('mouseup', onUp);
1553
+ }
1554
+ document.addEventListener('mousemove', onMove);
1555
+ document.addEventListener('mouseup', onUp);
1556
+ });
1557
+ });
1558
+ });
1559
+ }
1560
+
1561
+ /* ----------------------------------------------------------
1562
+ Sidebar Toggle
1563
+ ---------------------------------------------------------- */
1564
+ function initSidebarToggle(root) {
1565
+ root.querySelectorAll('.db-sidebar__toggle').forEach(function(btn) {
1566
+ if (btn._dbSidebar) return;
1567
+ btn._dbSidebar = true;
1568
+ btn.addEventListener('click', function() {
1569
+ var sidebar = btn.closest('.db-sidebar');
1570
+ if (!sidebar) return;
1571
+ sidebar.classList.toggle('db-sidebar--collapsed');
1572
+ });
1573
+ });
1574
+ }
1575
+
1576
+ function toggleSidebar(el) {
1577
+ if (typeof el === 'string') el = document.querySelector(el);
1578
+ if (el) el.classList.toggle('db-sidebar--collapsed');
1579
+ }
1580
+
1581
+ /* ----------------------------------------------------------
1582
+ Chip Close
1583
+ ---------------------------------------------------------- */
1584
+ function initChipClose(root) {
1585
+ root.querySelectorAll('.db-chip__close').forEach(function(btn) {
1586
+ if (btn._dbChip) return;
1587
+ btn._dbChip = true;
1588
+ btn.addEventListener('click', function() {
1589
+ var chip = btn.closest('.db-chip');
1590
+ if (!chip) return;
1591
+ chip.style.transition = 'opacity 150ms ease, transform 150ms ease';
1592
+ chip.style.opacity = '0';
1593
+ chip.style.transform = 'scale(0.8)';
1594
+ setTimeout(function() { chip.remove(); }, 150);
1595
+ });
1596
+ });
1597
+ }
1598
+
1599
+ /* ----------------------------------------------------------
1600
+ Navbar
1601
+ ---------------------------------------------------------- */
1602
+ function initNavbars(root) {
1603
+ root.querySelectorAll('.db-navbar__toggle').forEach(function(btn) {
1604
+ if (btn._dbNavbar) return;
1605
+ btn._dbNavbar = true;
1606
+ btn.addEventListener('click', function(e) {
1607
+ e.stopPropagation();
1608
+ var navbar = btn.closest('.db-navbar');
1609
+ if (navbar) navbar.classList.toggle('db-navbar--open');
1610
+ });
1611
+ });
1612
+ // Close on outside click
1613
+ if (!document._dbNavbarOutside) {
1614
+ document._dbNavbarOutside = true;
1615
+ document.addEventListener('click', function(e) {
1616
+ if (!e.target.closest('.db-navbar')) {
1617
+ document.querySelectorAll('.db-navbar--open').forEach(function(n) {
1618
+ n.classList.remove('db-navbar--open');
1619
+ });
1620
+ }
1621
+ });
1622
+ }
1623
+ }
1624
+
1625
+ function toggleNavbar(el) {
1626
+ if (typeof el === 'string') el = document.querySelector(el);
1627
+ if (el) el.classList.toggle('db-navbar--open');
1628
+ }
1629
+
1630
+ /* ----------------------------------------------------------
1631
+ Chip Toggle
1632
+ ---------------------------------------------------------- */
1633
+ function initChipToggle(root) {
1634
+ root.querySelectorAll('[data-db-chip-toggle]').forEach(function(container) {
1635
+ if (container._dbChipToggle) return;
1636
+ container._dbChipToggle = true;
1637
+ container.addEventListener('click', function(e) {
1638
+ var chip = e.target.closest('.db-chip');
1639
+ if (!chip || e.target.closest('.db-chip__close')) return;
1640
+ chip.classList.toggle('db-chip--active');
1641
+ });
1642
+ });
1643
+ }
1644
+
1645
+ /* ----------------------------------------------------------
1646
+ Icon Refresh
1647
+ ---------------------------------------------------------- */
1648
+ function refreshIcons() {
1649
+ if (typeof lucide !== 'undefined' && typeof lucide.createIcons === 'function') {
1650
+ lucide.createIcons();
1651
+ }
1652
+ }
1653
+
1654
+ /* ----------------------------------------------------------
1655
+ Dev-mode validation warnings
1656
+ ---------------------------------------------------------- */
1657
+ function _validate(root) {
1658
+ var host = location.hostname;
1659
+ var isDev = host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || document.documentElement.hasAttribute('data-db-debug');
1660
+ if (!isDev) return;
1661
+ var checks = [
1662
+ ['.db-dropdown', '.db-dropdown__trigger', 'DAUB: .db-dropdown missing __trigger child'],
1663
+ ['.db-custom-select', '.db-custom-select__trigger', 'DAUB: .db-custom-select missing __trigger child'],
1664
+ ['.db-tabs', '.db-tabs__list', 'DAUB: .db-tabs missing __list child'],
1665
+ ['.db-field', '.db-field__input', 'DAUB: .db-field missing __input child'],
1666
+ ['.db-slider', '.db-slider__input', 'DAUB: .db-slider missing __input child'],
1667
+ ['.db-accordion', '.db-accordion__item', 'DAUB: .db-accordion has no __item children'],
1668
+ ['.db-checkbox', '.db-checkbox__input', 'DAUB: .db-checkbox missing __input child'],
1669
+ ['.db-radio', '.db-radio__input', 'DAUB: .db-radio missing __input child']
1670
+ ];
1671
+ checks.forEach(function(c) {
1672
+ root.querySelectorAll(c[0]).forEach(function(el) {
1673
+ if (!el.querySelector(c[1])) console.warn(c[2], el);
1674
+ });
1675
+ });
1676
+ root.querySelectorAll('.db-modal').forEach(function(el) {
1677
+ if (!el.id) console.warn('DAUB: .db-modal missing id attribute', el);
1678
+ });
1679
+ }
1680
+
1681
+ /* ----------------------------------------------------------
1682
+ Init
1683
+ ---------------------------------------------------------- */
1684
+ function init(root) {
1685
+ root = root || document;
1686
+ initTheme();
1687
+ initSwitches(root);
1688
+ initTabs(root);
1689
+ initModals(root);
1690
+ initSteppers(root);
1691
+ initTooltips(root);
1692
+ initSliders(root);
1693
+ initTemperature();
1694
+ initNoise();
1695
+ initTexture();
1696
+ initCheckboxes(root);
1697
+ initRadios(root);
1698
+ initAccordions(root);
1699
+ initCollapsibles(root);
1700
+ initAlertDialogs(root);
1701
+ initSheets(root);
1702
+ initDrawers(root);
1703
+ initPopovers(root);
1704
+ initContextMenus(root);
1705
+ initDropdowns(root);
1706
+ initToggles(root);
1707
+ initCustomSelects(root);
1708
+ initCommands(root);
1709
+ initMenubars(root);
1710
+ initCalendars(root);
1711
+ initCarousels(root);
1712
+ initOTP(root);
1713
+ initResizables(root);
1714
+ initChipClose(root);
1715
+ initChipToggle(root);
1716
+ initNavbars(root);
1717
+ initSidebarToggle(root);
1718
+ initThemeSwitcher();
1719
+ fixNestedRadius(root);
1720
+ refreshIcons();
1721
+ _validate(root);
1722
+ }
1723
+
1724
+ if (document.readyState === 'loading') {
1725
+ document.addEventListener('DOMContentLoaded', function() { init(); });
1726
+ } else {
1727
+ init();
1728
+ }
1729
+
1730
+ function getColor(token) {
1731
+ var prop = token.indexOf('--') === 0 ? token : '--db-' + token;
1732
+ return getComputedStyle(document.documentElement).getPropertyValue(prop).trim();
1733
+ }
1734
+
1735
+ /* ----------------------------------------------------------
1736
+ Public API
1737
+ ---------------------------------------------------------- */
1738
+ window.DAUB = {
1739
+ getColor: getColor,
1740
+ init: init,
1741
+ toast: toast,
1742
+ openModal: openModal,
1743
+ closeModal: closeModal,
1744
+ openAlertDialog: openAlertDialog,
1745
+ closeAlertDialog: closeAlertDialog,
1746
+ openSheet: openSheet,
1747
+ closeSheet: closeSheet,
1748
+ openDrawer: openDrawer,
1749
+ closeDrawer: closeDrawer,
1750
+ openCommand: openCommand,
1751
+ closeCommand: closeCommand,
1752
+ getTheme: getTheme,
1753
+ setTheme: setTheme,
1754
+ cycleTheme: cycleTheme,
1755
+ THEMES: THEMES,
1756
+ THEME_FAMILIES: THEME_FAMILIES,
1757
+ FAMILY_NAMES: FAMILY_NAMES,
1758
+ getScheme: getScheme,
1759
+ setScheme: setScheme,
1760
+ getFamily: getFamily,
1761
+ setFamily: setFamily,
1762
+ setAccent: setAccent,
1763
+ resetAccent: resetAccent,
1764
+ getAccent: getAccent,
1765
+ fixNestedRadius: fixNestedRadius,
1766
+ setTexture: function(type) {
1767
+ document.documentElement.setAttribute('data-db-texture', type);
1768
+ localStorage.setItem('db-texture', type);
1769
+ },
1770
+ getTexture: function() {
1771
+ return document.documentElement.getAttribute('data-db-texture') || 'grain';
1772
+ },
1773
+ TEXTURES: ['grain', 'paper', 'metal', 'wood', 'glass', 'none'],
1774
+ toggleSidebar: toggleSidebar,
1775
+ toggleNavbar: toggleNavbar,
1776
+ refreshIcons: refreshIcons,
1777
+ THEME_CATEGORIES: THEME_CATEGORIES,
1778
+ CATEGORY_NAMES: CATEGORY_NAMES,
1779
+ getCategory: getCategory
1780
+ };
1781
+
1782
+ })();