bitwrench 2.0.13 → 2.0.15
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/README.md +4 -4
- package/dist/bitwrench-code-edit.cjs.js +46 -46
- package/dist/bitwrench-code-edit.cjs.min.js +16 -0
- package/dist/bitwrench-code-edit.es5.js +8 -8
- package/dist/bitwrench-code-edit.es5.min.js +2 -2
- package/dist/bitwrench-code-edit.esm.js +46 -46
- package/dist/bitwrench-code-edit.esm.min.js +2 -2
- package/dist/bitwrench-code-edit.umd.js +46 -46
- package/dist/bitwrench-code-edit.umd.min.js +2 -2
- package/dist/bitwrench-lean.cjs.js +5011 -3419
- package/dist/bitwrench-lean.cjs.min.js +35 -6
- package/dist/bitwrench-lean.es5.js +6218 -4272
- package/dist/bitwrench-lean.es5.min.js +32 -3
- package/dist/bitwrench-lean.esm.js +5011 -3419
- package/dist/bitwrench-lean.esm.min.js +35 -6
- package/dist/bitwrench-lean.umd.js +5011 -3419
- package/dist/bitwrench-lean.umd.min.js +35 -6
- package/dist/bitwrench.cjs.js +6966 -4662
- package/dist/bitwrench.cjs.min.js +38 -8
- package/dist/bitwrench.css +2453 -4784
- package/dist/bitwrench.es5.js +9592 -6813
- package/dist/bitwrench.es5.min.js +34 -5
- package/dist/bitwrench.esm.js +6966 -4662
- package/dist/bitwrench.esm.min.js +38 -8
- package/dist/bitwrench.min.css +1 -0
- package/dist/bitwrench.umd.js +6966 -4662
- package/dist/bitwrench.umd.min.js +38 -8
- package/dist/builds.json +89 -67
- package/dist/sri.json +28 -26
- package/package.json +7 -5
- package/readme.html +14 -14
- package/src/{bitwrench-components-v2.js → bitwrench-bccl.js} +1311 -600
- package/src/bitwrench-code-edit.js +45 -45
- package/src/bitwrench-color-utils.js +154 -27
- package/src/bitwrench-components-stub.js +4 -1
- package/src/bitwrench-file-ops.js +180 -0
- package/src/bitwrench-lean.js +2 -2
- package/src/bitwrench-styles.js +1468 -3494
- package/src/bitwrench-utils.js +458 -0
- package/src/bitwrench.js +1795 -1349
- package/src/cli/layout-default.js +18 -18
- package/src/generate-css.js +73 -53
- package/src/version.js +3 -3
- package/src/bitwrench-component-base.js +0 -736
- package/src/bitwrench-components-inline.js +0 -374
- package/src/bitwrench-components.js +0 -610
|
@@ -12,11 +12,43 @@
|
|
|
12
12
|
* Handle classes (CardHandle, TableHandle, NavbarHandle, TabsHandle)
|
|
13
13
|
* provide imperative DOM manipulation for rendered components.
|
|
14
14
|
*
|
|
15
|
-
* @module bitwrench-
|
|
15
|
+
* @module bitwrench-bccl
|
|
16
16
|
* @license BSD-2-Clause
|
|
17
17
|
* @author M A Chatterjee <deftio [at] deftio [dot] com>
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
// =========================================================================
|
|
21
|
+
// Variant → Utility Class Mapping
|
|
22
|
+
//
|
|
23
|
+
// Components compose these shared utility classes instead of owning
|
|
24
|
+
// their own variant selectors. The CSS is generated once by
|
|
25
|
+
// generatePaletteUtilities() + generateInteractionRules().
|
|
26
|
+
// =========================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Maps component type to a function that returns utility classes for a variant.
|
|
30
|
+
* Each function takes a variant name (e.g. 'primary') and returns a class string.
|
|
31
|
+
* @type {Object.<string, function(string): string>}
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Convert a variant name to a single palette class.
|
|
35
|
+
* All BCCL components use this: variant='primary' → class includes 'bw_primary'.
|
|
36
|
+
* The CSS palette class (.bw-primary) sets bg/color/border; component-specific
|
|
37
|
+
* overrides in generatePaletteClasses() adjust per component type.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} v - Variant name (e.g. 'primary', 'danger', 'outline_primary')
|
|
40
|
+
* @returns {string} CSS class string
|
|
41
|
+
*/
|
|
42
|
+
export function variantClass(v) {
|
|
43
|
+
if (!v) return '';
|
|
44
|
+
// Handle outline variants: 'outline_primary' or 'outline-primary'
|
|
45
|
+
if (v.indexOf('outline') === 0) {
|
|
46
|
+
var base = v.replace(/^outline[_-]/, '');
|
|
47
|
+
return 'bw_btn_outline bw_' + base;
|
|
48
|
+
}
|
|
49
|
+
return 'bw_' + v;
|
|
50
|
+
}
|
|
51
|
+
|
|
20
52
|
/**
|
|
21
53
|
* Create a card component with optional header, body, footer, and image support
|
|
22
54
|
*
|
|
@@ -76,54 +108,54 @@ export function makeCard(props = {}) {
|
|
|
76
108
|
|
|
77
109
|
const shadowClasses = {
|
|
78
110
|
none: '',
|
|
79
|
-
sm: '
|
|
80
|
-
md: '
|
|
81
|
-
lg: '
|
|
111
|
+
sm: 'bw_shadow_sm',
|
|
112
|
+
md: 'bw_shadow',
|
|
113
|
+
lg: 'bw_shadow_lg'
|
|
82
114
|
};
|
|
83
115
|
|
|
84
116
|
const cardClasses = [
|
|
85
|
-
'
|
|
86
|
-
variant
|
|
117
|
+
'bw_card',
|
|
118
|
+
variantClass(variant),
|
|
87
119
|
shadow ? (shadowClasses[shadow] || '') : '',
|
|
88
|
-
!bordered ? '
|
|
89
|
-
hoverable ? '
|
|
120
|
+
!bordered ? 'bw_border_0' : '',
|
|
121
|
+
hoverable ? 'bw_card_hoverable' : '',
|
|
90
122
|
className
|
|
91
123
|
].filter(Boolean).join(' ').trim();
|
|
92
124
|
|
|
93
125
|
const cardContent = [
|
|
94
126
|
header && {
|
|
95
127
|
t: 'div',
|
|
96
|
-
a: { class: `
|
|
128
|
+
a: { class: `bw_card_header ${headerClass}`.trim() },
|
|
97
129
|
c: header
|
|
98
130
|
},
|
|
99
131
|
image && (imagePosition === 'top' || imagePosition === 'left') && {
|
|
100
132
|
t: 'img',
|
|
101
133
|
a: {
|
|
102
|
-
class: `
|
|
134
|
+
class: `bw_card_img_${imagePosition}`,
|
|
103
135
|
src: image.src,
|
|
104
136
|
alt: image.alt || ''
|
|
105
137
|
}
|
|
106
138
|
},
|
|
107
139
|
{
|
|
108
140
|
t: 'div',
|
|
109
|
-
a: { class: `
|
|
141
|
+
a: { class: `bw_card_body ${bodyClass}`.trim() },
|
|
110
142
|
c: [
|
|
111
|
-
title && { t: 'h5', a: { class: '
|
|
112
|
-
subtitle && { t: 'h6', a: { class: '
|
|
143
|
+
title && { t: 'h5', a: { class: 'bw_card_title' }, c: title },
|
|
144
|
+
subtitle && { t: 'h6', a: { class: 'bw_card_subtitle bw_mb_2 bw_text_muted' }, c: subtitle },
|
|
113
145
|
content && (Array.isArray(content) ? content : [content])
|
|
114
146
|
].flat().filter(Boolean)
|
|
115
147
|
},
|
|
116
148
|
image && (imagePosition === 'bottom' || imagePosition === 'right') && {
|
|
117
149
|
t: 'img',
|
|
118
150
|
a: {
|
|
119
|
-
class: `
|
|
151
|
+
class: `bw_card_img_${imagePosition}`,
|
|
120
152
|
src: image.src,
|
|
121
153
|
alt: image.alt || ''
|
|
122
154
|
}
|
|
123
155
|
},
|
|
124
156
|
footer && {
|
|
125
157
|
t: 'div',
|
|
126
|
-
a: { class: `
|
|
158
|
+
a: { class: `bw_card_footer ${footerClass}`.trim() },
|
|
127
159
|
c: footer
|
|
128
160
|
}
|
|
129
161
|
].filter(Boolean);
|
|
@@ -135,7 +167,7 @@ export function makeCard(props = {}) {
|
|
|
135
167
|
a: { class: cardClasses, style },
|
|
136
168
|
c: {
|
|
137
169
|
t: 'div',
|
|
138
|
-
a: { class: '
|
|
170
|
+
a: { class: 'bw_row bw_g_0' },
|
|
139
171
|
c: cardContent
|
|
140
172
|
},
|
|
141
173
|
o: {
|
|
@@ -176,8 +208,11 @@ export function makeCard(props = {}) {
|
|
|
176
208
|
* variant: "success",
|
|
177
209
|
* onclick: () => console.log("saved")
|
|
178
210
|
* });
|
|
211
|
+
* // String shorthand:
|
|
212
|
+
* const ok = makeButton("OK");
|
|
179
213
|
*/
|
|
180
214
|
export function makeButton(props = {}) {
|
|
215
|
+
if (typeof props === 'string') props = { text: props };
|
|
181
216
|
const {
|
|
182
217
|
text,
|
|
183
218
|
variant = 'primary',
|
|
@@ -194,9 +229,9 @@ export function makeButton(props = {}) {
|
|
|
194
229
|
a: {
|
|
195
230
|
type,
|
|
196
231
|
class: [
|
|
197
|
-
'
|
|
198
|
-
|
|
199
|
-
size && `
|
|
232
|
+
'bw_btn',
|
|
233
|
+
variantClass(variant),
|
|
234
|
+
size && `bw_btn_${size}`,
|
|
200
235
|
className
|
|
201
236
|
].filter(Boolean).join(' '),
|
|
202
237
|
disabled,
|
|
@@ -230,7 +265,7 @@ export function makeContainer(props = {}) {
|
|
|
230
265
|
|
|
231
266
|
return {
|
|
232
267
|
t: 'div',
|
|
233
|
-
a: { class: `
|
|
268
|
+
a: { class: `bw_container${fluid ? '-fluid' : ''} ${className}`.trim() },
|
|
234
269
|
c: children
|
|
235
270
|
};
|
|
236
271
|
}
|
|
@@ -241,7 +276,7 @@ export function makeContainer(props = {}) {
|
|
|
241
276
|
* @param {Object} [props] - Row configuration
|
|
242
277
|
* @param {Array|Object|string} [props.children] - Child columns
|
|
243
278
|
* @param {string} [props.className] - Additional CSS classes
|
|
244
|
-
* @param {number} [props.gap] - Gap size (1-5) applied via
|
|
279
|
+
* @param {number} [props.gap] - Gap size (1-5) applied via bw_g_{gap} class
|
|
245
280
|
* @returns {Object} TACO object representing a grid row
|
|
246
281
|
* @category Component Builders
|
|
247
282
|
* @example
|
|
@@ -256,7 +291,7 @@ export function makeRow(props = {}) {
|
|
|
256
291
|
return {
|
|
257
292
|
t: 'div',
|
|
258
293
|
a: {
|
|
259
|
-
class: `
|
|
294
|
+
class: `bw_row ${gap ? `bw_g_${gap}` : ''} ${className}`.trim()
|
|
260
295
|
},
|
|
261
296
|
c: children
|
|
262
297
|
};
|
|
@@ -290,20 +325,20 @@ export function makeCol(props = {}) {
|
|
|
290
325
|
// Responsive sizes
|
|
291
326
|
Object.entries(size).forEach(([breakpoint, value]) => {
|
|
292
327
|
if (breakpoint === 'xs') {
|
|
293
|
-
classes.push(`
|
|
328
|
+
classes.push(`bw_col_${value}`);
|
|
294
329
|
} else {
|
|
295
|
-
classes.push(`
|
|
330
|
+
classes.push(`bw_col_${breakpoint}-${value}`);
|
|
296
331
|
}
|
|
297
332
|
});
|
|
298
333
|
} else if (size) {
|
|
299
|
-
classes.push(`
|
|
334
|
+
classes.push(`bw_col_${size}`);
|
|
300
335
|
} else {
|
|
301
|
-
classes.push('
|
|
336
|
+
classes.push('bw_col');
|
|
302
337
|
}
|
|
303
338
|
|
|
304
|
-
if (offset) classes.push(`
|
|
305
|
-
if (push) classes.push(`
|
|
306
|
-
if (pull) classes.push(`
|
|
339
|
+
if (offset) classes.push(`bw_offset_${offset}`);
|
|
340
|
+
if (push) classes.push(`bw_push_${push}`);
|
|
341
|
+
if (pull) classes.push(`bw_pull_${pull}`);
|
|
307
342
|
|
|
308
343
|
return {
|
|
309
344
|
t: 'div',
|
|
@@ -346,16 +381,16 @@ export function makeNav(props = {}) {
|
|
|
346
381
|
return {
|
|
347
382
|
t: 'ul',
|
|
348
383
|
a: {
|
|
349
|
-
class: `
|
|
384
|
+
class: `bw_nav ${pills ? 'bw_nav_pills' : 'bw_nav_tabs'} ${vertical ? 'bw_nav_vertical' : ''} ${className}`.trim()
|
|
350
385
|
},
|
|
351
386
|
c: items.map(item => ({
|
|
352
387
|
t: 'li',
|
|
353
|
-
a: { class: '
|
|
388
|
+
a: { class: 'bw_nav_item' },
|
|
354
389
|
c: {
|
|
355
390
|
t: 'a',
|
|
356
391
|
a: {
|
|
357
392
|
href: item.href || '#',
|
|
358
|
-
class: `
|
|
393
|
+
class: `bw_nav_link ${item.active ? 'active' : ''} ${item.disabled ? 'disabled' : ''}`.trim()
|
|
359
394
|
},
|
|
360
395
|
c: item.text
|
|
361
396
|
}
|
|
@@ -399,25 +434,25 @@ export function makeNavbar(props = {}) {
|
|
|
399
434
|
return {
|
|
400
435
|
t: 'nav',
|
|
401
436
|
a: {
|
|
402
|
-
class: `
|
|
437
|
+
class: `bw_navbar ${dark ? 'bw_navbar_dark' : 'bw_navbar_light'} ${className}`.trim()
|
|
403
438
|
},
|
|
404
439
|
c: {
|
|
405
440
|
t: 'div',
|
|
406
|
-
a: { class: '
|
|
441
|
+
a: { class: 'bw_container' },
|
|
407
442
|
c: [
|
|
408
443
|
brand && {
|
|
409
444
|
t: 'a',
|
|
410
|
-
a: { href: brandHref, class: '
|
|
445
|
+
a: { href: brandHref, class: 'bw_navbar_brand' },
|
|
411
446
|
c: brand
|
|
412
447
|
},
|
|
413
448
|
items.length > 0 && {
|
|
414
449
|
t: 'div',
|
|
415
|
-
a: { class: '
|
|
450
|
+
a: { class: 'bw_navbar_nav' },
|
|
416
451
|
c: items.map(item => ({
|
|
417
452
|
t: 'a',
|
|
418
453
|
a: {
|
|
419
454
|
href: item.href || '#',
|
|
420
|
-
class: `
|
|
455
|
+
class: `bw_nav_link ${item.active ? 'active' : ''}`
|
|
421
456
|
},
|
|
422
457
|
c: item.text
|
|
423
458
|
}))
|
|
@@ -468,35 +503,38 @@ export function makeTabs(props = {}) {
|
|
|
468
503
|
|
|
469
504
|
return {
|
|
470
505
|
t: 'div',
|
|
471
|
-
a: { class: '
|
|
506
|
+
a: { class: 'bw_tabs' },
|
|
472
507
|
c: [
|
|
473
508
|
{
|
|
474
509
|
t: 'ul',
|
|
475
|
-
a: { class: '
|
|
510
|
+
a: { class: 'bw_nav bw_nav_tabs', role: 'tablist' },
|
|
476
511
|
c: tabs.map((tab, index) => ({
|
|
477
512
|
t: 'li',
|
|
478
|
-
a: { class: '
|
|
513
|
+
a: { class: 'bw_nav_item', role: 'presentation' },
|
|
479
514
|
c: {
|
|
480
515
|
t: 'button',
|
|
481
516
|
a: {
|
|
482
|
-
class: `
|
|
517
|
+
class: `bw_nav_link ${index === actualActiveIndex ? 'active' : ''}`,
|
|
483
518
|
type: 'button',
|
|
484
519
|
role: 'tab',
|
|
520
|
+
tabindex: index === actualActiveIndex ? '0' : '-1',
|
|
485
521
|
'aria-selected': index === actualActiveIndex ? 'true' : 'false',
|
|
486
522
|
'data-tab-index': index,
|
|
487
523
|
onclick: (e) => {
|
|
488
|
-
const tabsContainer = e.target.closest('.
|
|
489
|
-
const allTabs = tabsContainer.querySelectorAll('.
|
|
490
|
-
const allPanes = tabsContainer.querySelectorAll('.
|
|
524
|
+
const tabsContainer = e.target.closest('.bw_tabs');
|
|
525
|
+
const allTabs = tabsContainer.querySelectorAll('.bw_nav_link');
|
|
526
|
+
const allPanes = tabsContainer.querySelectorAll('.bw_tab_pane');
|
|
491
527
|
|
|
492
528
|
allTabs.forEach(t => {
|
|
493
529
|
t.classList.remove('active');
|
|
494
530
|
t.setAttribute('aria-selected', 'false');
|
|
531
|
+
t.setAttribute('tabindex', '-1');
|
|
495
532
|
});
|
|
496
533
|
allPanes.forEach(p => p.classList.remove('active'));
|
|
497
534
|
|
|
498
535
|
e.target.classList.add('active');
|
|
499
536
|
e.target.setAttribute('aria-selected', 'true');
|
|
537
|
+
e.target.setAttribute('tabindex', '0');
|
|
500
538
|
const targetIndex = parseInt(e.target.getAttribute('data-tab-index'));
|
|
501
539
|
allPanes[targetIndex].classList.add('active');
|
|
502
540
|
}
|
|
@@ -507,11 +545,11 @@ export function makeTabs(props = {}) {
|
|
|
507
545
|
},
|
|
508
546
|
{
|
|
509
547
|
t: 'div',
|
|
510
|
-
a: { class: '
|
|
548
|
+
a: { class: 'bw_tab_content' },
|
|
511
549
|
c: tabs.map((tab, index) => ({
|
|
512
550
|
t: 'div',
|
|
513
551
|
a: {
|
|
514
|
-
class: `
|
|
552
|
+
class: `bw_tab_pane ${index === actualActiveIndex ? 'active' : ''}`,
|
|
515
553
|
role: 'tabpanel'
|
|
516
554
|
},
|
|
517
555
|
c: tab.content
|
|
@@ -520,7 +558,39 @@ export function makeTabs(props = {}) {
|
|
|
520
558
|
],
|
|
521
559
|
o: {
|
|
522
560
|
type: 'tabs',
|
|
523
|
-
state: { activeIndex: actualActiveIndex }
|
|
561
|
+
state: { activeIndex: actualActiveIndex },
|
|
562
|
+
mounted: function(el) {
|
|
563
|
+
var tablist = el.querySelector('[role="tablist"]');
|
|
564
|
+
if (!tablist) return;
|
|
565
|
+
tablist.addEventListener('keydown', function(e) {
|
|
566
|
+
var tabButtons = tablist.querySelectorAll('[role="tab"]');
|
|
567
|
+
var currentIndex = -1;
|
|
568
|
+
for (var i = 0; i < tabButtons.length; i++) {
|
|
569
|
+
if (tabButtons[i] === e.target) { currentIndex = i; break; }
|
|
570
|
+
}
|
|
571
|
+
if (currentIndex === -1) return;
|
|
572
|
+
|
|
573
|
+
var newIndex = -1;
|
|
574
|
+
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
575
|
+
e.preventDefault();
|
|
576
|
+
newIndex = currentIndex > 0 ? currentIndex - 1 : tabButtons.length - 1;
|
|
577
|
+
} else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
|
578
|
+
e.preventDefault();
|
|
579
|
+
newIndex = currentIndex < tabButtons.length - 1 ? currentIndex + 1 : 0;
|
|
580
|
+
} else if (e.key === 'Home') {
|
|
581
|
+
e.preventDefault();
|
|
582
|
+
newIndex = 0;
|
|
583
|
+
} else if (e.key === 'End') {
|
|
584
|
+
e.preventDefault();
|
|
585
|
+
newIndex = tabButtons.length - 1;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (newIndex >= 0) {
|
|
589
|
+
tabButtons[newIndex].focus();
|
|
590
|
+
tabButtons[newIndex].click();
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
524
594
|
}
|
|
525
595
|
};
|
|
526
596
|
}
|
|
@@ -541,8 +611,11 @@ export function makeTabs(props = {}) {
|
|
|
541
611
|
* variant: "success",
|
|
542
612
|
* dismissible: true
|
|
543
613
|
* });
|
|
614
|
+
* // String shorthand:
|
|
615
|
+
* const msg = makeAlert("Something happened");
|
|
544
616
|
*/
|
|
545
617
|
export function makeAlert(props = {}) {
|
|
618
|
+
if (typeof props === 'string') props = { content: props };
|
|
546
619
|
const {
|
|
547
620
|
content,
|
|
548
621
|
variant = 'info',
|
|
@@ -553,7 +626,7 @@ export function makeAlert(props = {}) {
|
|
|
553
626
|
return {
|
|
554
627
|
t: 'div',
|
|
555
628
|
a: {
|
|
556
|
-
class: `
|
|
629
|
+
class: `bw_alert ${variantClass(variant)} ${dismissible ? 'bw_alert_dismissible' : ''} ${className}`.trim(),
|
|
557
630
|
role: 'alert'
|
|
558
631
|
},
|
|
559
632
|
c: [
|
|
@@ -562,10 +635,10 @@ export function makeAlert(props = {}) {
|
|
|
562
635
|
t: 'button',
|
|
563
636
|
a: {
|
|
564
637
|
type: 'button',
|
|
565
|
-
class: '
|
|
638
|
+
class: 'bw_close',
|
|
566
639
|
'aria-label': 'Close',
|
|
567
640
|
onclick: function(e) {
|
|
568
|
-
var alert = e.target.closest('.
|
|
641
|
+
var alert = e.target.closest('.bw_alert');
|
|
569
642
|
if (alert) { alert.remove(); }
|
|
570
643
|
}
|
|
571
644
|
},
|
|
@@ -589,8 +662,11 @@ export function makeAlert(props = {}) {
|
|
|
589
662
|
* @example
|
|
590
663
|
* const badge = makeBadge({ text: "New", variant: "danger", pill: true });
|
|
591
664
|
* const small = makeBadge({ text: "3", variant: "info", size: "sm" });
|
|
665
|
+
* // String shorthand:
|
|
666
|
+
* const tag = makeBadge("New");
|
|
592
667
|
*/
|
|
593
668
|
export function makeBadge(props = {}) {
|
|
669
|
+
if (typeof props === 'string') props = { text: props };
|
|
594
670
|
const {
|
|
595
671
|
text,
|
|
596
672
|
variant = 'primary',
|
|
@@ -599,12 +675,12 @@ export function makeBadge(props = {}) {
|
|
|
599
675
|
className = ''
|
|
600
676
|
} = props;
|
|
601
677
|
|
|
602
|
-
const sizeClass = size === 'sm' ? '
|
|
678
|
+
const sizeClass = size === 'sm' ? ' bw_badge_sm' : size === 'lg' ? ' bw_badge_lg' : '';
|
|
603
679
|
|
|
604
680
|
return {
|
|
605
681
|
t: 'span',
|
|
606
682
|
a: {
|
|
607
|
-
class: `
|
|
683
|
+
class: `bw_badge ${variantClass(variant)}${sizeClass} ${pill ? 'bw_badge_pill' : ''} ${className}`.trim()
|
|
608
684
|
},
|
|
609
685
|
c: text
|
|
610
686
|
};
|
|
@@ -647,17 +723,17 @@ export function makeProgress(props = {}) {
|
|
|
647
723
|
return {
|
|
648
724
|
t: 'div',
|
|
649
725
|
a: {
|
|
650
|
-
class: '
|
|
726
|
+
class: 'bw_progress',
|
|
651
727
|
style: height ? { height: `${height}px` } : undefined
|
|
652
728
|
},
|
|
653
729
|
c: {
|
|
654
730
|
t: 'div',
|
|
655
731
|
a: {
|
|
656
732
|
class: [
|
|
657
|
-
'
|
|
658
|
-
|
|
659
|
-
striped && '
|
|
660
|
-
animated && '
|
|
733
|
+
'bw_progress_bar',
|
|
734
|
+
variantClass(variant),
|
|
735
|
+
striped && 'bw_progress_bar_striped',
|
|
736
|
+
animated && 'bw_progress_bar_animated'
|
|
661
737
|
].filter(Boolean).join(' '),
|
|
662
738
|
role: 'progressbar',
|
|
663
739
|
style: { width: `${percentage}%` },
|
|
@@ -703,7 +779,7 @@ export function makeListGroup(props = {}) {
|
|
|
703
779
|
|
|
704
780
|
return {
|
|
705
781
|
t: 'div',
|
|
706
|
-
a: { class: `
|
|
782
|
+
a: { class: `bw_list_group ${flush ? 'bw_list_group_flush' : ''}`.trim() },
|
|
707
783
|
c: items.map(item => {
|
|
708
784
|
const isObject = typeof item === 'object';
|
|
709
785
|
const text = isObject ? item.text : item;
|
|
@@ -718,7 +794,7 @@ export function makeListGroup(props = {}) {
|
|
|
718
794
|
t: 'a',
|
|
719
795
|
a: {
|
|
720
796
|
class: [
|
|
721
|
-
'
|
|
797
|
+
'bw_list_group_item',
|
|
722
798
|
active && 'active',
|
|
723
799
|
disabled && 'disabled'
|
|
724
800
|
].filter(Boolean).join(' '),
|
|
@@ -737,7 +813,7 @@ export function makeListGroup(props = {}) {
|
|
|
737
813
|
t: 'div',
|
|
738
814
|
a: {
|
|
739
815
|
class: [
|
|
740
|
-
'
|
|
816
|
+
'bw_list_group_item',
|
|
741
817
|
active && 'active',
|
|
742
818
|
disabled && 'disabled'
|
|
743
819
|
].filter(Boolean).join(' ')
|
|
@@ -778,11 +854,11 @@ export function makeBreadcrumb(props = {}) {
|
|
|
778
854
|
a: { 'aria-label': 'breadcrumb' },
|
|
779
855
|
c: {
|
|
780
856
|
t: 'ol',
|
|
781
|
-
a: { class: '
|
|
857
|
+
a: { class: 'bw_breadcrumb' },
|
|
782
858
|
c: items.map((item, index) => ({
|
|
783
859
|
t: 'li',
|
|
784
860
|
a: {
|
|
785
|
-
class: `
|
|
861
|
+
class: `bw_breadcrumb_item ${item.active ? 'active' : ''}`,
|
|
786
862
|
'aria-current': item.active ? 'page' : undefined
|
|
787
863
|
},
|
|
788
864
|
c: item.active ? item.text : {
|
|
@@ -827,13 +903,16 @@ export function makeForm(props = {}) {
|
|
|
827
903
|
}
|
|
828
904
|
|
|
829
905
|
/**
|
|
830
|
-
* Create a form group with label, input,
|
|
906
|
+
* Create a form group with label, input, optional help text and validation feedback
|
|
831
907
|
*
|
|
832
908
|
* @param {Object} [props] - Form group configuration
|
|
833
909
|
* @param {string} [props.label] - Label text
|
|
834
910
|
* @param {Object} [props.input] - Input TACO object (from makeInput, makeSelect, etc.)
|
|
835
911
|
* @param {string} [props.help] - Help text displayed below the input
|
|
836
912
|
* @param {string} [props.id] - Input ID (links label to input via for/id)
|
|
913
|
+
* @param {string} [props.validation] - Validation state ("valid" or "invalid")
|
|
914
|
+
* @param {string} [props.feedback] - Validation feedback text shown below input
|
|
915
|
+
* @param {boolean} [props.required=false] - Show required indicator (*) on label
|
|
837
916
|
* @returns {Object} TACO object representing a form group
|
|
838
917
|
* @category Component Builders
|
|
839
918
|
* @example
|
|
@@ -841,25 +920,41 @@ export function makeForm(props = {}) {
|
|
|
841
920
|
* label: "Email",
|
|
842
921
|
* id: "email",
|
|
843
922
|
* input: makeInput({ type: "email", id: "email", placeholder: "you@example.com" }),
|
|
844
|
-
*
|
|
923
|
+
* validation: "invalid",
|
|
924
|
+
* feedback: "Please enter a valid email address."
|
|
845
925
|
* });
|
|
846
926
|
*/
|
|
847
927
|
export function makeFormGroup(props = {}) {
|
|
848
|
-
|
|
928
|
+
var { label, input, help, id, validation, feedback, required } = props;
|
|
929
|
+
|
|
930
|
+
// Shallow-clone input TACO to add validation class without mutating original
|
|
931
|
+
var styledInput = input;
|
|
932
|
+
if (validation && input && input.a) {
|
|
933
|
+
styledInput = { t: input.t, a: Object.assign({}, input.a), c: input.c, o: input.o };
|
|
934
|
+
var validClass = validation === 'valid' ? 'bw_is_valid' : validation === 'invalid' ? 'bw_is_invalid' : '';
|
|
935
|
+
if (validClass) {
|
|
936
|
+
styledInput.a.class = ((styledInput.a.class || '') + ' ' + validClass).trim();
|
|
937
|
+
}
|
|
938
|
+
}
|
|
849
939
|
|
|
850
940
|
return {
|
|
851
941
|
t: 'div',
|
|
852
|
-
a: { class: '
|
|
942
|
+
a: { class: 'bw_form_group' },
|
|
853
943
|
c: [
|
|
854
944
|
label && {
|
|
855
945
|
t: 'label',
|
|
856
|
-
a: { for: id, class: '
|
|
857
|
-
c: label
|
|
946
|
+
a: { for: id, class: 'bw_form_label' },
|
|
947
|
+
c: required ? [label, { t: 'span', a: { class: 'bw_text_danger bw_ms_1' }, c: '*' }] : label
|
|
948
|
+
},
|
|
949
|
+
styledInput,
|
|
950
|
+
feedback && validation && {
|
|
951
|
+
t: 'div',
|
|
952
|
+
a: { class: validation === 'valid' ? 'bw_valid_feedback' : 'bw_invalid_feedback' },
|
|
953
|
+
c: feedback
|
|
858
954
|
},
|
|
859
|
-
input,
|
|
860
955
|
help && {
|
|
861
956
|
t: 'small',
|
|
862
|
-
a: { class: '
|
|
957
|
+
a: { class: 'bw_form_text bw_text_muted' },
|
|
863
958
|
c: help
|
|
864
959
|
}
|
|
865
960
|
].filter(Boolean)
|
|
@@ -912,7 +1007,7 @@ export function makeInput(props = {}) {
|
|
|
912
1007
|
t: 'input',
|
|
913
1008
|
a: {
|
|
914
1009
|
type,
|
|
915
|
-
class: `
|
|
1010
|
+
class: `bw_form_control ${className}`.trim(),
|
|
916
1011
|
placeholder,
|
|
917
1012
|
value,
|
|
918
1013
|
id,
|
|
@@ -965,7 +1060,7 @@ export function makeTextarea(props = {}) {
|
|
|
965
1060
|
return {
|
|
966
1061
|
t: 'textarea',
|
|
967
1062
|
a: {
|
|
968
|
-
class: `
|
|
1063
|
+
class: `bw_form_control ${className}`.trim(),
|
|
969
1064
|
placeholder,
|
|
970
1065
|
rows,
|
|
971
1066
|
id,
|
|
@@ -1019,7 +1114,7 @@ export function makeSelect(props = {}) {
|
|
|
1019
1114
|
return {
|
|
1020
1115
|
t: 'select',
|
|
1021
1116
|
a: {
|
|
1022
|
-
class: `
|
|
1117
|
+
class: `bw_form_control ${className}`.trim(),
|
|
1023
1118
|
id,
|
|
1024
1119
|
name,
|
|
1025
1120
|
disabled,
|
|
@@ -1070,13 +1165,13 @@ export function makeCheckbox(props = {}) {
|
|
|
1070
1165
|
|
|
1071
1166
|
return {
|
|
1072
1167
|
t: 'div',
|
|
1073
|
-
a: { class: `
|
|
1168
|
+
a: { class: `bw_form_check ${className}`.trim() },
|
|
1074
1169
|
c: [
|
|
1075
1170
|
{
|
|
1076
1171
|
t: 'input',
|
|
1077
1172
|
a: {
|
|
1078
1173
|
type: 'checkbox',
|
|
1079
|
-
class: '
|
|
1174
|
+
class: 'bw_form_check_input',
|
|
1080
1175
|
checked,
|
|
1081
1176
|
id,
|
|
1082
1177
|
name,
|
|
@@ -1087,7 +1182,7 @@ export function makeCheckbox(props = {}) {
|
|
|
1087
1182
|
},
|
|
1088
1183
|
label && {
|
|
1089
1184
|
t: 'label',
|
|
1090
|
-
a: { class: '
|
|
1185
|
+
a: { class: 'bw_form_check_label', for: id },
|
|
1091
1186
|
c: label
|
|
1092
1187
|
}
|
|
1093
1188
|
].filter(Boolean)
|
|
@@ -1125,7 +1220,7 @@ export function makeStack(props = {}) {
|
|
|
1125
1220
|
return {
|
|
1126
1221
|
t: 'div',
|
|
1127
1222
|
a: {
|
|
1128
|
-
class: `
|
|
1223
|
+
class: `bw_${direction === 'vertical' ? 'vstack' : 'hstack'} bw_gap_${gap} ${className}`.trim()
|
|
1129
1224
|
},
|
|
1130
1225
|
c: children
|
|
1131
1226
|
};
|
|
@@ -1153,12 +1248,12 @@ export function makeSpinner(props = {}) {
|
|
|
1153
1248
|
return {
|
|
1154
1249
|
t: 'div',
|
|
1155
1250
|
a: {
|
|
1156
|
-
class: `
|
|
1251
|
+
class: `bw_spinner_${type} bw_spinner_${type}-${size} ${variantClass(variant)}`,
|
|
1157
1252
|
role: 'status'
|
|
1158
1253
|
},
|
|
1159
1254
|
c: {
|
|
1160
1255
|
t: 'span',
|
|
1161
|
-
a: { class: '
|
|
1256
|
+
a: { class: 'bw_visually_hidden' },
|
|
1162
1257
|
c: 'Loading...'
|
|
1163
1258
|
}
|
|
1164
1259
|
};
|
|
@@ -1209,44 +1304,44 @@ export function makeHero(props = {}) {
|
|
|
1209
1304
|
} = props;
|
|
1210
1305
|
|
|
1211
1306
|
const sizeClasses = {
|
|
1212
|
-
sm: '
|
|
1213
|
-
md: '
|
|
1214
|
-
lg: '
|
|
1215
|
-
xl: '
|
|
1307
|
+
sm: 'bw_py_3',
|
|
1308
|
+
md: 'bw_py_4',
|
|
1309
|
+
lg: 'bw_py_5',
|
|
1310
|
+
xl: 'bw_py_6'
|
|
1216
1311
|
};
|
|
1217
1312
|
|
|
1218
1313
|
return {
|
|
1219
1314
|
t: 'section',
|
|
1220
1315
|
a: {
|
|
1221
|
-
class: `
|
|
1316
|
+
class: `bw_hero ${variantClass(variant)} ${sizeClasses[size] || sizeClasses.lg} ${centered ? 'bw_text_center' : ''} ${className}`.trim(),
|
|
1222
1317
|
style: backgroundImage ? `background-image: url('${backgroundImage}'); background-size: cover; background-position: center;` : undefined
|
|
1223
1318
|
},
|
|
1224
1319
|
c: [
|
|
1225
1320
|
overlay && {
|
|
1226
1321
|
t: 'div',
|
|
1227
|
-
a: { class: '
|
|
1322
|
+
a: { class: 'bw_hero_overlay' }
|
|
1228
1323
|
},
|
|
1229
1324
|
{
|
|
1230
1325
|
t: 'div',
|
|
1231
|
-
a: { class: '
|
|
1326
|
+
a: { class: 'bw_container' },
|
|
1232
1327
|
c: {
|
|
1233
1328
|
t: 'div',
|
|
1234
|
-
a: { class: '
|
|
1329
|
+
a: { class: 'bw_hero_content' },
|
|
1235
1330
|
c: [
|
|
1236
1331
|
title && {
|
|
1237
1332
|
t: 'h1',
|
|
1238
|
-
a: { class: '
|
|
1333
|
+
a: { class: 'bw_hero_title bw_display_4 bw_mb_3' },
|
|
1239
1334
|
c: title
|
|
1240
1335
|
},
|
|
1241
1336
|
subtitle && {
|
|
1242
1337
|
t: 'p',
|
|
1243
|
-
a: { class: '
|
|
1338
|
+
a: { class: 'bw_hero_subtitle bw_lead bw_mb_4' },
|
|
1244
1339
|
c: subtitle
|
|
1245
1340
|
},
|
|
1246
1341
|
content,
|
|
1247
1342
|
actions && {
|
|
1248
1343
|
t: 'div',
|
|
1249
|
-
a: { class: '
|
|
1344
|
+
a: { class: 'bw_hero_actions bw_mt_4' },
|
|
1250
1345
|
c: actions
|
|
1251
1346
|
}
|
|
1252
1347
|
].filter(Boolean)
|
|
@@ -1292,37 +1387,37 @@ export function makeFeatureGrid(props = {}) {
|
|
|
1292
1387
|
className = ''
|
|
1293
1388
|
} = props;
|
|
1294
1389
|
|
|
1295
|
-
const colClass = `
|
|
1390
|
+
const colClass = `bw_col_md_${12/columns}`;
|
|
1296
1391
|
|
|
1297
1392
|
return {
|
|
1298
1393
|
t: 'div',
|
|
1299
|
-
a: { class: `
|
|
1394
|
+
a: { class: `bw_feature_grid ${className}`.trim() },
|
|
1300
1395
|
c: {
|
|
1301
1396
|
t: 'div',
|
|
1302
|
-
a: { class: '
|
|
1397
|
+
a: { class: 'bw_row bw_g_4' },
|
|
1303
1398
|
c: features.map(feature => ({
|
|
1304
1399
|
t: 'div',
|
|
1305
1400
|
a: { class: colClass },
|
|
1306
1401
|
c: {
|
|
1307
1402
|
t: 'div',
|
|
1308
|
-
a: { class: `
|
|
1403
|
+
a: { class: `bw_feature ${centered ? 'bw_text_center' : ''}` },
|
|
1309
1404
|
c: [
|
|
1310
1405
|
feature.icon && {
|
|
1311
1406
|
t: 'div',
|
|
1312
1407
|
a: {
|
|
1313
|
-
class: '
|
|
1408
|
+
class: 'bw_feature_icon bw_mb_3 bw_text_primary',
|
|
1314
1409
|
style: `font-size: ${iconSize};`
|
|
1315
1410
|
},
|
|
1316
1411
|
c: feature.icon
|
|
1317
1412
|
},
|
|
1318
1413
|
feature.title && {
|
|
1319
1414
|
t: 'h3',
|
|
1320
|
-
a: { class: '
|
|
1415
|
+
a: { class: 'bw_feature_title bw_h5 bw_mb_2' },
|
|
1321
1416
|
c: feature.title
|
|
1322
1417
|
},
|
|
1323
1418
|
feature.description && {
|
|
1324
1419
|
t: 'p',
|
|
1325
|
-
a: { class: '
|
|
1420
|
+
a: { class: 'bw_feature_description bw_text_muted' },
|
|
1326
1421
|
c: feature.description
|
|
1327
1422
|
}
|
|
1328
1423
|
].filter(Boolean)
|
|
@@ -1366,19 +1461,19 @@ export function makeCTA(props = {}) {
|
|
|
1366
1461
|
|
|
1367
1462
|
return {
|
|
1368
1463
|
t: 'section',
|
|
1369
|
-
a: { class: `
|
|
1464
|
+
a: { class: `bw_cta bw_bg_${variant} bw_py_5 ${className}`.trim() },
|
|
1370
1465
|
c: {
|
|
1371
1466
|
t: 'div',
|
|
1372
|
-
a: { class: '
|
|
1467
|
+
a: { class: 'bw_container' },
|
|
1373
1468
|
c: {
|
|
1374
1469
|
t: 'div',
|
|
1375
|
-
a: { class: `
|
|
1470
|
+
a: { class: `bw_cta_content ${centered ? 'bw_text_center' : ''}` },
|
|
1376
1471
|
c: [
|
|
1377
|
-
title && { t: 'h2', a: { class: '
|
|
1378
|
-
description && { t: 'p', a: { class: '
|
|
1472
|
+
title && { t: 'h2', a: { class: 'bw_cta_title bw_mb_3' }, c: title },
|
|
1473
|
+
description && { t: 'p', a: { class: 'bw_cta_description bw_lead bw_mb_4' }, c: description },
|
|
1379
1474
|
actions && {
|
|
1380
1475
|
t: 'div',
|
|
1381
|
-
a: { class: '
|
|
1476
|
+
a: { class: 'bw_cta_actions' },
|
|
1382
1477
|
c: actions
|
|
1383
1478
|
}
|
|
1384
1479
|
].filter(Boolean)
|
|
@@ -1418,27 +1513,27 @@ export function makeSection(props = {}) {
|
|
|
1418
1513
|
} = props;
|
|
1419
1514
|
|
|
1420
1515
|
const spacingClasses = {
|
|
1421
|
-
sm: '
|
|
1422
|
-
md: '
|
|
1423
|
-
lg: '
|
|
1424
|
-
xl: '
|
|
1516
|
+
sm: 'bw_py_3',
|
|
1517
|
+
md: 'bw_py_4',
|
|
1518
|
+
lg: 'bw_py_5',
|
|
1519
|
+
xl: 'bw_py_6'
|
|
1425
1520
|
};
|
|
1426
1521
|
|
|
1427
1522
|
return {
|
|
1428
1523
|
t: 'section',
|
|
1429
1524
|
a: {
|
|
1430
|
-
class: `
|
|
1525
|
+
class: `bw_section ${spacingClasses[spacing] || spacingClasses.md} ${variant !== 'default' ? `bw_bg_${variant}` : ''} ${className}`.trim()
|
|
1431
1526
|
},
|
|
1432
1527
|
c: {
|
|
1433
1528
|
t: 'div',
|
|
1434
|
-
a: { class: '
|
|
1529
|
+
a: { class: 'bw_container' },
|
|
1435
1530
|
c: [
|
|
1436
1531
|
(title || subtitle) && {
|
|
1437
1532
|
t: 'div',
|
|
1438
|
-
a: { class: '
|
|
1533
|
+
a: { class: 'bw_section_header bw_text_center bw_mb_5' },
|
|
1439
1534
|
c: [
|
|
1440
|
-
title && { t: 'h2', a: { class: '
|
|
1441
|
-
subtitle && { t: 'p', a: { class: '
|
|
1535
|
+
title && { t: 'h2', a: { class: 'bw_section_title' }, c: title },
|
|
1536
|
+
subtitle && { t: 'p', a: { class: 'bw_section_subtitle bw_text_muted' }, c: subtitle }
|
|
1442
1537
|
].filter(Boolean)
|
|
1443
1538
|
},
|
|
1444
1539
|
content
|
|
@@ -1455,317 +1550,9 @@ export function makeSection(props = {}) {
|
|
|
1455
1550
|
// full re-renders. Used by bw.createCard(), bw.createTable(), etc.
|
|
1456
1551
|
// =========================================================================
|
|
1457
1552
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
* Provides methods to update card title, content, and CSS classes
|
|
1462
|
-
* without re-rendering the entire component. Created automatically
|
|
1463
|
-
* when using bw.createCard().
|
|
1464
|
-
*
|
|
1465
|
-
* @category Component Handles
|
|
1466
|
-
*/
|
|
1467
|
-
export class CardHandle {
|
|
1468
|
-
/**
|
|
1469
|
-
* @param {Element} element - The card's root DOM element
|
|
1470
|
-
* @param {Object} taco - The original TACO object used to create the card
|
|
1471
|
-
*/
|
|
1472
|
-
constructor(element, taco) {
|
|
1473
|
-
this.element = element;
|
|
1474
|
-
this._taco = taco;
|
|
1475
|
-
this.state = taco.o?.state || {};
|
|
1476
|
-
|
|
1477
|
-
// Cache child elements
|
|
1478
|
-
this.children = {
|
|
1479
|
-
header: element.querySelector('.bw-card-header'),
|
|
1480
|
-
title: element.querySelector('.bw-card-title'),
|
|
1481
|
-
body: element.querySelector('.bw-card-body'),
|
|
1482
|
-
footer: element.querySelector('.bw-card-footer')
|
|
1483
|
-
};
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
/**
|
|
1487
|
-
* Update the card title text
|
|
1488
|
-
*
|
|
1489
|
-
* @param {string} title - New title text
|
|
1490
|
-
* @returns {CardHandle} this (for chaining)
|
|
1491
|
-
*/
|
|
1492
|
-
setTitle(title) {
|
|
1493
|
-
if (this.children.title) {
|
|
1494
|
-
this.children.title.textContent = title;
|
|
1495
|
-
}
|
|
1496
|
-
return this;
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
/**
|
|
1500
|
-
* Replace the card body content
|
|
1501
|
-
*
|
|
1502
|
-
* @param {string|Object} content - New content (string or TACO object)
|
|
1503
|
-
* @returns {CardHandle} this (for chaining)
|
|
1504
|
-
*/
|
|
1505
|
-
setContent(content) {
|
|
1506
|
-
if (this.children.body) {
|
|
1507
|
-
if (typeof content === 'string') {
|
|
1508
|
-
this.children.body.textContent = content;
|
|
1509
|
-
} else {
|
|
1510
|
-
// Re-render content
|
|
1511
|
-
this.children.body.innerHTML = '';
|
|
1512
|
-
const newContent = window.bw.taco.toDOM(content);
|
|
1513
|
-
this.children.body.appendChild(newContent);
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
return this;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
/**
|
|
1520
|
-
* Add a CSS class to the card root element
|
|
1521
|
-
*
|
|
1522
|
-
* @param {string} className - Class to add
|
|
1523
|
-
* @returns {CardHandle} this (for chaining)
|
|
1524
|
-
*/
|
|
1525
|
-
addClass(className) {
|
|
1526
|
-
this.element.classList.add(className);
|
|
1527
|
-
return this;
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
/**
|
|
1531
|
-
* Remove a CSS class from the card root element
|
|
1532
|
-
*
|
|
1533
|
-
* @param {string} className - Class to remove
|
|
1534
|
-
* @returns {CardHandle} this (for chaining)
|
|
1535
|
-
*/
|
|
1536
|
-
removeClass(className) {
|
|
1537
|
-
this.element.classList.remove(className);
|
|
1538
|
-
return this;
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
/**
|
|
1542
|
-
* Query a child element within the card
|
|
1543
|
-
*
|
|
1544
|
-
* @param {string} selector - CSS selector
|
|
1545
|
-
* @returns {Element|null} Matching element or null
|
|
1546
|
-
*/
|
|
1547
|
-
select(selector) {
|
|
1548
|
-
return this.element.querySelector(selector);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
/**
|
|
1553
|
-
* Imperative handle for a rendered table component
|
|
1554
|
-
*
|
|
1555
|
-
* Provides methods for data updates and column sorting. Caches
|
|
1556
|
-
* thead/tbody/header references for efficient DOM updates.
|
|
1557
|
-
* Created automatically when using bw.createTable().
|
|
1558
|
-
*
|
|
1559
|
-
* @category Component Handles
|
|
1560
|
-
*/
|
|
1561
|
-
export class TableHandle {
|
|
1562
|
-
/**
|
|
1563
|
-
* @param {Element} element - The table's root DOM element
|
|
1564
|
-
* @param {Object} taco - The original TACO object used to create the table
|
|
1565
|
-
*/
|
|
1566
|
-
constructor(element, taco) {
|
|
1567
|
-
this.element = element;
|
|
1568
|
-
this._taco = taco;
|
|
1569
|
-
this.state = taco.o?.state || {};
|
|
1570
|
-
this._data = this.state.data || [];
|
|
1571
|
-
this._sortColumn = null;
|
|
1572
|
-
this._sortDirection = 'asc';
|
|
1573
|
-
|
|
1574
|
-
// Cache elements
|
|
1575
|
-
this.children = {
|
|
1576
|
-
thead: element.querySelector('thead'),
|
|
1577
|
-
tbody: element.querySelector('tbody'),
|
|
1578
|
-
headers: element.querySelectorAll('th')
|
|
1579
|
-
};
|
|
1580
|
-
|
|
1581
|
-
// Set up sorting if enabled
|
|
1582
|
-
if (this.state.sortable) {
|
|
1583
|
-
this._setupSorting();
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
/**
|
|
1588
|
-
* Attach click-to-sort handlers on all column headers
|
|
1589
|
-
* @private
|
|
1590
|
-
*/
|
|
1591
|
-
_setupSorting() {
|
|
1592
|
-
this.children.headers.forEach((th, index) => {
|
|
1593
|
-
th.style.cursor = 'pointer';
|
|
1594
|
-
th.onclick = () => this.sortBy(th.textContent);
|
|
1595
|
-
});
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
/**
|
|
1599
|
-
* Replace the table data and re-render the body
|
|
1600
|
-
*
|
|
1601
|
-
* @param {Array<Object>} data - Array of row objects
|
|
1602
|
-
* @returns {TableHandle} this (for chaining)
|
|
1603
|
-
*/
|
|
1604
|
-
setData(data) {
|
|
1605
|
-
this._data = data;
|
|
1606
|
-
this._renderBody();
|
|
1607
|
-
return this;
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
/**
|
|
1611
|
-
* Sort the table by a column name
|
|
1612
|
-
*
|
|
1613
|
-
* Toggles direction if the same column is sorted again.
|
|
1614
|
-
*
|
|
1615
|
-
* @param {string} column - Column header text to sort by
|
|
1616
|
-
* @param {string} [direction] - Sort direction ("asc" or "desc"); toggles if omitted
|
|
1617
|
-
* @returns {TableHandle} this (for chaining)
|
|
1618
|
-
*/
|
|
1619
|
-
sortBy(column, direction) {
|
|
1620
|
-
if (column === this._sortColumn && !direction) {
|
|
1621
|
-
this._sortDirection = this._sortDirection === 'asc' ? 'desc' : 'asc';
|
|
1622
|
-
} else {
|
|
1623
|
-
this._sortColumn = column;
|
|
1624
|
-
this._sortDirection = direction || 'asc';
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
const columnKey = Object.keys(this._data[0])[
|
|
1628
|
-
Array.from(this.children.headers).findIndex(th => th.textContent === column)
|
|
1629
|
-
];
|
|
1630
|
-
|
|
1631
|
-
this._data.sort((a, b) => {
|
|
1632
|
-
const aVal = a[columnKey];
|
|
1633
|
-
const bVal = b[columnKey];
|
|
1634
|
-
const result = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
1635
|
-
return this._sortDirection === 'asc' ? result : -result;
|
|
1636
|
-
});
|
|
1637
|
-
|
|
1638
|
-
this._renderBody();
|
|
1639
|
-
return this;
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
/**
|
|
1643
|
-
* Re-render the tbody from current _data
|
|
1644
|
-
* @private
|
|
1645
|
-
*/
|
|
1646
|
-
_renderBody() {
|
|
1647
|
-
this.children.tbody.innerHTML = '';
|
|
1648
|
-
this._data.forEach(row => {
|
|
1649
|
-
const tr = document.createElement('tr');
|
|
1650
|
-
Object.values(row).forEach(value => {
|
|
1651
|
-
const td = document.createElement('td');
|
|
1652
|
-
td.textContent = value;
|
|
1653
|
-
tr.appendChild(td);
|
|
1654
|
-
});
|
|
1655
|
-
this.children.tbody.appendChild(tr);
|
|
1656
|
-
});
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
/**
|
|
1661
|
-
* Imperative handle for a rendered navbar component
|
|
1662
|
-
*
|
|
1663
|
-
* Provides methods to update the active navigation link.
|
|
1664
|
-
* Created automatically when using bw.createNavbar().
|
|
1665
|
-
*
|
|
1666
|
-
* @category Component Handles
|
|
1667
|
-
*/
|
|
1668
|
-
export class NavbarHandle {
|
|
1669
|
-
/**
|
|
1670
|
-
* @param {Element} element - The navbar's root DOM element
|
|
1671
|
-
* @param {Object} taco - The original TACO object used to create the navbar
|
|
1672
|
-
*/
|
|
1673
|
-
constructor(element, taco) {
|
|
1674
|
-
this.element = element;
|
|
1675
|
-
this._taco = taco;
|
|
1676
|
-
this.state = taco.o?.state || {};
|
|
1677
|
-
|
|
1678
|
-
this.children = {
|
|
1679
|
-
brand: element.querySelector('.bw-navbar-brand'),
|
|
1680
|
-
links: element.querySelectorAll('.bw-nav-link')
|
|
1681
|
-
};
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
/**
|
|
1685
|
-
* Set the active navigation link by href
|
|
1686
|
-
*
|
|
1687
|
-
* @param {string} href - The href value of the link to activate
|
|
1688
|
-
* @returns {NavbarHandle} this (for chaining)
|
|
1689
|
-
*/
|
|
1690
|
-
setActive(href) {
|
|
1691
|
-
this.children.links.forEach(link => {
|
|
1692
|
-
if (link.getAttribute('href') === href) {
|
|
1693
|
-
link.classList.add('active');
|
|
1694
|
-
} else {
|
|
1695
|
-
link.classList.remove('active');
|
|
1696
|
-
}
|
|
1697
|
-
});
|
|
1698
|
-
return this;
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
/**
|
|
1703
|
-
* Imperative handle for a rendered tabs component
|
|
1704
|
-
*
|
|
1705
|
-
* Provides programmatic tab switching. Sets up click handlers
|
|
1706
|
-
* on tab buttons and manages active states on both buttons and panes.
|
|
1707
|
-
* Created automatically when using bw.createTabs().
|
|
1708
|
-
*
|
|
1709
|
-
* @category Component Handles
|
|
1710
|
-
*/
|
|
1711
|
-
export class TabsHandle {
|
|
1712
|
-
/**
|
|
1713
|
-
* @param {Element} element - The tabs container DOM element
|
|
1714
|
-
* @param {Object} taco - The original TACO object used to create the tabs
|
|
1715
|
-
*/
|
|
1716
|
-
constructor(element, taco) {
|
|
1717
|
-
this.element = element;
|
|
1718
|
-
this._taco = taco;
|
|
1719
|
-
this.state = taco.o?.state || {};
|
|
1720
|
-
|
|
1721
|
-
this.children = {
|
|
1722
|
-
navItems: element.querySelectorAll('.bw-nav-link'),
|
|
1723
|
-
tabPanes: element.querySelectorAll('.bw-tab-pane')
|
|
1724
|
-
};
|
|
1725
|
-
|
|
1726
|
-
this._setupTabs();
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
/**
|
|
1730
|
-
* Attach click handlers to tab navigation buttons
|
|
1731
|
-
* @private
|
|
1732
|
-
*/
|
|
1733
|
-
_setupTabs() {
|
|
1734
|
-
this.children.navItems.forEach((navItem, index) => {
|
|
1735
|
-
navItem.onclick = (e) => {
|
|
1736
|
-
e.preventDefault();
|
|
1737
|
-
this.switchTo(index);
|
|
1738
|
-
};
|
|
1739
|
-
});
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
/**
|
|
1743
|
-
* Programmatically switch to a tab by index
|
|
1744
|
-
*
|
|
1745
|
-
* @param {number} index - Zero-based tab index to activate
|
|
1746
|
-
* @returns {TabsHandle} this (for chaining)
|
|
1747
|
-
*/
|
|
1748
|
-
switchTo(index) {
|
|
1749
|
-
this.children.navItems.forEach((item, i) => {
|
|
1750
|
-
if (i === index) {
|
|
1751
|
-
item.classList.add('active');
|
|
1752
|
-
} else {
|
|
1753
|
-
item.classList.remove('active');
|
|
1754
|
-
}
|
|
1755
|
-
});
|
|
1756
|
-
|
|
1757
|
-
this.children.tabPanes.forEach((pane, i) => {
|
|
1758
|
-
if (i === index) {
|
|
1759
|
-
pane.classList.add('active');
|
|
1760
|
-
} else {
|
|
1761
|
-
pane.classList.remove('active');
|
|
1762
|
-
}
|
|
1763
|
-
});
|
|
1764
|
-
|
|
1765
|
-
this.state.activeIndex = index;
|
|
1766
|
-
return this;
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1553
|
+
// Handle classes (CardHandle, TableHandle, NavbarHandle, TabsHandle)
|
|
1554
|
+
// removed in v2.0.15 — superseded by ComponentHandle.
|
|
1555
|
+
// See dev/dead-code-elimination-v2.0.15.md for recovery.
|
|
1769
1556
|
|
|
1770
1557
|
/**
|
|
1771
1558
|
* Create a code demo component for documentation pages
|
|
@@ -1820,18 +1607,16 @@ export function makeCodeDemo(props = {}) {
|
|
|
1820
1607
|
{
|
|
1821
1608
|
t: 'button',
|
|
1822
1609
|
a: {
|
|
1823
|
-
class: '
|
|
1824
|
-
onclick: (e)
|
|
1825
|
-
navigator.clipboard.writeText(code).then(()
|
|
1826
|
-
|
|
1827
|
-
|
|
1610
|
+
class: 'bw_copy_btn bw_code_copy_btn',
|
|
1611
|
+
onclick: function(e) {
|
|
1612
|
+
navigator.clipboard.writeText(code).then(function() {
|
|
1613
|
+
var btn = e.target;
|
|
1614
|
+
var originalText = btn.textContent;
|
|
1828
1615
|
btn.textContent = 'Copied!';
|
|
1829
|
-
btn.
|
|
1830
|
-
|
|
1831
|
-
setTimeout(() => {
|
|
1616
|
+
btn.classList.add('bw_code_copy_btn_copied');
|
|
1617
|
+
setTimeout(function() {
|
|
1832
1618
|
btn.textContent = originalText;
|
|
1833
|
-
btn.
|
|
1834
|
-
btn.style.color = '#aaa';
|
|
1619
|
+
btn.classList.remove('bw_code_copy_btn_copied');
|
|
1835
1620
|
}, 2000);
|
|
1836
1621
|
});
|
|
1837
1622
|
}
|
|
@@ -1842,10 +1627,10 @@ export function makeCodeDemo(props = {}) {
|
|
|
1842
1627
|
? globalThis.bw.codeEditor({ code: code, lang: language === 'javascript' ? 'js' : language, readOnly: true, height: 'auto' })
|
|
1843
1628
|
: {
|
|
1844
1629
|
t: 'pre',
|
|
1845
|
-
a: { class: '
|
|
1630
|
+
a: { class: 'bw_code_pre' },
|
|
1846
1631
|
c: {
|
|
1847
1632
|
t: 'code',
|
|
1848
|
-
a: { class: `
|
|
1633
|
+
a: { class: `bw_code_block language-${language}` },
|
|
1849
1634
|
c: code
|
|
1850
1635
|
}
|
|
1851
1636
|
}
|
|
@@ -1858,7 +1643,7 @@ export function makeCodeDemo(props = {}) {
|
|
|
1858
1643
|
title && { t: 'h3', c: title },
|
|
1859
1644
|
description && {
|
|
1860
1645
|
t: 'p',
|
|
1861
|
-
a: { class: '
|
|
1646
|
+
a: { class: 'bw_text_muted bw_mb_3' },
|
|
1862
1647
|
c: description
|
|
1863
1648
|
},
|
|
1864
1649
|
makeTabs({ tabs, id: demoId })
|
|
@@ -1866,7 +1651,7 @@ export function makeCodeDemo(props = {}) {
|
|
|
1866
1651
|
|
|
1867
1652
|
return {
|
|
1868
1653
|
t: 'div',
|
|
1869
|
-
a: { class: '
|
|
1654
|
+
a: { class: 'bw_code_demo' },
|
|
1870
1655
|
c: content
|
|
1871
1656
|
};
|
|
1872
1657
|
}
|
|
@@ -1923,10 +1708,10 @@ export function makePagination(props = {}) {
|
|
|
1923
1708
|
// Previous arrow
|
|
1924
1709
|
items.push({
|
|
1925
1710
|
t: 'li',
|
|
1926
|
-
a: { class: `
|
|
1711
|
+
a: { class: `bw_page_item ${currentPage <= 1 ? 'bw_disabled' : ''}`.trim() },
|
|
1927
1712
|
c: {
|
|
1928
1713
|
t: 'a',
|
|
1929
|
-
a: { class: '
|
|
1714
|
+
a: { class: 'bw_page_link', href: '#', onclick: handleClick(currentPage - 1), 'aria-label': 'Previous' },
|
|
1930
1715
|
c: '\u2039'
|
|
1931
1716
|
}
|
|
1932
1717
|
});
|
|
@@ -1936,10 +1721,10 @@ export function makePagination(props = {}) {
|
|
|
1936
1721
|
(function(pageNum) {
|
|
1937
1722
|
items.push({
|
|
1938
1723
|
t: 'li',
|
|
1939
|
-
a: { class: `
|
|
1724
|
+
a: { class: `bw_page_item ${pageNum === currentPage ? 'bw_active' : ''}`.trim() },
|
|
1940
1725
|
c: {
|
|
1941
1726
|
t: 'a',
|
|
1942
|
-
a: { class: '
|
|
1727
|
+
a: { class: 'bw_page_link', href: '#', onclick: handleClick(pageNum) },
|
|
1943
1728
|
c: '' + pageNum
|
|
1944
1729
|
}
|
|
1945
1730
|
});
|
|
@@ -1949,10 +1734,10 @@ export function makePagination(props = {}) {
|
|
|
1949
1734
|
// Next arrow
|
|
1950
1735
|
items.push({
|
|
1951
1736
|
t: 'li',
|
|
1952
|
-
a: { class: `
|
|
1737
|
+
a: { class: `bw_page_item ${currentPage >= pages ? 'bw_disabled' : ''}`.trim() },
|
|
1953
1738
|
c: {
|
|
1954
1739
|
t: 'a',
|
|
1955
|
-
a: { class: '
|
|
1740
|
+
a: { class: 'bw_page_link', href: '#', onclick: handleClick(currentPage + 1), 'aria-label': 'Next' },
|
|
1956
1741
|
c: '\u203A'
|
|
1957
1742
|
}
|
|
1958
1743
|
});
|
|
@@ -1963,7 +1748,7 @@ export function makePagination(props = {}) {
|
|
|
1963
1748
|
c: {
|
|
1964
1749
|
t: 'ul',
|
|
1965
1750
|
a: {
|
|
1966
|
-
class: `
|
|
1751
|
+
class: `bw_pagination ${size ? 'bw_pagination_' + size : ''} ${className}`.trim()
|
|
1967
1752
|
},
|
|
1968
1753
|
c: items
|
|
1969
1754
|
}
|
|
@@ -2005,13 +1790,13 @@ export function makeRadio(props = {}) {
|
|
|
2005
1790
|
|
|
2006
1791
|
return {
|
|
2007
1792
|
t: 'div',
|
|
2008
|
-
a: { class: `
|
|
1793
|
+
a: { class: `bw_form_check ${className}`.trim() },
|
|
2009
1794
|
c: [
|
|
2010
1795
|
{
|
|
2011
1796
|
t: 'input',
|
|
2012
1797
|
a: {
|
|
2013
1798
|
type: 'radio',
|
|
2014
|
-
class: '
|
|
1799
|
+
class: 'bw_form_check_input',
|
|
2015
1800
|
name,
|
|
2016
1801
|
value,
|
|
2017
1802
|
checked,
|
|
@@ -2022,7 +1807,7 @@ export function makeRadio(props = {}) {
|
|
|
2022
1807
|
},
|
|
2023
1808
|
label && {
|
|
2024
1809
|
t: 'label',
|
|
2025
|
-
a: { class: '
|
|
1810
|
+
a: { class: 'bw_form_check_label', for: id },
|
|
2026
1811
|
c: label
|
|
2027
1812
|
}
|
|
2028
1813
|
].filter(Boolean)
|
|
@@ -2059,7 +1844,7 @@ export function makeButtonGroup(props = {}) {
|
|
|
2059
1844
|
return {
|
|
2060
1845
|
t: 'div',
|
|
2061
1846
|
a: {
|
|
2062
|
-
class: `${vertical ? '
|
|
1847
|
+
class: `${vertical ? 'bw_btn_group_vertical' : 'bw_btn_group'} ${size ? 'bw_btn_group_' + size : ''} ${className}`.trim(),
|
|
2063
1848
|
role: 'group'
|
|
2064
1849
|
},
|
|
2065
1850
|
c: children
|
|
@@ -2099,53 +1884,71 @@ export function makeAccordion(props = {}) {
|
|
|
2099
1884
|
|
|
2100
1885
|
return {
|
|
2101
1886
|
t: 'div',
|
|
2102
|
-
a: { class: `
|
|
1887
|
+
a: { class: `bw_accordion ${className}`.trim() },
|
|
2103
1888
|
c: items.map(function(item, index) {
|
|
2104
1889
|
return {
|
|
2105
1890
|
t: 'div',
|
|
2106
|
-
a: { class: '
|
|
1891
|
+
a: { class: 'bw_accordion_item' },
|
|
2107
1892
|
c: [
|
|
2108
1893
|
{
|
|
2109
1894
|
t: 'h2',
|
|
2110
|
-
a: { class: '
|
|
1895
|
+
a: { class: 'bw_accordion_header' },
|
|
2111
1896
|
c: {
|
|
2112
1897
|
t: 'button',
|
|
2113
1898
|
a: {
|
|
2114
|
-
class: `
|
|
1899
|
+
class: `bw_accordion_button ${item.open ? '' : 'bw_collapsed'}`.trim(),
|
|
2115
1900
|
type: 'button',
|
|
2116
1901
|
'aria-expanded': item.open ? 'true' : 'false',
|
|
2117
1902
|
'data-accordion-index': index,
|
|
2118
1903
|
onclick: function(e) {
|
|
2119
|
-
var btn = e.target.closest('.
|
|
2120
|
-
var accordionEl = btn.closest('.
|
|
2121
|
-
var accordionItem = btn.closest('.
|
|
2122
|
-
var collapse = accordionItem.querySelector('.
|
|
2123
|
-
var isOpen = collapse.classList.contains('
|
|
1904
|
+
var btn = e.target.closest('.bw_accordion_button');
|
|
1905
|
+
var accordionEl = btn.closest('.bw_accordion');
|
|
1906
|
+
var accordionItem = btn.closest('.bw_accordion_item');
|
|
1907
|
+
var collapse = accordionItem.querySelector('.bw_accordion_collapse');
|
|
1908
|
+
var isOpen = collapse.classList.contains('bw_collapse_show');
|
|
2124
1909
|
|
|
2125
1910
|
if (!multiOpen) {
|
|
2126
|
-
//
|
|
2127
|
-
var
|
|
2128
|
-
var
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
1911
|
+
// Animate-close all other open siblings
|
|
1912
|
+
var allItems = accordionEl.querySelectorAll('.bw_accordion_item');
|
|
1913
|
+
for (var j = 0; j < allItems.length; j++) {
|
|
1914
|
+
if (allItems[j] === accordionItem) continue;
|
|
1915
|
+
var sibCollapse = allItems[j].querySelector('.bw_accordion_collapse');
|
|
1916
|
+
var sibBtn = allItems[j].querySelector('.bw_accordion_button');
|
|
1917
|
+
if (sibCollapse.classList.contains('bw_collapse_show')) {
|
|
1918
|
+
sibCollapse.style.maxHeight = sibCollapse.scrollHeight + 'px';
|
|
1919
|
+
sibCollapse.offsetHeight; // force reflow
|
|
1920
|
+
sibCollapse.style.maxHeight = '0px';
|
|
1921
|
+
sibCollapse.classList.remove('bw_collapse_show');
|
|
1922
|
+
sibBtn.classList.add('bw_collapsed');
|
|
1923
|
+
sibBtn.setAttribute('aria-expanded', 'false');
|
|
1924
|
+
}
|
|
2136
1925
|
}
|
|
2137
1926
|
}
|
|
2138
1927
|
|
|
2139
1928
|
if (isOpen) {
|
|
2140
|
-
|
|
2141
|
-
collapse.style.maxHeight =
|
|
2142
|
-
|
|
1929
|
+
// Animate close
|
|
1930
|
+
collapse.style.maxHeight = collapse.scrollHeight + 'px';
|
|
1931
|
+
collapse.offsetHeight; // force reflow
|
|
1932
|
+
collapse.style.maxHeight = '0px';
|
|
1933
|
+
collapse.classList.remove('bw_collapse_show');
|
|
1934
|
+
btn.classList.add('bw_collapsed');
|
|
2143
1935
|
btn.setAttribute('aria-expanded', 'false');
|
|
2144
1936
|
} else {
|
|
2145
|
-
|
|
1937
|
+
// Animate open
|
|
1938
|
+
collapse.classList.add('bw_collapse_show');
|
|
1939
|
+
collapse.style.maxHeight = '0px';
|
|
1940
|
+
collapse.offsetHeight; // force reflow
|
|
2146
1941
|
collapse.style.maxHeight = collapse.scrollHeight + 'px';
|
|
2147
|
-
btn.classList.remove('
|
|
1942
|
+
btn.classList.remove('bw_collapsed');
|
|
2148
1943
|
btn.setAttribute('aria-expanded', 'true');
|
|
1944
|
+
// After transition, allow dynamic content sizing
|
|
1945
|
+
var onEnd = function(ev) {
|
|
1946
|
+
if (ev.propertyName === 'max-height' && collapse.classList.contains('bw_collapse_show')) {
|
|
1947
|
+
collapse.style.maxHeight = 'none';
|
|
1948
|
+
}
|
|
1949
|
+
collapse.removeEventListener('transitionend', onEnd);
|
|
1950
|
+
};
|
|
1951
|
+
collapse.addEventListener('transitionend', onEnd);
|
|
2149
1952
|
}
|
|
2150
1953
|
}
|
|
2151
1954
|
},
|
|
@@ -2154,15 +1957,15 @@ export function makeAccordion(props = {}) {
|
|
|
2154
1957
|
},
|
|
2155
1958
|
{
|
|
2156
1959
|
t: 'div',
|
|
2157
|
-
a: { class: `
|
|
1960
|
+
a: { class: `bw_accordion_collapse ${item.open ? 'bw_collapse_show' : ''}`.trim() },
|
|
2158
1961
|
c: {
|
|
2159
1962
|
t: 'div',
|
|
2160
|
-
a: { class: '
|
|
1963
|
+
a: { class: 'bw_accordion_body' },
|
|
2161
1964
|
c: item.content
|
|
2162
1965
|
},
|
|
2163
1966
|
o: item.open ? {
|
|
2164
1967
|
mounted: function(el) {
|
|
2165
|
-
el.style.maxHeight =
|
|
1968
|
+
el.style.maxHeight = 'none';
|
|
2166
1969
|
}
|
|
2167
1970
|
} : undefined
|
|
2168
1971
|
}
|
|
@@ -2176,60 +1979,8 @@ export function makeAccordion(props = {}) {
|
|
|
2176
1979
|
};
|
|
2177
1980
|
}
|
|
2178
1981
|
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
*
|
|
2182
|
-
* Provides `.show()`, `.hide()`, `.toggle()`, and `.destroy()` methods
|
|
2183
|
-
* for controlling the modal programmatically.
|
|
2184
|
-
*
|
|
2185
|
-
* @category Component Handles
|
|
2186
|
-
*/
|
|
2187
|
-
export class ModalHandle {
|
|
2188
|
-
/**
|
|
2189
|
-
* @param {Element} element - The modal backdrop DOM element
|
|
2190
|
-
* @param {Object} taco - The original TACO object
|
|
2191
|
-
*/
|
|
2192
|
-
constructor(element, taco) {
|
|
2193
|
-
this.element = element;
|
|
2194
|
-
this._taco = taco;
|
|
2195
|
-
this._escHandler = null;
|
|
2196
|
-
}
|
|
2197
|
-
|
|
2198
|
-
/** Show the modal */
|
|
2199
|
-
show() {
|
|
2200
|
-
this.element.classList.add('bw-modal-show');
|
|
2201
|
-
document.body.style.overflow = 'hidden';
|
|
2202
|
-
return this;
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
/** Hide the modal */
|
|
2206
|
-
hide() {
|
|
2207
|
-
this.element.classList.remove('bw-modal-show');
|
|
2208
|
-
document.body.style.overflow = '';
|
|
2209
|
-
return this;
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
/** Toggle modal visibility */
|
|
2213
|
-
toggle() {
|
|
2214
|
-
if (this.element.classList.contains('bw-modal-show')) {
|
|
2215
|
-
this.hide();
|
|
2216
|
-
} else {
|
|
2217
|
-
this.show();
|
|
2218
|
-
}
|
|
2219
|
-
return this;
|
|
2220
|
-
}
|
|
2221
|
-
|
|
2222
|
-
/** Remove the modal from DOM and clean up */
|
|
2223
|
-
destroy() {
|
|
2224
|
-
this.hide();
|
|
2225
|
-
if (this._escHandler) {
|
|
2226
|
-
document.removeEventListener('keydown', this._escHandler);
|
|
2227
|
-
}
|
|
2228
|
-
if (this.element.parentNode) {
|
|
2229
|
-
this.element.parentNode.removeChild(this.element);
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
1982
|
+
// ModalHandle removed in v2.0.15 — superseded by ComponentHandle.
|
|
1983
|
+
// See dev/dead-code-elimination-v2.0.15.md for recovery.
|
|
2233
1984
|
|
|
2234
1985
|
/**
|
|
2235
1986
|
* Create a modal dialog overlay
|
|
@@ -2263,9 +2014,9 @@ export function makeModal(props = {}) {
|
|
|
2263
2014
|
} = props;
|
|
2264
2015
|
|
|
2265
2016
|
function closeModal(el) {
|
|
2266
|
-
var backdrop = el.closest('.
|
|
2017
|
+
var backdrop = el.closest('.bw_modal');
|
|
2267
2018
|
if (backdrop) {
|
|
2268
|
-
backdrop.classList.remove('
|
|
2019
|
+
backdrop.classList.remove('bw_modal_show');
|
|
2269
2020
|
document.body.style.overflow = '';
|
|
2270
2021
|
}
|
|
2271
2022
|
if (onClose) onClose();
|
|
@@ -2273,24 +2024,24 @@ export function makeModal(props = {}) {
|
|
|
2273
2024
|
|
|
2274
2025
|
return {
|
|
2275
2026
|
t: 'div',
|
|
2276
|
-
a: { class: `
|
|
2027
|
+
a: { class: `bw_modal ${className}`.trim() },
|
|
2277
2028
|
c: {
|
|
2278
2029
|
t: 'div',
|
|
2279
|
-
a: { class: `
|
|
2030
|
+
a: { class: `bw_modal_dialog ${size ? 'bw_modal_' + size : ''}`.trim() },
|
|
2280
2031
|
c: {
|
|
2281
2032
|
t: 'div',
|
|
2282
|
-
a: { class: '
|
|
2033
|
+
a: { class: 'bw_modal_content' },
|
|
2283
2034
|
c: [
|
|
2284
2035
|
(title || closeButton) && {
|
|
2285
2036
|
t: 'div',
|
|
2286
|
-
a: { class: '
|
|
2037
|
+
a: { class: 'bw_modal_header' },
|
|
2287
2038
|
c: [
|
|
2288
|
-
title && { t: 'h5', a: { class: '
|
|
2039
|
+
title && { t: 'h5', a: { class: 'bw_modal_title' }, c: title },
|
|
2289
2040
|
closeButton && {
|
|
2290
2041
|
t: 'button',
|
|
2291
2042
|
a: {
|
|
2292
2043
|
type: 'button',
|
|
2293
|
-
class: '
|
|
2044
|
+
class: 'bw_close',
|
|
2294
2045
|
'aria-label': 'Close',
|
|
2295
2046
|
onclick: function(e) { closeModal(e.target); }
|
|
2296
2047
|
},
|
|
@@ -2300,12 +2051,12 @@ export function makeModal(props = {}) {
|
|
|
2300
2051
|
},
|
|
2301
2052
|
content && {
|
|
2302
2053
|
t: 'div',
|
|
2303
|
-
a: { class: '
|
|
2054
|
+
a: { class: 'bw_modal_body' },
|
|
2304
2055
|
c: content
|
|
2305
2056
|
},
|
|
2306
2057
|
footer && {
|
|
2307
2058
|
t: 'div',
|
|
2308
|
-
a: { class: '
|
|
2059
|
+
a: { class: 'bw_modal_footer' },
|
|
2309
2060
|
c: footer
|
|
2310
2061
|
}
|
|
2311
2062
|
].filter(Boolean)
|
|
@@ -2320,7 +2071,7 @@ export function makeModal(props = {}) {
|
|
|
2320
2071
|
});
|
|
2321
2072
|
// Escape key to close
|
|
2322
2073
|
var escHandler = function(e) {
|
|
2323
|
-
if (e.key === 'Escape' && el.classList.contains('
|
|
2074
|
+
if (e.key === 'Escape' && el.classList.contains('bw_modal_show')) {
|
|
2324
2075
|
closeModal(el);
|
|
2325
2076
|
}
|
|
2326
2077
|
};
|
|
@@ -2371,26 +2122,26 @@ export function makeToast(props = {}) {
|
|
|
2371
2122
|
return {
|
|
2372
2123
|
t: 'div',
|
|
2373
2124
|
a: {
|
|
2374
|
-
class: `
|
|
2125
|
+
class: `bw_toast ${variantClass(variant)} ${className}`.trim(),
|
|
2375
2126
|
role: 'alert',
|
|
2376
2127
|
'data-position': position
|
|
2377
2128
|
},
|
|
2378
2129
|
c: [
|
|
2379
2130
|
(title) && {
|
|
2380
2131
|
t: 'div',
|
|
2381
|
-
a: { class: '
|
|
2132
|
+
a: { class: 'bw_toast_header' },
|
|
2382
2133
|
c: [
|
|
2383
2134
|
{ t: 'strong', c: title },
|
|
2384
2135
|
{
|
|
2385
2136
|
t: 'button',
|
|
2386
2137
|
a: {
|
|
2387
2138
|
type: 'button',
|
|
2388
|
-
class: '
|
|
2139
|
+
class: 'bw_close',
|
|
2389
2140
|
'aria-label': 'Close',
|
|
2390
2141
|
onclick: function(e) {
|
|
2391
|
-
var toast = e.target.closest('.
|
|
2142
|
+
var toast = e.target.closest('.bw_toast');
|
|
2392
2143
|
if (toast) {
|
|
2393
|
-
toast.classList.add('
|
|
2144
|
+
toast.classList.add('bw_toast_hiding');
|
|
2394
2145
|
setTimeout(function() { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 300);
|
|
2395
2146
|
}
|
|
2396
2147
|
}
|
|
@@ -2401,7 +2152,7 @@ export function makeToast(props = {}) {
|
|
|
2401
2152
|
},
|
|
2402
2153
|
content && {
|
|
2403
2154
|
t: 'div',
|
|
2404
|
-
a: { class: '
|
|
2155
|
+
a: { class: 'bw_toast_body' },
|
|
2405
2156
|
c: content
|
|
2406
2157
|
}
|
|
2407
2158
|
].filter(Boolean),
|
|
@@ -2410,12 +2161,12 @@ export function makeToast(props = {}) {
|
|
|
2410
2161
|
mounted: function(el) {
|
|
2411
2162
|
// Trigger show animation
|
|
2412
2163
|
requestAnimationFrame(function() {
|
|
2413
|
-
el.classList.add('
|
|
2164
|
+
el.classList.add('bw_toast_show');
|
|
2414
2165
|
});
|
|
2415
2166
|
// Auto-dismiss
|
|
2416
2167
|
if (autoDismiss) {
|
|
2417
2168
|
setTimeout(function() {
|
|
2418
|
-
el.classList.add('
|
|
2169
|
+
el.classList.add('bw_toast_hiding');
|
|
2419
2170
|
setTimeout(function() { if (el.parentNode) el.parentNode.removeChild(el); }, 300);
|
|
2420
2171
|
}, delay);
|
|
2421
2172
|
}
|
|
@@ -2468,12 +2219,12 @@ export function makeDropdown(props = {}) {
|
|
|
2468
2219
|
triggerTaco = {
|
|
2469
2220
|
t: 'button',
|
|
2470
2221
|
a: {
|
|
2471
|
-
class: `
|
|
2222
|
+
class: `bw_btn ${variantClass(variant)} bw_dropdown_toggle`,
|
|
2472
2223
|
type: 'button',
|
|
2473
2224
|
onclick: function(e) {
|
|
2474
|
-
var dropdown = e.target.closest('.
|
|
2475
|
-
var menu = dropdown.querySelector('.
|
|
2476
|
-
menu.classList.toggle('
|
|
2225
|
+
var dropdown = e.target.closest('.bw_dropdown');
|
|
2226
|
+
var menu = dropdown.querySelector('.bw_dropdown_menu');
|
|
2227
|
+
menu.classList.toggle('bw_dropdown_show');
|
|
2477
2228
|
}
|
|
2478
2229
|
},
|
|
2479
2230
|
c: trigger || 'Dropdown'
|
|
@@ -2484,26 +2235,26 @@ export function makeDropdown(props = {}) {
|
|
|
2484
2235
|
|
|
2485
2236
|
return {
|
|
2486
2237
|
t: 'div',
|
|
2487
|
-
a: { class: `
|
|
2238
|
+
a: { class: `bw_dropdown ${className}`.trim() },
|
|
2488
2239
|
c: [
|
|
2489
2240
|
triggerTaco,
|
|
2490
2241
|
{
|
|
2491
2242
|
t: 'div',
|
|
2492
|
-
a: { class: `
|
|
2243
|
+
a: { class: `bw_dropdown_menu ${align === 'end' ? 'bw_dropdown_menu_end' : ''}`.trim() },
|
|
2493
2244
|
c: items.map(function(item) {
|
|
2494
2245
|
if (item.divider) {
|
|
2495
|
-
return { t: 'hr', a: { class: '
|
|
2246
|
+
return { t: 'hr', a: { class: 'bw_dropdown_divider' } };
|
|
2496
2247
|
}
|
|
2497
2248
|
return {
|
|
2498
2249
|
t: 'a',
|
|
2499
2250
|
a: {
|
|
2500
|
-
class: `
|
|
2251
|
+
class: `bw_dropdown_item ${item.disabled ? 'disabled' : ''}`.trim(),
|
|
2501
2252
|
href: item.href || '#',
|
|
2502
2253
|
onclick: item.disabled ? undefined : function(e) {
|
|
2503
2254
|
if (!item.href) e.preventDefault();
|
|
2504
|
-
var dropdown = e.target.closest('.
|
|
2505
|
-
var menu = dropdown.querySelector('.
|
|
2506
|
-
menu.classList.remove('
|
|
2255
|
+
var dropdown = e.target.closest('.bw_dropdown');
|
|
2256
|
+
var menu = dropdown.querySelector('.bw_dropdown_menu');
|
|
2257
|
+
menu.classList.remove('bw_dropdown_show');
|
|
2507
2258
|
if (item.onclick) item.onclick(e);
|
|
2508
2259
|
}
|
|
2509
2260
|
},
|
|
@@ -2518,8 +2269,8 @@ export function makeDropdown(props = {}) {
|
|
|
2518
2269
|
// Click outside to close
|
|
2519
2270
|
var outsideHandler = function(e) {
|
|
2520
2271
|
if (!el.contains(e.target)) {
|
|
2521
|
-
var menu = el.querySelector('.
|
|
2522
|
-
if (menu) menu.classList.remove('
|
|
2272
|
+
var menu = el.querySelector('.bw_dropdown_menu');
|
|
2273
|
+
if (menu) menu.classList.remove('bw_dropdown_show');
|
|
2523
2274
|
}
|
|
2524
2275
|
};
|
|
2525
2276
|
document.addEventListener('click', outsideHandler);
|
|
@@ -2566,13 +2317,13 @@ export function makeSwitch(props = {}) {
|
|
|
2566
2317
|
|
|
2567
2318
|
return {
|
|
2568
2319
|
t: 'div',
|
|
2569
|
-
a: { class: `
|
|
2320
|
+
a: { class: `bw_form_check bw_form_switch ${className}`.trim() },
|
|
2570
2321
|
c: [
|
|
2571
2322
|
{
|
|
2572
2323
|
t: 'input',
|
|
2573
2324
|
a: {
|
|
2574
2325
|
type: 'checkbox',
|
|
2575
|
-
class: '
|
|
2326
|
+
class: 'bw_form_check_input bw_switch_input',
|
|
2576
2327
|
role: 'switch',
|
|
2577
2328
|
checked,
|
|
2578
2329
|
id,
|
|
@@ -2583,7 +2334,7 @@ export function makeSwitch(props = {}) {
|
|
|
2583
2334
|
},
|
|
2584
2335
|
label && {
|
|
2585
2336
|
t: 'label',
|
|
2586
|
-
a: { class: '
|
|
2337
|
+
a: { class: 'bw_form_check_label', for: id },
|
|
2587
2338
|
c: label
|
|
2588
2339
|
}
|
|
2589
2340
|
].filter(Boolean)
|
|
@@ -2618,7 +2369,7 @@ export function makeSkeleton(props = {}) {
|
|
|
2618
2369
|
return {
|
|
2619
2370
|
t: 'div',
|
|
2620
2371
|
a: {
|
|
2621
|
-
class: `
|
|
2372
|
+
class: `bw_skeleton bw_skeleton_circle ${className}`.trim(),
|
|
2622
2373
|
style: { width: circleSize, height: circleSize }
|
|
2623
2374
|
}
|
|
2624
2375
|
};
|
|
@@ -2628,7 +2379,7 @@ export function makeSkeleton(props = {}) {
|
|
|
2628
2379
|
return {
|
|
2629
2380
|
t: 'div',
|
|
2630
2381
|
a: {
|
|
2631
|
-
class: `
|
|
2382
|
+
class: `bw_skeleton bw_skeleton_rect ${className}`.trim(),
|
|
2632
2383
|
style: {
|
|
2633
2384
|
width: width || '100%',
|
|
2634
2385
|
height: height || '120px'
|
|
@@ -2642,7 +2393,7 @@ export function makeSkeleton(props = {}) {
|
|
|
2642
2393
|
return {
|
|
2643
2394
|
t: 'div',
|
|
2644
2395
|
a: {
|
|
2645
|
-
class: `
|
|
2396
|
+
class: `bw_skeleton bw_skeleton_text ${className}`.trim(),
|
|
2646
2397
|
style: {
|
|
2647
2398
|
width: width || '100%',
|
|
2648
2399
|
height: height || '1em'
|
|
@@ -2656,7 +2407,7 @@ export function makeSkeleton(props = {}) {
|
|
|
2656
2407
|
lines.push({
|
|
2657
2408
|
t: 'div',
|
|
2658
2409
|
a: {
|
|
2659
|
-
class: '
|
|
2410
|
+
class: 'bw_skeleton bw_skeleton_text',
|
|
2660
2411
|
style: {
|
|
2661
2412
|
width: i === count - 1 ? '75%' : (width || '100%'),
|
|
2662
2413
|
height: height || '1em'
|
|
@@ -2667,7 +2418,7 @@ export function makeSkeleton(props = {}) {
|
|
|
2667
2418
|
|
|
2668
2419
|
return {
|
|
2669
2420
|
t: 'div',
|
|
2670
|
-
a: { class: `
|
|
2421
|
+
a: { class: `bw_skeleton_group ${className}`.trim() },
|
|
2671
2422
|
c: lines
|
|
2672
2423
|
};
|
|
2673
2424
|
}
|
|
@@ -2702,7 +2453,7 @@ export function makeAvatar(props = {}) {
|
|
|
2702
2453
|
return {
|
|
2703
2454
|
t: 'img',
|
|
2704
2455
|
a: {
|
|
2705
|
-
class: `
|
|
2456
|
+
class: `bw_avatar bw_avatar_${size} ${className}`.trim(),
|
|
2706
2457
|
src: src,
|
|
2707
2458
|
alt: alt
|
|
2708
2459
|
}
|
|
@@ -2712,7 +2463,7 @@ export function makeAvatar(props = {}) {
|
|
|
2712
2463
|
return {
|
|
2713
2464
|
t: 'div',
|
|
2714
2465
|
a: {
|
|
2715
|
-
class: `
|
|
2466
|
+
class: `bw_avatar bw_avatar_${size} ${variantClass(variant)} ${className}`.trim()
|
|
2716
2467
|
},
|
|
2717
2468
|
c: initials || ''
|
|
2718
2469
|
};
|
|
@@ -2761,14 +2512,14 @@ export function makeCarousel(props = {}) {
|
|
|
2761
2512
|
|
|
2762
2513
|
// Shared navigation logic
|
|
2763
2514
|
function goToSlide(carouselEl, index) {
|
|
2764
|
-
var total = carouselEl.querySelectorAll('.
|
|
2515
|
+
var total = carouselEl.querySelectorAll('.bw_carousel_slide').length;
|
|
2765
2516
|
if (index < 0) index = total - 1;
|
|
2766
2517
|
if (index >= total) index = 0;
|
|
2767
2518
|
carouselEl.setAttribute('data-carousel-index', index);
|
|
2768
|
-
var track = carouselEl.querySelector('.
|
|
2519
|
+
var track = carouselEl.querySelector('.bw_carousel_track');
|
|
2769
2520
|
track.style.transform = 'translateX(-' + (index * 100) + '%)';
|
|
2770
2521
|
// Update indicators
|
|
2771
|
-
var indicators = carouselEl.querySelectorAll('.
|
|
2522
|
+
var indicators = carouselEl.querySelectorAll('.bw_carousel_indicator');
|
|
2772
2523
|
for (var i = 0; i < indicators.length; i++) {
|
|
2773
2524
|
if (i === index) {
|
|
2774
2525
|
indicators[i].classList.add('active');
|
|
@@ -2787,14 +2538,14 @@ export function makeCarousel(props = {}) {
|
|
|
2787
2538
|
item.content,
|
|
2788
2539
|
item.caption && {
|
|
2789
2540
|
t: 'div',
|
|
2790
|
-
a: { class: '
|
|
2541
|
+
a: { class: 'bw_carousel_caption' },
|
|
2791
2542
|
c: item.caption
|
|
2792
2543
|
}
|
|
2793
2544
|
].filter(Boolean);
|
|
2794
2545
|
|
|
2795
2546
|
return {
|
|
2796
2547
|
t: 'div',
|
|
2797
|
-
a: { class: '
|
|
2548
|
+
a: { class: 'bw_carousel_slide' },
|
|
2798
2549
|
c: slideContent.length === 1 ? slideContent[0] : slideContent
|
|
2799
2550
|
};
|
|
2800
2551
|
});
|
|
@@ -2804,7 +2555,7 @@ export function makeCarousel(props = {}) {
|
|
|
2804
2555
|
{
|
|
2805
2556
|
t: 'div',
|
|
2806
2557
|
a: {
|
|
2807
|
-
class: '
|
|
2558
|
+
class: 'bw_carousel_track',
|
|
2808
2559
|
style: 'transform: translateX(-' + (startIndex * 100) + '%)'
|
|
2809
2560
|
},
|
|
2810
2561
|
c: slides
|
|
@@ -2816,11 +2567,11 @@ export function makeCarousel(props = {}) {
|
|
|
2816
2567
|
children.push({
|
|
2817
2568
|
t: 'button',
|
|
2818
2569
|
a: {
|
|
2819
|
-
class: '
|
|
2570
|
+
class: 'bw_carousel_control bw_carousel_control_prev',
|
|
2820
2571
|
type: 'button',
|
|
2821
2572
|
'aria-label': 'Previous slide',
|
|
2822
2573
|
onclick: function(e) {
|
|
2823
|
-
var carousel = e.target.closest('.
|
|
2574
|
+
var carousel = e.target.closest('.bw_carousel');
|
|
2824
2575
|
var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
|
|
2825
2576
|
goToSlide(carousel, idx - 1);
|
|
2826
2577
|
}
|
|
@@ -2830,11 +2581,11 @@ export function makeCarousel(props = {}) {
|
|
|
2830
2581
|
children.push({
|
|
2831
2582
|
t: 'button',
|
|
2832
2583
|
a: {
|
|
2833
|
-
class: '
|
|
2584
|
+
class: 'bw_carousel_control bw_carousel_control_next',
|
|
2834
2585
|
type: 'button',
|
|
2835
2586
|
'aria-label': 'Next slide',
|
|
2836
2587
|
onclick: function(e) {
|
|
2837
|
-
var carousel = e.target.closest('.
|
|
2588
|
+
var carousel = e.target.closest('.bw_carousel');
|
|
2838
2589
|
var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
|
|
2839
2590
|
goToSlide(carousel, idx + 1);
|
|
2840
2591
|
}
|
|
@@ -2847,17 +2598,17 @@ export function makeCarousel(props = {}) {
|
|
|
2847
2598
|
if (showIndicators && items.length > 1) {
|
|
2848
2599
|
children.push({
|
|
2849
2600
|
t: 'div',
|
|
2850
|
-
a: { class: '
|
|
2601
|
+
a: { class: 'bw_carousel_indicators' },
|
|
2851
2602
|
c: items.map(function(_, i) {
|
|
2852
2603
|
return {
|
|
2853
2604
|
t: 'button',
|
|
2854
2605
|
a: {
|
|
2855
|
-
class: '
|
|
2606
|
+
class: 'bw_carousel_indicator' + (i === startIndex ? ' active' : ''),
|
|
2856
2607
|
type: 'button',
|
|
2857
2608
|
'aria-label': 'Go to slide ' + (i + 1),
|
|
2858
2609
|
'data-slide-index': i,
|
|
2859
2610
|
onclick: function(e) {
|
|
2860
|
-
var carousel = e.target.closest('.
|
|
2611
|
+
var carousel = e.target.closest('.bw_carousel');
|
|
2861
2612
|
var idx = parseInt(e.target.getAttribute('data-slide-index'));
|
|
2862
2613
|
goToSlide(carousel, idx);
|
|
2863
2614
|
}
|
|
@@ -2870,34 +2621,994 @@ export function makeCarousel(props = {}) {
|
|
|
2870
2621
|
return {
|
|
2871
2622
|
t: 'div',
|
|
2872
2623
|
a: {
|
|
2873
|
-
class: ('
|
|
2624
|
+
class: ('bw_carousel ' + className).trim(),
|
|
2874
2625
|
style: 'height: ' + height,
|
|
2626
|
+
tabindex: '0',
|
|
2627
|
+
'aria-roledescription': 'carousel',
|
|
2875
2628
|
'data-carousel-index': startIndex
|
|
2876
2629
|
},
|
|
2877
2630
|
c: children,
|
|
2878
2631
|
o: {
|
|
2879
2632
|
type: 'carousel',
|
|
2880
2633
|
state: { activeIndex: startIndex, autoPlay: autoPlay, interval: interval },
|
|
2881
|
-
mounted:
|
|
2882
|
-
|
|
2634
|
+
mounted: function(el) {
|
|
2635
|
+
// Keyboard navigation
|
|
2636
|
+
el.addEventListener('keydown', function(e) {
|
|
2883
2637
|
var idx = parseInt(el.getAttribute('data-carousel-index') || '0');
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2638
|
+
if (e.key === 'ArrowLeft') {
|
|
2639
|
+
e.preventDefault();
|
|
2640
|
+
goToSlide(el, idx - 1);
|
|
2641
|
+
} else if (e.key === 'ArrowRight') {
|
|
2642
|
+
e.preventDefault();
|
|
2643
|
+
goToSlide(el, idx + 1);
|
|
2644
|
+
}
|
|
2645
|
+
});
|
|
2646
|
+
// Auto-play
|
|
2647
|
+
if (autoPlay) {
|
|
2648
|
+
var intervalId = setInterval(function() {
|
|
2649
|
+
var idx = parseInt(el.getAttribute('data-carousel-index') || '0');
|
|
2650
|
+
goToSlide(el, idx + 1);
|
|
2651
|
+
}, interval);
|
|
2652
|
+
el._bw_carouselInterval = intervalId;
|
|
2653
|
+
// Pause on hover/focus for usability
|
|
2654
|
+
el.addEventListener('mouseenter', function() {
|
|
2655
|
+
if (el._bw_carouselInterval) clearInterval(el._bw_carouselInterval);
|
|
2656
|
+
});
|
|
2657
|
+
el.addEventListener('mouseleave', function() {
|
|
2658
|
+
el._bw_carouselInterval = setInterval(function() {
|
|
2659
|
+
var idx = parseInt(el.getAttribute('data-carousel-index') || '0');
|
|
2660
|
+
goToSlide(el, idx + 1);
|
|
2661
|
+
}, interval);
|
|
2662
|
+
});
|
|
2663
|
+
}
|
|
2664
|
+
},
|
|
2665
|
+
unmount: function(el) {
|
|
2889
2666
|
if (el._bw_carouselInterval) {
|
|
2890
2667
|
clearInterval(el._bw_carouselInterval);
|
|
2891
2668
|
}
|
|
2892
|
-
}
|
|
2669
|
+
}
|
|
2893
2670
|
}
|
|
2894
2671
|
};
|
|
2895
2672
|
}
|
|
2896
2673
|
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2674
|
+
// =========================================================================
|
|
2675
|
+
// Phase 4: Dashboard & Data Display
|
|
2676
|
+
// =========================================================================
|
|
2677
|
+
|
|
2678
|
+
/**
|
|
2679
|
+
* Create a stat card for dashboard metrics display
|
|
2680
|
+
*
|
|
2681
|
+
* Shows a large value with a label and optional change indicator.
|
|
2682
|
+
* Designed for dashboard grid layouts with left-border accent.
|
|
2683
|
+
*
|
|
2684
|
+
* @param {Object|string} [props] - Stat card configuration (string shorthand sets label)
|
|
2685
|
+
* @param {string|number} [props.value=0] - The main stat value to display
|
|
2686
|
+
* @param {string} [props.label] - Descriptive label below the value
|
|
2687
|
+
* @param {number} [props.change] - Percentage change indicator (positive = green arrow, negative = red)
|
|
2688
|
+
* @param {string} [props.format] - Value format ("number", "currency", "percent")
|
|
2689
|
+
* @param {string} [props.prefix] - Custom prefix (e.g. "$")
|
|
2690
|
+
* @param {string} [props.suffix] - Custom suffix (e.g. "%")
|
|
2691
|
+
* @param {string} [props.icon] - Icon content (emoji or text) shown above value
|
|
2692
|
+
* @param {string} [props.variant] - Left-border color variant ("primary", "success", "danger", etc.)
|
|
2693
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
2694
|
+
* @param {Object} [props.style] - Inline style object
|
|
2695
|
+
* @returns {Object} TACO object representing a stat card
|
|
2696
|
+
* @category Component Builders
|
|
2697
|
+
* @example
|
|
2698
|
+
* const stat = makeStatCard({
|
|
2699
|
+
* value: 2345,
|
|
2700
|
+
* label: 'Active Users',
|
|
2701
|
+
* change: 5.3,
|
|
2702
|
+
* format: 'number',
|
|
2703
|
+
* variant: 'primary'
|
|
2704
|
+
* });
|
|
2705
|
+
*/
|
|
2706
|
+
export function makeStatCard(props = {}) {
|
|
2707
|
+
if (typeof props === 'string') props = { label: props };
|
|
2708
|
+
var {
|
|
2709
|
+
value = 0,
|
|
2710
|
+
label,
|
|
2711
|
+
change,
|
|
2712
|
+
format,
|
|
2713
|
+
prefix,
|
|
2714
|
+
suffix,
|
|
2715
|
+
icon,
|
|
2716
|
+
variant,
|
|
2717
|
+
className = '',
|
|
2718
|
+
style
|
|
2719
|
+
} = props;
|
|
2720
|
+
|
|
2721
|
+
function formatValue(val, fmt) {
|
|
2722
|
+
if (prefix || suffix) return (prefix || '') + val + (suffix || '');
|
|
2723
|
+
switch (fmt) {
|
|
2724
|
+
case 'currency': return '$' + Number(val).toLocaleString();
|
|
2725
|
+
case 'percent': return val + '%';
|
|
2726
|
+
case 'number': return Number(val).toLocaleString();
|
|
2727
|
+
default: return '' + val;
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
var classes = [
|
|
2732
|
+
'bw_stat_card',
|
|
2733
|
+
variantClass(variant),
|
|
2734
|
+
className
|
|
2735
|
+
].filter(Boolean).join(' ').trim();
|
|
2736
|
+
|
|
2737
|
+
var children = [];
|
|
2738
|
+
|
|
2739
|
+
if (icon) {
|
|
2740
|
+
children.push({
|
|
2741
|
+
t: 'div',
|
|
2742
|
+
a: { class: 'bw_stat_icon' },
|
|
2743
|
+
c: icon
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
children.push({
|
|
2748
|
+
t: 'div',
|
|
2749
|
+
a: { class: 'bw_stat_value' },
|
|
2750
|
+
c: formatValue(value, format)
|
|
2751
|
+
});
|
|
2752
|
+
|
|
2753
|
+
if (label) {
|
|
2754
|
+
children.push({
|
|
2755
|
+
t: 'div',
|
|
2756
|
+
a: { class: 'bw_stat_label' },
|
|
2757
|
+
c: label
|
|
2758
|
+
});
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
if (change !== undefined && change !== null) {
|
|
2762
|
+
children.push({
|
|
2763
|
+
t: 'div',
|
|
2764
|
+
a: {
|
|
2765
|
+
class: 'bw_stat_change ' + (change >= 0 ? 'bw_stat_change_up' : 'bw_stat_change_down')
|
|
2766
|
+
},
|
|
2767
|
+
c: (change >= 0 ? '\u2191 +' : '\u2193 ') + change + '%'
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
return {
|
|
2772
|
+
t: 'div',
|
|
2773
|
+
a: { class: classes, style: style },
|
|
2774
|
+
c: children,
|
|
2775
|
+
o: { type: 'stat-card' }
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
// =========================================================================
|
|
2780
|
+
// Phase 5: Overlays & Popovers
|
|
2781
|
+
// =========================================================================
|
|
2782
|
+
|
|
2783
|
+
/**
|
|
2784
|
+
* Create a tooltip wrapper around trigger content
|
|
2785
|
+
*
|
|
2786
|
+
* Wraps the trigger element in a container that shows tooltip text
|
|
2787
|
+
* on hover and focus. Pure CSS-driven show/hide with JS lifecycle
|
|
2788
|
+
* for event binding.
|
|
2789
|
+
*
|
|
2790
|
+
* @param {Object} [props] - Tooltip configuration
|
|
2791
|
+
* @param {string|Object|Array} [props.content] - Trigger content (what the user hovers/focuses)
|
|
2792
|
+
* @param {string} [props.text=""] - Tooltip text to display
|
|
2793
|
+
* @param {string} [props.placement="top"] - Tooltip placement ("top", "bottom", "left", "right")
|
|
2794
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
2795
|
+
* @returns {Object} TACO object representing a tooltip wrapper
|
|
2796
|
+
* @category Component Builders
|
|
2797
|
+
* @example
|
|
2798
|
+
* const tip = makeTooltip({
|
|
2799
|
+
* content: makeButton({ text: 'Hover me' }),
|
|
2800
|
+
* text: 'This is a tooltip!',
|
|
2801
|
+
* placement: 'top'
|
|
2802
|
+
* });
|
|
2803
|
+
*/
|
|
2804
|
+
export function makeTooltip(props = {}) {
|
|
2805
|
+
var {
|
|
2806
|
+
content,
|
|
2807
|
+
text = '',
|
|
2808
|
+
placement = 'top',
|
|
2809
|
+
className = ''
|
|
2810
|
+
} = props;
|
|
2811
|
+
|
|
2812
|
+
return {
|
|
2813
|
+
t: 'span',
|
|
2814
|
+
a: { class: ('bw_tooltip_wrapper ' + className).trim() },
|
|
2815
|
+
c: [
|
|
2816
|
+
content,
|
|
2817
|
+
{
|
|
2818
|
+
t: 'span',
|
|
2819
|
+
a: {
|
|
2820
|
+
class: 'bw_tooltip bw_tooltip_' + placement,
|
|
2821
|
+
role: 'tooltip'
|
|
2822
|
+
},
|
|
2823
|
+
c: text
|
|
2824
|
+
}
|
|
2825
|
+
],
|
|
2826
|
+
o: {
|
|
2827
|
+
type: 'tooltip',
|
|
2828
|
+
mounted: function(el) {
|
|
2829
|
+
var tip = el.querySelector('.bw_tooltip');
|
|
2830
|
+
el.addEventListener('mouseenter', function() {
|
|
2831
|
+
tip.classList.add('bw_tooltip_show');
|
|
2832
|
+
});
|
|
2833
|
+
el.addEventListener('mouseleave', function() {
|
|
2834
|
+
tip.classList.remove('bw_tooltip_show');
|
|
2835
|
+
});
|
|
2836
|
+
el.addEventListener('focusin', function() {
|
|
2837
|
+
tip.classList.add('bw_tooltip_show');
|
|
2838
|
+
});
|
|
2839
|
+
el.addEventListener('focusout', function() {
|
|
2840
|
+
tip.classList.remove('bw_tooltip_show');
|
|
2841
|
+
});
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
/**
|
|
2848
|
+
* Create a popover wrapper around trigger content
|
|
2849
|
+
*
|
|
2850
|
+
* Like a tooltip but richer — supports title + body content and is
|
|
2851
|
+
* triggered by click rather than hover. Dismisses on click outside.
|
|
2852
|
+
*
|
|
2853
|
+
* @param {Object} [props] - Popover configuration
|
|
2854
|
+
* @param {string|Object|Array} [props.trigger] - Trigger content (what the user clicks)
|
|
2855
|
+
* @param {string} [props.title] - Popover header title
|
|
2856
|
+
* @param {string|Object|Array} [props.content] - Popover body content
|
|
2857
|
+
* @param {string} [props.placement="top"] - Placement ("top", "bottom", "left", "right")
|
|
2858
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
2859
|
+
* @returns {Object} TACO object representing a popover wrapper
|
|
2860
|
+
* @category Component Builders
|
|
2861
|
+
* @example
|
|
2862
|
+
* const pop = makePopover({
|
|
2863
|
+
* trigger: makeButton({ text: 'Click me' }),
|
|
2864
|
+
* title: 'Popover Title',
|
|
2865
|
+
* content: 'Some helpful information here.',
|
|
2866
|
+
* placement: 'bottom'
|
|
2867
|
+
* });
|
|
2868
|
+
*/
|
|
2869
|
+
export function makePopover(props = {}) {
|
|
2870
|
+
var {
|
|
2871
|
+
trigger,
|
|
2872
|
+
title,
|
|
2873
|
+
content,
|
|
2874
|
+
placement = 'top',
|
|
2875
|
+
className = ''
|
|
2876
|
+
} = props;
|
|
2877
|
+
|
|
2878
|
+
var popoverContent = [
|
|
2879
|
+
title && {
|
|
2880
|
+
t: 'div',
|
|
2881
|
+
a: { class: 'bw_popover_header' },
|
|
2882
|
+
c: title
|
|
2883
|
+
},
|
|
2884
|
+
content && {
|
|
2885
|
+
t: 'div',
|
|
2886
|
+
a: { class: 'bw_popover_body' },
|
|
2887
|
+
c: content
|
|
2888
|
+
}
|
|
2889
|
+
].filter(Boolean);
|
|
2890
|
+
|
|
2891
|
+
return {
|
|
2892
|
+
t: 'span',
|
|
2893
|
+
a: { class: ('bw_popover_wrapper ' + className).trim() },
|
|
2894
|
+
c: [
|
|
2895
|
+
{
|
|
2896
|
+
t: 'span',
|
|
2897
|
+
a: {
|
|
2898
|
+
class: 'bw_popover_trigger',
|
|
2899
|
+
onclick: function(e) {
|
|
2900
|
+
var wrapper = e.target.closest('.bw_popover_wrapper');
|
|
2901
|
+
var pop = wrapper.querySelector('.bw_popover');
|
|
2902
|
+
pop.classList.toggle('bw_popover_show');
|
|
2903
|
+
}
|
|
2904
|
+
},
|
|
2905
|
+
c: trigger
|
|
2906
|
+
},
|
|
2907
|
+
{
|
|
2908
|
+
t: 'div',
|
|
2909
|
+
a: {
|
|
2910
|
+
class: 'bw_popover bw_popover_' + placement
|
|
2911
|
+
},
|
|
2912
|
+
c: popoverContent
|
|
2913
|
+
}
|
|
2914
|
+
],
|
|
2915
|
+
o: {
|
|
2916
|
+
type: 'popover',
|
|
2917
|
+
mounted: function(el) {
|
|
2918
|
+
// Click outside to close
|
|
2919
|
+
var outsideHandler = function(e) {
|
|
2920
|
+
if (!el.contains(e.target)) {
|
|
2921
|
+
var pop = el.querySelector('.bw_popover');
|
|
2922
|
+
if (pop) pop.classList.remove('bw_popover_show');
|
|
2923
|
+
}
|
|
2924
|
+
};
|
|
2925
|
+
document.addEventListener('click', outsideHandler);
|
|
2926
|
+
el._bw_outsideHandler = outsideHandler;
|
|
2927
|
+
},
|
|
2928
|
+
unmount: function(el) {
|
|
2929
|
+
if (el._bw_outsideHandler) {
|
|
2930
|
+
document.removeEventListener('click', el._bw_outsideHandler);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
// =========================================================================
|
|
2938
|
+
// Phase 6: Form Enhancements & Layout
|
|
2939
|
+
// =========================================================================
|
|
2940
|
+
|
|
2941
|
+
/**
|
|
2942
|
+
* Create a search input with clear button
|
|
2943
|
+
*
|
|
2944
|
+
* Wraps a text input with a clear (×) button that appears when
|
|
2945
|
+
* the field has content. Calls onSearch on Enter key.
|
|
2946
|
+
*
|
|
2947
|
+
* @param {Object} [props] - Search input configuration
|
|
2948
|
+
* @param {string} [props.placeholder="Search..."] - Placeholder text
|
|
2949
|
+
* @param {string} [props.value] - Initial value
|
|
2950
|
+
* @param {Function} [props.onSearch] - Callback when Enter is pressed, receives value
|
|
2951
|
+
* @param {Function} [props.onInput] - Callback on each keystroke, receives value
|
|
2952
|
+
* @param {string} [props.id] - Element ID
|
|
2953
|
+
* @param {string} [props.name] - Input name attribute
|
|
2954
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
2955
|
+
* @returns {Object} TACO object representing a search input
|
|
2956
|
+
* @category Component Builders
|
|
2957
|
+
* @example
|
|
2958
|
+
* const search = makeSearchInput({
|
|
2959
|
+
* placeholder: 'Search users...',
|
|
2960
|
+
* onSearch: (val) => filterUsers(val)
|
|
2961
|
+
* });
|
|
2962
|
+
*/
|
|
2963
|
+
export function makeSearchInput(props = {}) {
|
|
2964
|
+
if (typeof props === 'string') props = { placeholder: props };
|
|
2965
|
+
var {
|
|
2966
|
+
placeholder = 'Search...',
|
|
2967
|
+
value,
|
|
2968
|
+
onSearch,
|
|
2969
|
+
onInput,
|
|
2970
|
+
id,
|
|
2971
|
+
name,
|
|
2972
|
+
className = ''
|
|
2973
|
+
} = props;
|
|
2974
|
+
|
|
2975
|
+
return {
|
|
2976
|
+
t: 'div',
|
|
2977
|
+
a: { class: ('bw_search_input ' + className).trim() },
|
|
2978
|
+
c: [
|
|
2979
|
+
{
|
|
2980
|
+
t: 'input',
|
|
2981
|
+
a: {
|
|
2982
|
+
type: 'search',
|
|
2983
|
+
class: 'bw_form_control bw_search_field',
|
|
2984
|
+
placeholder: placeholder,
|
|
2985
|
+
value: value,
|
|
2986
|
+
id: id,
|
|
2987
|
+
name: name,
|
|
2988
|
+
onkeydown: function(e) {
|
|
2989
|
+
if (e.key === 'Enter' && onSearch) {
|
|
2990
|
+
e.preventDefault();
|
|
2991
|
+
onSearch(e.target.value);
|
|
2992
|
+
}
|
|
2993
|
+
},
|
|
2994
|
+
oninput: function(e) {
|
|
2995
|
+
var wrapper = e.target.closest('.bw_search_input');
|
|
2996
|
+
var clearBtn = wrapper.querySelector('.bw_search_clear');
|
|
2997
|
+
if (clearBtn) {
|
|
2998
|
+
clearBtn.style.display = e.target.value ? 'flex' : 'none';
|
|
2999
|
+
}
|
|
3000
|
+
if (onInput) onInput(e.target.value);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
},
|
|
3004
|
+
{
|
|
3005
|
+
t: 'button',
|
|
3006
|
+
a: {
|
|
3007
|
+
type: 'button',
|
|
3008
|
+
class: 'bw_search_clear',
|
|
3009
|
+
'aria-label': 'Clear search',
|
|
3010
|
+
style: value ? undefined : 'display: none',
|
|
3011
|
+
onclick: function(e) {
|
|
3012
|
+
var wrapper = e.target.closest('.bw_search_input');
|
|
3013
|
+
var input = wrapper.querySelector('.bw_search_field');
|
|
3014
|
+
input.value = '';
|
|
3015
|
+
e.target.style.display = 'none';
|
|
3016
|
+
input.focus();
|
|
3017
|
+
if (onInput) onInput('');
|
|
3018
|
+
if (onSearch) onSearch('');
|
|
3019
|
+
}
|
|
3020
|
+
},
|
|
3021
|
+
c: '\u00D7'
|
|
3022
|
+
}
|
|
3023
|
+
],
|
|
3024
|
+
o: { type: 'search-input' }
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
/**
|
|
3029
|
+
* Create a styled range slider input
|
|
3030
|
+
*
|
|
3031
|
+
* @param {Object} [props] - Range configuration
|
|
3032
|
+
* @param {number} [props.min=0] - Minimum value
|
|
3033
|
+
* @param {number} [props.max=100] - Maximum value
|
|
3034
|
+
* @param {number} [props.step=1] - Step increment
|
|
3035
|
+
* @param {number} [props.value=50] - Current value
|
|
3036
|
+
* @param {string} [props.label] - Label text
|
|
3037
|
+
* @param {boolean} [props.showValue=false] - Show current value display
|
|
3038
|
+
* @param {string} [props.id] - Element ID
|
|
3039
|
+
* @param {string} [props.name] - Input name attribute
|
|
3040
|
+
* @param {boolean} [props.disabled=false] - Whether the slider is disabled
|
|
3041
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
3042
|
+
* @returns {Object} TACO object representing a range input
|
|
3043
|
+
* @category Component Builders
|
|
3044
|
+
* @example
|
|
3045
|
+
* const slider = makeRange({
|
|
3046
|
+
* min: 0, max: 100, value: 50,
|
|
3047
|
+
* label: 'Volume',
|
|
3048
|
+
* showValue: true,
|
|
3049
|
+
* oninput: (e) => setVolume(e.target.value)
|
|
3050
|
+
* });
|
|
3051
|
+
*/
|
|
3052
|
+
export function makeRange(props = {}) {
|
|
3053
|
+
var {
|
|
3054
|
+
min = 0,
|
|
3055
|
+
max = 100,
|
|
3056
|
+
step = 1,
|
|
3057
|
+
value = 50,
|
|
3058
|
+
label,
|
|
3059
|
+
showValue = false,
|
|
3060
|
+
id,
|
|
3061
|
+
name,
|
|
3062
|
+
disabled = false,
|
|
3063
|
+
className = '',
|
|
3064
|
+
...eventHandlers
|
|
3065
|
+
} = props;
|
|
3066
|
+
|
|
3067
|
+
var children = [];
|
|
3068
|
+
|
|
3069
|
+
if (label || showValue) {
|
|
3070
|
+
var labelContent = [];
|
|
3071
|
+
if (label) {
|
|
3072
|
+
labelContent.push({
|
|
3073
|
+
t: 'span',
|
|
3074
|
+
c: label
|
|
3075
|
+
});
|
|
3076
|
+
}
|
|
3077
|
+
if (showValue) {
|
|
3078
|
+
labelContent.push({
|
|
3079
|
+
t: 'span',
|
|
3080
|
+
a: { class: 'bw_range_value' },
|
|
3081
|
+
c: '' + value
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
3084
|
+
children.push({
|
|
3085
|
+
t: 'div',
|
|
3086
|
+
a: { class: 'bw_range_label' },
|
|
3087
|
+
c: labelContent
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
// Wrap oninput to update value display
|
|
3092
|
+
var userOnInput = eventHandlers.oninput;
|
|
3093
|
+
if (showValue) {
|
|
3094
|
+
eventHandlers.oninput = function(e) {
|
|
3095
|
+
var wrapper = e.target.closest('.bw_range_wrapper');
|
|
3096
|
+
var valDisplay = wrapper.querySelector('.bw_range_value');
|
|
3097
|
+
if (valDisplay) valDisplay.textContent = e.target.value;
|
|
3098
|
+
if (userOnInput) userOnInput(e);
|
|
3099
|
+
};
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
children.push({
|
|
3103
|
+
t: 'input',
|
|
3104
|
+
a: {
|
|
3105
|
+
type: 'range',
|
|
3106
|
+
class: 'bw_range',
|
|
3107
|
+
min: min,
|
|
3108
|
+
max: max,
|
|
3109
|
+
step: step,
|
|
3110
|
+
value: value,
|
|
3111
|
+
id: id,
|
|
3112
|
+
name: name,
|
|
3113
|
+
disabled: disabled,
|
|
3114
|
+
...eventHandlers
|
|
3115
|
+
}
|
|
3116
|
+
});
|
|
3117
|
+
|
|
3118
|
+
return {
|
|
3119
|
+
t: 'div',
|
|
3120
|
+
a: { class: ('bw_range_wrapper ' + className).trim() },
|
|
3121
|
+
c: children,
|
|
3122
|
+
o: { type: 'range' }
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
/**
|
|
3127
|
+
* Create a media object layout (image + text side-by-side)
|
|
3128
|
+
*
|
|
3129
|
+
* Classic media object pattern: image/icon on one side, text content
|
|
3130
|
+
* on the other, using flexbox. Supports reversed layout.
|
|
3131
|
+
*
|
|
3132
|
+
* @param {Object} [props] - Media object configuration
|
|
3133
|
+
* @param {string} [props.src] - Image source URL
|
|
3134
|
+
* @param {string} [props.alt=""] - Image alt text
|
|
3135
|
+
* @param {string} [props.title] - Title text
|
|
3136
|
+
* @param {string|Object|Array} [props.content] - Body content
|
|
3137
|
+
* @param {boolean} [props.reverse=false] - Put image on the right
|
|
3138
|
+
* @param {string} [props.imageSize="3rem"] - Image width/height
|
|
3139
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
3140
|
+
* @returns {Object} TACO object representing a media object
|
|
3141
|
+
* @category Component Builders
|
|
3142
|
+
* @example
|
|
3143
|
+
* const media = makeMediaObject({
|
|
3144
|
+
* src: '/avatar.jpg',
|
|
3145
|
+
* title: 'Jane Doe',
|
|
3146
|
+
* content: 'Posted a comment 5 minutes ago.'
|
|
3147
|
+
* });
|
|
3148
|
+
*/
|
|
3149
|
+
export function makeMediaObject(props = {}) {
|
|
3150
|
+
var {
|
|
3151
|
+
src,
|
|
3152
|
+
alt = '',
|
|
3153
|
+
title,
|
|
3154
|
+
content,
|
|
3155
|
+
reverse = false,
|
|
3156
|
+
imageSize = '3rem',
|
|
3157
|
+
className = ''
|
|
3158
|
+
} = props;
|
|
3159
|
+
|
|
3160
|
+
var imgEl = src ? {
|
|
3161
|
+
t: 'img',
|
|
3162
|
+
a: {
|
|
3163
|
+
class: 'bw_media_img',
|
|
3164
|
+
src: src,
|
|
3165
|
+
alt: alt,
|
|
3166
|
+
style: 'width:' + imageSize + ';height:' + imageSize
|
|
3167
|
+
}
|
|
3168
|
+
} : null;
|
|
3169
|
+
|
|
3170
|
+
var bodyEl = {
|
|
3171
|
+
t: 'div',
|
|
3172
|
+
a: { class: 'bw_media_body' },
|
|
3173
|
+
c: [
|
|
3174
|
+
title && { t: 'h5', a: { class: 'bw_media_title' }, c: title },
|
|
3175
|
+
content
|
|
3176
|
+
].filter(Boolean)
|
|
3177
|
+
};
|
|
3178
|
+
|
|
3179
|
+
return {
|
|
3180
|
+
t: 'div',
|
|
3181
|
+
a: { class: ('bw_media ' + (reverse ? 'bw_media_reverse ' : '') + className).trim() },
|
|
3182
|
+
c: reverse
|
|
3183
|
+
? [bodyEl, imgEl].filter(Boolean)
|
|
3184
|
+
: [imgEl, bodyEl].filter(Boolean),
|
|
3185
|
+
o: { type: 'media-object' }
|
|
3186
|
+
};
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3189
|
+
/**
|
|
3190
|
+
* Create a file upload zone with drag-and-drop support
|
|
3191
|
+
*
|
|
3192
|
+
* Styled drop zone with file input. Supports drag-and-drop visuals
|
|
3193
|
+
* and multiple file selection.
|
|
3194
|
+
*
|
|
3195
|
+
* @param {Object} [props] - File upload configuration
|
|
3196
|
+
* @param {string} [props.accept] - Accepted file types (e.g. "image/*", ".pdf,.doc")
|
|
3197
|
+
* @param {boolean} [props.multiple=false] - Allow multiple file selection
|
|
3198
|
+
* @param {Function} [props.onFiles] - Callback when files are selected, receives FileList
|
|
3199
|
+
* @param {string} [props.text="Drop files here or click to browse"] - Zone label text
|
|
3200
|
+
* @param {string} [props.id] - Element ID
|
|
3201
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
3202
|
+
* @returns {Object} TACO object representing a file upload zone
|
|
3203
|
+
* @category Component Builders
|
|
3204
|
+
* @example
|
|
3205
|
+
* const upload = makeFileUpload({
|
|
3206
|
+
* accept: 'image/*',
|
|
3207
|
+
* multiple: true,
|
|
3208
|
+
* onFiles: (files) => uploadFiles(files)
|
|
3209
|
+
* });
|
|
3210
|
+
*/
|
|
3211
|
+
export function makeFileUpload(props = {}) {
|
|
3212
|
+
var {
|
|
3213
|
+
accept,
|
|
3214
|
+
multiple = false,
|
|
3215
|
+
onFiles,
|
|
3216
|
+
text = 'Drop files here or click to browse',
|
|
3217
|
+
id,
|
|
3218
|
+
className = ''
|
|
3219
|
+
} = props;
|
|
3220
|
+
|
|
3221
|
+
return {
|
|
3222
|
+
t: 'div',
|
|
3223
|
+
a: {
|
|
3224
|
+
class: ('bw_file_upload ' + className).trim(),
|
|
3225
|
+
tabindex: '0',
|
|
3226
|
+
role: 'button',
|
|
3227
|
+
'aria-label': text
|
|
3228
|
+
},
|
|
3229
|
+
c: [
|
|
3230
|
+
{ t: 'div', a: { class: 'bw_file_upload_icon' }, c: '\uD83D\uDCC1' },
|
|
3231
|
+
{ t: 'div', a: { class: 'bw_file_upload_text' }, c: text },
|
|
3232
|
+
{
|
|
3233
|
+
t: 'input',
|
|
3234
|
+
a: {
|
|
3235
|
+
type: 'file',
|
|
3236
|
+
class: 'bw_file_upload_input',
|
|
3237
|
+
accept: accept,
|
|
3238
|
+
multiple: multiple,
|
|
3239
|
+
id: id,
|
|
3240
|
+
onchange: function(e) {
|
|
3241
|
+
if (onFiles && e.target.files.length) onFiles(e.target.files);
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
],
|
|
3246
|
+
o: {
|
|
3247
|
+
type: 'file-upload',
|
|
3248
|
+
mounted: function(el) {
|
|
3249
|
+
var input = el.querySelector('.bw_file_upload_input');
|
|
3250
|
+
|
|
3251
|
+
// Click zone to trigger file input
|
|
3252
|
+
el.addEventListener('click', function(e) {
|
|
3253
|
+
if (e.target !== input) input.click();
|
|
3254
|
+
});
|
|
3255
|
+
|
|
3256
|
+
// Keyboard activation
|
|
3257
|
+
el.addEventListener('keydown', function(e) {
|
|
3258
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
3259
|
+
e.preventDefault();
|
|
3260
|
+
input.click();
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3263
|
+
|
|
3264
|
+
// Drag-and-drop visuals
|
|
3265
|
+
el.addEventListener('dragover', function(e) {
|
|
3266
|
+
e.preventDefault();
|
|
3267
|
+
el.classList.add('bw_file_upload_active');
|
|
3268
|
+
});
|
|
3269
|
+
el.addEventListener('dragleave', function() {
|
|
3270
|
+
el.classList.remove('bw_file_upload_active');
|
|
3271
|
+
});
|
|
3272
|
+
el.addEventListener('drop', function(e) {
|
|
3273
|
+
e.preventDefault();
|
|
3274
|
+
el.classList.remove('bw_file_upload_active');
|
|
3275
|
+
if (onFiles && e.dataTransfer.files.length) onFiles(e.dataTransfer.files);
|
|
3276
|
+
});
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
// =========================================================================
|
|
3283
|
+
// Phase 7: Data Display & Workflow
|
|
3284
|
+
// =========================================================================
|
|
3285
|
+
|
|
3286
|
+
/**
|
|
3287
|
+
* Create a vertical timeline for chronological event display
|
|
3288
|
+
*
|
|
3289
|
+
* Renders events as a vertical line with markers and content cards.
|
|
3290
|
+
* Each item can have a colored variant marker.
|
|
3291
|
+
*
|
|
3292
|
+
* @param {Object} [props] - Timeline configuration
|
|
3293
|
+
* @param {Array<Object>} [props.items=[]] - Timeline events
|
|
3294
|
+
* @param {string} [props.items[].title] - Event title
|
|
3295
|
+
* @param {string|Object|Array} [props.items[].content] - Event description content
|
|
3296
|
+
* @param {string} [props.items[].date] - Date or time label
|
|
3297
|
+
* @param {string} [props.items[].variant="primary"] - Marker color variant
|
|
3298
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
3299
|
+
* @returns {Object} TACO object representing a timeline
|
|
3300
|
+
* @category Component Builders
|
|
3301
|
+
* @example
|
|
3302
|
+
* const timeline = makeTimeline({
|
|
3303
|
+
* items: [
|
|
3304
|
+
* { title: 'Project Started', date: 'Jan 2026', variant: 'primary' },
|
|
3305
|
+
* { title: 'Beta Release', date: 'Mar 2026', content: 'v2.0 beta shipped' },
|
|
3306
|
+
* { title: 'Stable Release', date: 'Jun 2026', variant: 'success' }
|
|
3307
|
+
* ]
|
|
3308
|
+
* });
|
|
3309
|
+
*/
|
|
3310
|
+
export function makeTimeline(props = {}) {
|
|
3311
|
+
var {
|
|
3312
|
+
items = [],
|
|
3313
|
+
className = ''
|
|
3314
|
+
} = props;
|
|
3315
|
+
|
|
3316
|
+
return {
|
|
3317
|
+
t: 'div',
|
|
3318
|
+
a: { class: ('bw_timeline ' + className).trim() },
|
|
3319
|
+
c: items.map(function(item) {
|
|
3320
|
+
return {
|
|
3321
|
+
t: 'div',
|
|
3322
|
+
a: { class: 'bw_timeline_item' },
|
|
3323
|
+
c: [
|
|
3324
|
+
{
|
|
3325
|
+
t: 'div',
|
|
3326
|
+
a: { class: 'bw_timeline_marker ' + variantClass(item.variant || 'primary') }
|
|
3327
|
+
},
|
|
3328
|
+
{
|
|
3329
|
+
t: 'div',
|
|
3330
|
+
a: { class: 'bw_timeline_content' },
|
|
3331
|
+
c: [
|
|
3332
|
+
item.date && {
|
|
3333
|
+
t: 'div',
|
|
3334
|
+
a: { class: 'bw_timeline_date' },
|
|
3335
|
+
c: item.date
|
|
3336
|
+
},
|
|
3337
|
+
item.title && {
|
|
3338
|
+
t: 'h5',
|
|
3339
|
+
a: { class: 'bw_timeline_title' },
|
|
3340
|
+
c: item.title
|
|
3341
|
+
},
|
|
3342
|
+
item.content && (typeof item.content === 'string'
|
|
3343
|
+
? { t: 'p', a: { class: 'bw_timeline_text' }, c: item.content }
|
|
3344
|
+
: item.content)
|
|
3345
|
+
].filter(Boolean)
|
|
3346
|
+
}
|
|
3347
|
+
]
|
|
3348
|
+
};
|
|
3349
|
+
}),
|
|
3350
|
+
o: { type: 'timeline' }
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
/**
|
|
3355
|
+
* Create a multi-step wizard/progress indicator
|
|
3356
|
+
*
|
|
3357
|
+
* Displays numbered steps with active and completed states.
|
|
3358
|
+
* Steps before currentStep are marked completed, the currentStep
|
|
3359
|
+
* is active, and subsequent steps are pending.
|
|
3360
|
+
*
|
|
3361
|
+
* @param {Object} [props] - Stepper configuration
|
|
3362
|
+
* @param {Array<Object>} [props.steps=[]] - Step definitions
|
|
3363
|
+
* @param {string} [props.steps[].label] - Step label text
|
|
3364
|
+
* @param {string} [props.steps[].description] - Optional step description
|
|
3365
|
+
* @param {number} [props.currentStep=0] - Zero-based index of the active step
|
|
3366
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
3367
|
+
* @returns {Object} TACO object representing a stepper
|
|
3368
|
+
* @category Component Builders
|
|
3369
|
+
* @example
|
|
3370
|
+
* const stepper = makeStepper({
|
|
3371
|
+
* currentStep: 1,
|
|
3372
|
+
* steps: [
|
|
3373
|
+
* { label: 'Account', description: 'Create account' },
|
|
3374
|
+
* { label: 'Profile', description: 'Set up profile' },
|
|
3375
|
+
* { label: 'Confirm', description: 'Review & submit' }
|
|
3376
|
+
* ]
|
|
3377
|
+
* });
|
|
3378
|
+
*/
|
|
3379
|
+
export function makeStepper(props = {}) {
|
|
3380
|
+
var {
|
|
3381
|
+
steps = [],
|
|
3382
|
+
currentStep = 0,
|
|
3383
|
+
className = ''
|
|
3384
|
+
} = props;
|
|
3385
|
+
|
|
3386
|
+
return {
|
|
3387
|
+
t: 'div',
|
|
3388
|
+
a: { class: ('bw_stepper ' + className).trim(), role: 'list' },
|
|
3389
|
+
c: steps.map(function(step, index) {
|
|
3390
|
+
var state = index < currentStep ? 'completed' : index === currentStep ? 'active' : 'pending';
|
|
3391
|
+
return {
|
|
3392
|
+
t: 'div',
|
|
3393
|
+
a: {
|
|
3394
|
+
class: 'bw_step bw_step_' + state,
|
|
3395
|
+
role: 'listitem',
|
|
3396
|
+
'aria-current': state === 'active' ? 'step' : undefined
|
|
3397
|
+
},
|
|
3398
|
+
c: [
|
|
3399
|
+
{
|
|
3400
|
+
t: 'div',
|
|
3401
|
+
a: { class: 'bw_step_indicator' },
|
|
3402
|
+
c: state === 'completed' ? '\u2713' : '' + (index + 1)
|
|
3403
|
+
},
|
|
3404
|
+
{
|
|
3405
|
+
t: 'div',
|
|
3406
|
+
a: { class: 'bw_step_body' },
|
|
3407
|
+
c: [
|
|
3408
|
+
{ t: 'div', a: { class: 'bw_step_label' }, c: step.label },
|
|
3409
|
+
step.description && { t: 'div', a: { class: 'bw_step_description' }, c: step.description }
|
|
3410
|
+
].filter(Boolean)
|
|
3411
|
+
}
|
|
3412
|
+
]
|
|
3413
|
+
};
|
|
3414
|
+
}),
|
|
3415
|
+
o: { type: 'stepper' }
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3419
|
+
/**
|
|
3420
|
+
* Create a chip/tag input for managing a list of items
|
|
3421
|
+
*
|
|
3422
|
+
* Displays existing chips with remove buttons and an input field
|
|
3423
|
+
* for adding new ones. Chips are added on Enter and removed on
|
|
3424
|
+
* clicking the × button.
|
|
3425
|
+
*
|
|
3426
|
+
* @param {Object} [props] - Chip input configuration
|
|
3427
|
+
* @param {Array<string>} [props.chips=[]] - Initial chip values
|
|
3428
|
+
* @param {string} [props.placeholder="Add..."] - Input placeholder text
|
|
3429
|
+
* @param {Function} [props.onAdd] - Callback when a chip is added, receives value
|
|
3430
|
+
* @param {Function} [props.onRemove] - Callback when a chip is removed, receives value
|
|
3431
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
3432
|
+
* @returns {Object} TACO object representing a chip input
|
|
3433
|
+
* @category Component Builders
|
|
3434
|
+
* @example
|
|
3435
|
+
* const tags = makeChipInput({
|
|
3436
|
+
* chips: ['JavaScript', 'CSS'],
|
|
3437
|
+
* placeholder: 'Add tag...',
|
|
3438
|
+
* onAdd: (val) => addTag(val),
|
|
3439
|
+
* onRemove: (val) => removeTag(val)
|
|
3440
|
+
* });
|
|
3441
|
+
*/
|
|
3442
|
+
export function makeChipInput(props = {}) {
|
|
3443
|
+
var {
|
|
3444
|
+
chips = [],
|
|
3445
|
+
placeholder = 'Add...',
|
|
3446
|
+
onAdd,
|
|
3447
|
+
onRemove,
|
|
3448
|
+
className = ''
|
|
3449
|
+
} = props;
|
|
3450
|
+
|
|
3451
|
+
function makeChipEl(text) {
|
|
3452
|
+
return {
|
|
3453
|
+
t: 'span',
|
|
3454
|
+
a: { class: 'bw_chip', 'data-chip-value': text },
|
|
3455
|
+
c: [
|
|
3456
|
+
text,
|
|
3457
|
+
{
|
|
3458
|
+
t: 'button',
|
|
3459
|
+
a: {
|
|
3460
|
+
type: 'button',
|
|
3461
|
+
class: 'bw_chip_remove',
|
|
3462
|
+
'aria-label': 'Remove ' + text,
|
|
3463
|
+
onclick: function(e) {
|
|
3464
|
+
var chip = e.target.closest('.bw_chip');
|
|
3465
|
+
var val = chip.getAttribute('data-chip-value');
|
|
3466
|
+
chip.parentNode.removeChild(chip);
|
|
3467
|
+
if (onRemove) onRemove(val);
|
|
3468
|
+
}
|
|
3469
|
+
},
|
|
3470
|
+
c: '\u00D7'
|
|
3471
|
+
}
|
|
3472
|
+
]
|
|
3473
|
+
};
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
return {
|
|
3477
|
+
t: 'div',
|
|
3478
|
+
a: { class: ('bw_chip_input ' + className).trim() },
|
|
3479
|
+
c: [
|
|
3480
|
+
...chips.map(makeChipEl),
|
|
3481
|
+
{
|
|
3482
|
+
t: 'input',
|
|
3483
|
+
a: {
|
|
3484
|
+
type: 'text',
|
|
3485
|
+
class: 'bw_chip_field',
|
|
3486
|
+
placeholder: placeholder,
|
|
3487
|
+
onkeydown: function(e) {
|
|
3488
|
+
if (e.key === 'Enter' && e.target.value.trim()) {
|
|
3489
|
+
e.preventDefault();
|
|
3490
|
+
var val = e.target.value.trim();
|
|
3491
|
+
var wrapper = e.target.closest('.bw_chip_input');
|
|
3492
|
+
// Insert chip before the input
|
|
3493
|
+
var chipEl = document.createElement('span');
|
|
3494
|
+
chipEl.className = 'bw_chip';
|
|
3495
|
+
chipEl.setAttribute('data-chip-value', val);
|
|
3496
|
+
chipEl.innerHTML = '';
|
|
3497
|
+
chipEl.textContent = val;
|
|
3498
|
+
var removeBtn = document.createElement('button');
|
|
3499
|
+
removeBtn.type = 'button';
|
|
3500
|
+
removeBtn.className = 'bw_chip_remove';
|
|
3501
|
+
removeBtn.setAttribute('aria-label', 'Remove ' + val);
|
|
3502
|
+
removeBtn.textContent = '\u00D7';
|
|
3503
|
+
removeBtn.onclick = function() {
|
|
3504
|
+
chipEl.parentNode.removeChild(chipEl);
|
|
3505
|
+
if (onRemove) onRemove(val);
|
|
3506
|
+
};
|
|
3507
|
+
chipEl.appendChild(removeBtn);
|
|
3508
|
+
wrapper.insertBefore(chipEl, e.target);
|
|
3509
|
+
e.target.value = '';
|
|
3510
|
+
if (onAdd) onAdd(val);
|
|
3511
|
+
}
|
|
3512
|
+
// Backspace on empty input removes last chip
|
|
3513
|
+
if (e.key === 'Backspace' && !e.target.value) {
|
|
3514
|
+
var wrapper = e.target.closest('.bw_chip_input');
|
|
3515
|
+
var chipEls = wrapper.querySelectorAll('.bw_chip');
|
|
3516
|
+
if (chipEls.length) {
|
|
3517
|
+
var last = chipEls[chipEls.length - 1];
|
|
3518
|
+
var removedVal = last.getAttribute('data-chip-value');
|
|
3519
|
+
last.parentNode.removeChild(last);
|
|
3520
|
+
if (onRemove) onRemove(removedVal);
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
],
|
|
3527
|
+
o: { type: 'chip-input' }
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3531
|
+
// componentHandles registry removed in v2.0.15.
|
|
3532
|
+
// See dev/dead-code-elimination-v2.0.15.md for recovery.
|
|
3533
|
+
|
|
3534
|
+
// =========================================================================
|
|
3535
|
+
// BCCL Component Registry
|
|
3536
|
+
//
|
|
3537
|
+
// Single registry mapping type names to their factory functions.
|
|
3538
|
+
// Enables bw.make('card', props) dispatch and introspection via
|
|
3539
|
+
// Object.keys(BCCL).
|
|
3540
|
+
// =========================================================================
|
|
3541
|
+
|
|
3542
|
+
/**
|
|
3543
|
+
* BCCL component registry — maps component type names to factory functions.
|
|
3544
|
+
* Each entry's `make` function is the corresponding exported makeXxx().
|
|
3545
|
+
*
|
|
3546
|
+
* @type {Object.<string, {make: Function}>}
|
|
3547
|
+
*/
|
|
3548
|
+
export var BCCL = {
|
|
3549
|
+
card: { make: makeCard },
|
|
3550
|
+
button: { make: makeButton },
|
|
3551
|
+
container: { make: makeContainer },
|
|
3552
|
+
row: { make: makeRow },
|
|
3553
|
+
col: { make: makeCol },
|
|
3554
|
+
nav: { make: makeNav },
|
|
3555
|
+
navbar: { make: makeNavbar },
|
|
3556
|
+
tabs: { make: makeTabs },
|
|
3557
|
+
alert: { make: makeAlert },
|
|
3558
|
+
badge: { make: makeBadge },
|
|
3559
|
+
progress: { make: makeProgress },
|
|
3560
|
+
listGroup: { make: makeListGroup },
|
|
3561
|
+
breadcrumb: { make: makeBreadcrumb },
|
|
3562
|
+
form: { make: makeForm },
|
|
3563
|
+
formGroup: { make: makeFormGroup },
|
|
3564
|
+
input: { make: makeInput },
|
|
3565
|
+
textarea: { make: makeTextarea },
|
|
3566
|
+
select: { make: makeSelect },
|
|
3567
|
+
checkbox: { make: makeCheckbox },
|
|
3568
|
+
stack: { make: makeStack },
|
|
3569
|
+
spinner: { make: makeSpinner },
|
|
3570
|
+
hero: { make: makeHero },
|
|
3571
|
+
featureGrid: { make: makeFeatureGrid },
|
|
3572
|
+
cta: { make: makeCTA },
|
|
3573
|
+
section: { make: makeSection },
|
|
3574
|
+
codeDemo: { make: makeCodeDemo },
|
|
3575
|
+
pagination: { make: makePagination },
|
|
3576
|
+
radio: { make: makeRadio },
|
|
3577
|
+
buttonGroup: { make: makeButtonGroup },
|
|
3578
|
+
accordion: { make: makeAccordion },
|
|
3579
|
+
modal: { make: makeModal },
|
|
3580
|
+
toast: { make: makeToast },
|
|
3581
|
+
dropdown: { make: makeDropdown },
|
|
3582
|
+
switch: { make: makeSwitch },
|
|
3583
|
+
skeleton: { make: makeSkeleton },
|
|
3584
|
+
avatar: { make: makeAvatar },
|
|
3585
|
+
carousel: { make: makeCarousel },
|
|
3586
|
+
statCard: { make: makeStatCard },
|
|
3587
|
+
tooltip: { make: makeTooltip },
|
|
3588
|
+
popover: { make: makePopover },
|
|
3589
|
+
searchInput: { make: makeSearchInput },
|
|
3590
|
+
range: { make: makeRange },
|
|
3591
|
+
mediaObject: { make: makeMediaObject },
|
|
3592
|
+
fileUpload: { make: makeFileUpload },
|
|
3593
|
+
timeline: { make: makeTimeline },
|
|
3594
|
+
stepper: { make: makeStepper },
|
|
3595
|
+
chipInput: { make: makeChipInput }
|
|
3596
|
+
};
|
|
3597
|
+
|
|
3598
|
+
/**
|
|
3599
|
+
* Factory function — create any BCCL component by type name.
|
|
3600
|
+
*
|
|
3601
|
+
* @param {string} type - Component type (e.g. 'card', 'button', 'alert')
|
|
3602
|
+
* @param {Object} [props] - Component properties
|
|
3603
|
+
* @returns {Object} TACO object
|
|
3604
|
+
* @throws {Error} If type is not found in the registry
|
|
3605
|
+
* @example
|
|
3606
|
+
* var card = make('card', { title: 'Hello', variant: 'primary' });
|
|
3607
|
+
* var btn = make('button', { text: 'Click', variant: 'success' });
|
|
3608
|
+
* var types = Object.keys(BCCL); // list all available types
|
|
3609
|
+
*/
|
|
3610
|
+
export function make(type, props) {
|
|
3611
|
+
var def = BCCL[type];
|
|
3612
|
+
if (!def) throw new Error('bw.make: unknown component type "' + type + '". Available: ' + Object.keys(BCCL).join(', '));
|
|
3613
|
+
return def.make(props || {});
|
|
3614
|
+
}
|