@programmerg/bs-elements 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +9 -0
- package/README.md +148 -0
- package/custom-elements.json +7773 -0
- package/dist/bs-elements.bundle.min.js +8 -0
- package/dist/bs-elements.bundle.min.js.map +1 -0
- package/dist/bs-elements.esm.js +1721 -0
- package/dist/bs-elements.esm.js.map +1 -0
- package/dist/bs-elements.min.js +2 -0
- package/dist/bs-elements.min.js.map +1 -0
- package/package.json +50 -0
- package/src/base/BsAccordion.js +116 -0
- package/src/base/BsAlert.js +91 -0
- package/src/base/BsButton.js +129 -0
- package/src/base/BsCarousel.js +216 -0
- package/src/base/BsCollapse.js +84 -0
- package/src/base/BsDropdown.js +98 -0
- package/src/base/BsModal.js +204 -0
- package/src/base/BsOffcanvas.js +157 -0
- package/src/base/BsPopover.js +31 -0
- package/src/base/BsTab.js +69 -0
- package/src/base/BsToast.js +163 -0
- package/src/base/BsTooltip.js +118 -0
- package/src/core/BsElement.js +81 -0
- package/src/core/ReactiveElement.js +183 -0
- package/src/extra/BsCalendar.js +14 -0
- package/src/extra/BsCombobox.js +14 -0
- package/src/extra/BsForm.js +152 -0
- package/src/extra/BsInput.js +209 -0
- package/src/extra/BsTable.js +210 -0
- package/src/extra/BsTabs.js +0 -0
- package/src/extra/BsToastManager.js +0 -0
- package/src/extra/BsTree.js +14 -0
- package/src/extra/BsUploader.js +154 -0
- package/src/index.js +12 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import BsElement from "../core/BsElement.js";
|
|
2
|
+
|
|
3
|
+
export default class BsAccordion extends BsElement {
|
|
4
|
+
|
|
5
|
+
static get properties() {
|
|
6
|
+
return {
|
|
7
|
+
flush: {
|
|
8
|
+
description: "Flag to indicate if the alert is dismissible.",
|
|
9
|
+
type: Boolean,
|
|
10
|
+
default: false
|
|
11
|
+
},
|
|
12
|
+
alwaysOpen: {
|
|
13
|
+
description: "Flag to indicate if the alert is dismissible.",
|
|
14
|
+
type: Boolean,
|
|
15
|
+
default: false
|
|
16
|
+
},
|
|
17
|
+
active: {
|
|
18
|
+
description: "Controls the visibility of the alert.",
|
|
19
|
+
type: Number,
|
|
20
|
+
default: 0
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
toggle(idx) {
|
|
26
|
+
this.children[idx].querySelector('.accordion-button').click();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
firstUpdated() {
|
|
30
|
+
this.classList.add("accordion");
|
|
31
|
+
|
|
32
|
+
this._fixMarkup();
|
|
33
|
+
this._updateAlwaysOpenState();
|
|
34
|
+
this._updateFlushState();
|
|
35
|
+
this._updateActiveState(true);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_fixMarkup() {
|
|
39
|
+
if (!this.id) this.id = crypto.randomUUID();
|
|
40
|
+
Array.from(this.children).forEach((item, idx) => {
|
|
41
|
+
item.classList.add('accordion-item');
|
|
42
|
+
|
|
43
|
+
let header = item.querySelector('.accordion-header');
|
|
44
|
+
if (!header) {
|
|
45
|
+
header = document.createElement('h2');
|
|
46
|
+
header.classList.add('accordion-header');
|
|
47
|
+
item.insertBefore(header, item.firstChild);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let button = item.querySelector('.accordion-button, [data-bs-toggle="collapse"]');
|
|
51
|
+
if (!button) {
|
|
52
|
+
button = document.createElement('h2');
|
|
53
|
+
}
|
|
54
|
+
button.classList.add('accordion-button');
|
|
55
|
+
button.setAttribute('data-bs-toggle', 'collapse');
|
|
56
|
+
console.log(button)
|
|
57
|
+
if (!button.hasAttribute('type')) button.setAttribute('type', 'button');
|
|
58
|
+
if (button.parentElement !== header) header.appendChild(button);
|
|
59
|
+
|
|
60
|
+
let collapse = item.querySelector('.accordion-collapse, .collapse');
|
|
61
|
+
if (!collapse) {
|
|
62
|
+
collapse = document.createElement('div');
|
|
63
|
+
}
|
|
64
|
+
collapse.classList.add('accordion-collapse', 'collapse');
|
|
65
|
+
const collapseId = collapse.id || `${this.id}-collapse-${idx}`;
|
|
66
|
+
collapse.id = collapseId;
|
|
67
|
+
|
|
68
|
+
// Ensure collapse content is wrapped in .accordion-body
|
|
69
|
+
let body = collapse.querySelector('.accordion-body');
|
|
70
|
+
if (!body) {
|
|
71
|
+
body = document.createElement('div');
|
|
72
|
+
body.classList.add('accordion-body');
|
|
73
|
+
|
|
74
|
+
while (collapse.firstChild) {
|
|
75
|
+
body.appendChild(collapse.firstChild);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
collapse.appendChild(body);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Wire button -> collapse
|
|
82
|
+
button.setAttribute('data-bs-target', `#${collapseId}`);
|
|
83
|
+
button.setAttribute('aria-controls', collapseId);
|
|
84
|
+
|
|
85
|
+
// Set initial expanded/collapsed classes and aria state based on active
|
|
86
|
+
const activeIdx = Number(this.active) || 0;
|
|
87
|
+
if (idx === activeIdx) {
|
|
88
|
+
collapse.classList.add('show');
|
|
89
|
+
button.classList.remove('collapsed');
|
|
90
|
+
button.setAttribute('aria-expanded', 'true');
|
|
91
|
+
} else {
|
|
92
|
+
collapse.classList.remove('show');
|
|
93
|
+
if (!button.classList.contains('collapsed')) button.classList.add('collapsed');
|
|
94
|
+
button.setAttribute('aria-expanded', 'false');
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_updateAlwaysOpenState() {
|
|
100
|
+
this.querySelectorAll('.collapse').forEach(collapse => {
|
|
101
|
+
if (!this.alwaysOpen) collapse.setAttribute('data-bs-parent', `#${this.id}`);
|
|
102
|
+
else collapse.removeAttribute('data-bs-parent');
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_updateFlushState() {
|
|
107
|
+
this.classList.toggle('accordion-flush', this.flush);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_updateActiveState(isFirstUpdated = false) {
|
|
111
|
+
if (!isFirstUpdated) this.toggle(this.active);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
customElements.define("bs-accordion", BsAccordion);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Alert } from "bootstrap";
|
|
2
|
+
import BsElement from "../core/BsElement.js";
|
|
3
|
+
|
|
4
|
+
export default class BsAlert extends BsElement {
|
|
5
|
+
|
|
6
|
+
static get properties() {
|
|
7
|
+
return {
|
|
8
|
+
color: {
|
|
9
|
+
description: "The color theme for the alert.",
|
|
10
|
+
type: String,
|
|
11
|
+
default: "success",
|
|
12
|
+
values: ["primary", "secondary", "success", "danger", "warning", "info", "light", "dark"]
|
|
13
|
+
},
|
|
14
|
+
dismissible: {
|
|
15
|
+
description: "Flag to indicate if the alert is dismissible.",
|
|
16
|
+
type: Boolean,
|
|
17
|
+
default: false
|
|
18
|
+
},
|
|
19
|
+
fade: {
|
|
20
|
+
description: "Flag to enable fade transition.",
|
|
21
|
+
type: Boolean,
|
|
22
|
+
default: true
|
|
23
|
+
},
|
|
24
|
+
open: {
|
|
25
|
+
description: "Controls the visibility of the alert.",
|
|
26
|
+
type: Boolean,
|
|
27
|
+
default: true
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static get bs() {
|
|
33
|
+
return Alert;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
super();
|
|
38
|
+
|
|
39
|
+
// this is an external state managed by bootstrap
|
|
40
|
+
Object.defineProperty(this.constructor.prototype, "open", {
|
|
41
|
+
get() {
|
|
42
|
+
return this.classList.contains("show");
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
close() {
|
|
48
|
+
return this.bs?.close();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
firstUpdated() {
|
|
52
|
+
this.classList.add("alert");
|
|
53
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "alert");
|
|
54
|
+
|
|
55
|
+
this._updateColorState();
|
|
56
|
+
this._updateFadeState();
|
|
57
|
+
this._updateDismissibleState();
|
|
58
|
+
this._updateOpenState(true);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_updateColorState() {
|
|
62
|
+
this.classList.remove(
|
|
63
|
+
...this.constructor.properties
|
|
64
|
+
.color.values.map(v => "alert-" + v)
|
|
65
|
+
);
|
|
66
|
+
this.classList.add("alert-" + this.color);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_updateFadeState() {
|
|
70
|
+
this.classList.toggle("fade", this.fade);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_updateDismissibleState() {
|
|
74
|
+
this.classList.toggle("alert-dismissible", this.dismissible);
|
|
75
|
+
const btn = this.querySelector(":scope > .btn-close");
|
|
76
|
+
const markup = `<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>`;
|
|
77
|
+
|
|
78
|
+
if (this.dismissible) {
|
|
79
|
+
if (!btn) this.insertAdjacentHTML("beforeend", markup);
|
|
80
|
+
} else {
|
|
81
|
+
if (btn) btn.remove();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_updateOpenState(isFirstUpdate = false) {
|
|
86
|
+
this.classList.toggle("show", this.__propValues['open']);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
customElements.define("bs-alert", BsAlert);
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Button } from "bootstrap";
|
|
2
|
+
import BsElement from "../core/BsElement.js";
|
|
3
|
+
|
|
4
|
+
export default class BsButton extends BsElement {
|
|
5
|
+
|
|
6
|
+
static get properties() {
|
|
7
|
+
return {
|
|
8
|
+
active: {
|
|
9
|
+
description: "Indicates if the component is active.",
|
|
10
|
+
type: Boolean,
|
|
11
|
+
default: false
|
|
12
|
+
},
|
|
13
|
+
color: {
|
|
14
|
+
description: "Color theme for the button.",
|
|
15
|
+
type: String,
|
|
16
|
+
default: undefined,
|
|
17
|
+
values: ["primary", "secondary", "success", "danger", "warning", "info", "light", "dark"]
|
|
18
|
+
},
|
|
19
|
+
outline: {
|
|
20
|
+
description: "Indicates if the component should have an outline.",
|
|
21
|
+
type: Boolean,
|
|
22
|
+
default: false
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
description: "Size of the Button.",
|
|
26
|
+
type: String,
|
|
27
|
+
default: undefined,
|
|
28
|
+
values: ["sm", "lg"]
|
|
29
|
+
},
|
|
30
|
+
toggle: {
|
|
31
|
+
description: "Bootstrap toggle type.",
|
|
32
|
+
type: String,
|
|
33
|
+
default: undefined,
|
|
34
|
+
values: ["button", "collapse", "dropdown", "modal", "offcanvas", "tab"]
|
|
35
|
+
},
|
|
36
|
+
target: {
|
|
37
|
+
description: "Target selector for toggles (data-bs-target).",
|
|
38
|
+
type: String,
|
|
39
|
+
default: undefined
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static get bs() {
|
|
45
|
+
return Button;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
constructor() {
|
|
49
|
+
super();
|
|
50
|
+
|
|
51
|
+
// this is an external state managed by bootstrap
|
|
52
|
+
Object.defineProperty(this.constructor.prototype, "active", {
|
|
53
|
+
get() {
|
|
54
|
+
return this.classList.contains("active");
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// toggle() {
|
|
60
|
+
// return this.bs?.toggle();
|
|
61
|
+
// }
|
|
62
|
+
|
|
63
|
+
firstUpdated() {
|
|
64
|
+
//this.classList.add("btn");
|
|
65
|
+
|
|
66
|
+
// default type to prevent accidental form submit
|
|
67
|
+
if (!this.hasAttribute("type")) this.setAttribute("type", "button");
|
|
68
|
+
|
|
69
|
+
this._updateColorState();
|
|
70
|
+
this._updateOutlineState();
|
|
71
|
+
this._updateSizeState();
|
|
72
|
+
this._updateToggleState();
|
|
73
|
+
this._updateTargetState();
|
|
74
|
+
this._updateActiveState(true);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_updateColorState() {
|
|
78
|
+
this.classList.remove(
|
|
79
|
+
...this.constructor.properties
|
|
80
|
+
.color.values.map(v => "btn-" + v)
|
|
81
|
+
);
|
|
82
|
+
this.classList.remove(
|
|
83
|
+
...this.constructor.properties
|
|
84
|
+
.color.values.map(v => "btn-outline-" + v)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const variant = this.outline ? `btn-outline-${this.color}` : `btn-${this.color}`;
|
|
88
|
+
this.classList.add(variant);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_updateOutlineState() {
|
|
92
|
+
this._updateColorState();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
_updateSizeState() {
|
|
96
|
+
this.classList.remove(
|
|
97
|
+
...this.constructor.properties
|
|
98
|
+
.size.values.map(v => "btn-" + v)
|
|
99
|
+
);
|
|
100
|
+
if (this.size) this.classList.add("btn-" + this.size);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_updateToggleState() {
|
|
104
|
+
if (this.toggle) this.setAttribute("data-bs-toggle", this.toggle);
|
|
105
|
+
else this.removeAttribute("data-bs-toggle");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_updateTargetState() {
|
|
109
|
+
if (this.target) this.setAttribute("data-bs-target", this.target);
|
|
110
|
+
else this.removeAttribute("data-bs-target");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_updateActiveState(isFirstUpdate = false) {
|
|
114
|
+
if (isFirstUpdate) {
|
|
115
|
+
this.classList.toggle("active", !!this.__propValues['active']);
|
|
116
|
+
|
|
117
|
+
if (this.toggle === "button") {
|
|
118
|
+
this.setAttribute("aria-pressed", this.active ? "true" : "false");
|
|
119
|
+
} else {
|
|
120
|
+
this.removeAttribute("aria-pressed");
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
this.toggle();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
customElements.define("bs-button", BsButton);
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { Carousel } from "bootstrap";
|
|
2
|
+
import BsElement from "../core/BsElement.js";
|
|
3
|
+
|
|
4
|
+
export default class BsCarousel extends BsElement {
|
|
5
|
+
|
|
6
|
+
static get properties() {
|
|
7
|
+
return {
|
|
8
|
+
fade: {
|
|
9
|
+
description: "",
|
|
10
|
+
type: Boolean,
|
|
11
|
+
default: false
|
|
12
|
+
},
|
|
13
|
+
indicators: {
|
|
14
|
+
description: "",
|
|
15
|
+
type: Boolean,
|
|
16
|
+
default: true
|
|
17
|
+
},
|
|
18
|
+
interval: {
|
|
19
|
+
description: "The time interval (in milliseconds) between automatic transitions of the carousel items.",
|
|
20
|
+
type: Number,
|
|
21
|
+
default: 5000,
|
|
22
|
+
attribute: 'data-bs-interval'
|
|
23
|
+
},
|
|
24
|
+
keyboard: {
|
|
25
|
+
description: "A Boolean indicating whether the carousel should respond to keyboard navigation.",
|
|
26
|
+
type: Boolean,
|
|
27
|
+
default: true,
|
|
28
|
+
attribute: 'data-bs-keyboard'
|
|
29
|
+
},
|
|
30
|
+
pause: {
|
|
31
|
+
description: "A Boolean indicating whether automatic cycling of the carousel should pause on hover.",
|
|
32
|
+
type: Boolean,
|
|
33
|
+
default: true,
|
|
34
|
+
attribute: 'data-bs-pause'
|
|
35
|
+
},
|
|
36
|
+
ride: {
|
|
37
|
+
description: "A Boolean indicating whether the carousel should automatically cycle through items.",
|
|
38
|
+
type: Boolean,
|
|
39
|
+
default: true,
|
|
40
|
+
attribute: 'data-bs-ride'
|
|
41
|
+
},
|
|
42
|
+
touch: {
|
|
43
|
+
description: "A Boolean indicating whether the carousel should support left/right swipe interactions on touchscreen",
|
|
44
|
+
type: Boolean,
|
|
45
|
+
default: true,
|
|
46
|
+
attribute: 'data-bs-touch'
|
|
47
|
+
},
|
|
48
|
+
wrap: {
|
|
49
|
+
description: "A Boolean indicating whether the carousel should cycle continuously or have hard stops",
|
|
50
|
+
type: Boolean,
|
|
51
|
+
default: true,
|
|
52
|
+
attribute: 'data-bs-wrap'
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static get bs() {
|
|
58
|
+
return Carousel;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
cycle() {
|
|
62
|
+
return this.bs?.cycle();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
next() {
|
|
66
|
+
return this.bs?.next();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
nextWhenVisible() {
|
|
70
|
+
return this.bs?.nextWhenVisible();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pause() {
|
|
74
|
+
return this.bs?.pause();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
prev() {
|
|
78
|
+
return this.bs?.prev();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
to(idx) {
|
|
82
|
+
return this.bs?.to(idx);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
firstUpdated() {
|
|
86
|
+
this.classList.add("carousel");
|
|
87
|
+
this.classList.add("slide");
|
|
88
|
+
|
|
89
|
+
this._fixMarkup();
|
|
90
|
+
this._updateFadeState();
|
|
91
|
+
this._updateIndicatorsState();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_fixMarkup() {
|
|
95
|
+
// ensure inner wrapper
|
|
96
|
+
let inner = this.querySelector(":scope > .carousel-inner");
|
|
97
|
+
if (!inner) {
|
|
98
|
+
inner = document.createElement("div");
|
|
99
|
+
inner.className = "carousel-inner";
|
|
100
|
+
|
|
101
|
+
// move suitable children into inner
|
|
102
|
+
const toMove = [];
|
|
103
|
+
for (const child of Array.from(this.childNodes)) {
|
|
104
|
+
if (child.nodeType !== Node.ELEMENT_NODE) continue;
|
|
105
|
+
const el = child;
|
|
106
|
+
if (el.classList && (el.classList.contains("carousel-indicators") || el.classList.contains("carousel-control-prev") || el.classList.contains("carousel-control-next") || el.classList.contains("carousel-inner"))) continue;
|
|
107
|
+
toMove.push(el);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
toMove.forEach(el => {
|
|
111
|
+
// if element is already a carousel-item, just append
|
|
112
|
+
if (el.classList && el.classList.contains("carousel-item")) inner.appendChild(el);
|
|
113
|
+
else {
|
|
114
|
+
const wrap = document.createElement("div");
|
|
115
|
+
wrap.className = "carousel-item";
|
|
116
|
+
wrap.appendChild(el);
|
|
117
|
+
inner.appendChild(wrap);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// insert inner at the end (indicators may be inserted before it later)
|
|
122
|
+
this.appendChild(inner);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ensure every child of inner has the proper classes
|
|
126
|
+
const items = Array.from(inner.children).filter(n => n.nodeType === Node.ELEMENT_NODE);
|
|
127
|
+
items.forEach((it, i) => {
|
|
128
|
+
it.classList.add("carousel-item");
|
|
129
|
+
if (!inner.querySelector(".carousel-item.active") && i === 0) {
|
|
130
|
+
it.classList.add("active");
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// indicators and controls
|
|
135
|
+
this._ensureControls();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_renderIndicators() {
|
|
139
|
+
// remove existing indicators container
|
|
140
|
+
let indicators = this.querySelector(":scope > .carousel-indicators");
|
|
141
|
+
if (indicators) indicators.remove();
|
|
142
|
+
|
|
143
|
+
const inner = this.querySelector(":scope > .carousel-inner");
|
|
144
|
+
if (!inner) return;
|
|
145
|
+
|
|
146
|
+
indicators = document.createElement("div");
|
|
147
|
+
indicators.className = "carousel-indicators";
|
|
148
|
+
|
|
149
|
+
const slides = Array.from(inner.children).filter(n => n.nodeType === Node.ELEMENT_NODE);
|
|
150
|
+
slides.forEach((_, idx) => {
|
|
151
|
+
const btn = document.createElement("button");
|
|
152
|
+
btn.type = "button";
|
|
153
|
+
btn.setAttribute("aria-label", `Slide ${idx + 1}`);
|
|
154
|
+
btn.className = idx === 0 ? "active" : "";
|
|
155
|
+
btn.addEventListener("click", () => this.to(idx));
|
|
156
|
+
indicators.appendChild(btn);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// insert indicators before inner
|
|
160
|
+
this.insertBefore(indicators, inner);
|
|
161
|
+
this._renderIndicatorsActive();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_ensureControls() {
|
|
165
|
+
// prev
|
|
166
|
+
if (!this.querySelector(":scope > .carousel-control-prev")) {
|
|
167
|
+
const prev = document.createElement("button");
|
|
168
|
+
prev.className = "carousel-control-prev";
|
|
169
|
+
prev.type = "button";
|
|
170
|
+
prev.innerHTML = `<span class="carousel-control-prev-icon" aria-hidden="true"></span><span class="visually-hidden">Previous</span>`;
|
|
171
|
+
prev.addEventListener("click", () => this.prev());
|
|
172
|
+
this.appendChild(prev);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// next
|
|
176
|
+
if (!this.querySelector(":scope > .carousel-control-next")) {
|
|
177
|
+
const next = document.createElement("button");
|
|
178
|
+
next.className = "carousel-control-next";
|
|
179
|
+
next.type = "button";
|
|
180
|
+
next.innerHTML = `<span class="carousel-control-next-icon" aria-hidden="true"></span><span class="visually-hidden">Next</span>`;
|
|
181
|
+
next.addEventListener("click", () => this.next());
|
|
182
|
+
this.appendChild(next);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_renderIndicatorsActive() {
|
|
187
|
+
const indicators = this.querySelector(":scope > .carousel-indicators");
|
|
188
|
+
const inner = this.querySelector(":scope > .carousel-inner");
|
|
189
|
+
if (!indicators || !inner) return;
|
|
190
|
+
|
|
191
|
+
const slides = Array.from(inner.children).filter(n => n.nodeType === Node.ELEMENT_NODE);
|
|
192
|
+
const activeIndex = slides.findIndex(s => s.classList.contains("active"));
|
|
193
|
+
|
|
194
|
+
Array.from(indicators.children).forEach((btn, idx) => {
|
|
195
|
+
btn.classList.toggle("active", idx === activeIndex);
|
|
196
|
+
if (idx === activeIndex) btn.setAttribute("aria-current", "true");
|
|
197
|
+
else btn.removeAttribute("aria-current");
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_updateFadeState() {
|
|
202
|
+
this.classList.toggle("carousel-fade", !!this.fade);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_updateIndicatorsState() {
|
|
206
|
+
if (this.indicators) {
|
|
207
|
+
this._renderIndicators();
|
|
208
|
+
} else {
|
|
209
|
+
const el = this.querySelector(".carousel-indicators");
|
|
210
|
+
if (el) el.remove();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
customElements.define("bs-carousel", BsCarousel);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Collapse } from "bootstrap";
|
|
2
|
+
import BsElement from "../core/BsElement.js";
|
|
3
|
+
|
|
4
|
+
export default class BsCollapse extends BsElement {
|
|
5
|
+
|
|
6
|
+
static get properties() {
|
|
7
|
+
return {
|
|
8
|
+
open: {
|
|
9
|
+
description: "Toggle visible state",
|
|
10
|
+
type: Boolean,
|
|
11
|
+
default: false
|
|
12
|
+
},
|
|
13
|
+
horizontal: {
|
|
14
|
+
description: "Toggle horizontal collapsing",
|
|
15
|
+
type: Boolean,
|
|
16
|
+
default: false
|
|
17
|
+
},
|
|
18
|
+
parent: {
|
|
19
|
+
description: "Selector of the container element",
|
|
20
|
+
type: String,
|
|
21
|
+
default: undefined,
|
|
22
|
+
attribute: "data-bs-parent"
|
|
23
|
+
},
|
|
24
|
+
toggle: {
|
|
25
|
+
description: "Toggles the collapsible element",
|
|
26
|
+
type: Boolean,
|
|
27
|
+
default: false,
|
|
28
|
+
attribute: "data-bs-toggle"
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static get bs() {
|
|
34
|
+
return Collapse;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
super();
|
|
39
|
+
|
|
40
|
+
// this is an external state managed by bootstrap
|
|
41
|
+
Object.defineProperty(this.constructor.prototype, "open", {
|
|
42
|
+
get() {
|
|
43
|
+
return this.classList.contains("show");
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
hide() {
|
|
49
|
+
return this.bs?.hide();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
show() {
|
|
53
|
+
return this.bs?.show();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
toggle() {
|
|
57
|
+
return this.bs?.toggle();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
firstUpdated() {
|
|
61
|
+
this.classList.add("collapse");
|
|
62
|
+
|
|
63
|
+
this._updateHorizontalState();
|
|
64
|
+
this._updateOpenState(true);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_updateHorizontalState() {
|
|
68
|
+
if (this.horizontal) {
|
|
69
|
+
this.classList.add("collapse-horizontal");
|
|
70
|
+
this.style.width ||= "0px";
|
|
71
|
+
} else {
|
|
72
|
+
this.classList.remove("collapse-horizontal");
|
|
73
|
+
this.style.width = "";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_updateOpenState(isFirstUpdate = false) {
|
|
78
|
+
if (isFirstUpdate) this.classList.toggle("show", this.__propValues['open']);
|
|
79
|
+
else this.toggle();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
customElements.define("bs-collapse", BsCollapse);
|