Fix tab/tooltip/popover/dropdown/toast init firing too early (#3218)

This commit is contained in:
Bartosz-Do authored and GitHub committed 2026-09-25 14:56:12 +02:00
1 parent d104e78947
commit 0f6e843977
7 files changed
+62 -42

No files matched your search

@@ -0,0 +1,5 @@
---
"@tabler/core": patch
---
Fixed tab, tooltip, popover, dropdown and toast init running before `DOMContentLoaded`, dropping early listeners.
+11 -8
View File
@@ -1,15 +1,18 @@
import { Dropdown } from './bootstrap'
import Manipulator from './bootstrap/dom/manipulator'
import { onDOMContentLoaded } from './bootstrap/util/index'
// js-docs-start dropdown-init
for (const dropdownTriggerEl of document.querySelectorAll<HTMLElement>('[data-bs-toggle="dropdown"], [data-tblr-toggle="dropdown"]')) {
try {
const options = {
boundary: Manipulator.getDataAttribute(dropdownTriggerEl, 'boundary') === 'viewport' ? document.documentElement : ('clippingParents' as const),
onDOMContentLoaded(() => {
for (const dropdownTriggerEl of document.querySelectorAll<HTMLElement>('[data-bs-toggle="dropdown"], [data-tblr-toggle="dropdown"]')) {
try {
const options = {
boundary: Manipulator.getDataAttribute(dropdownTriggerEl, 'boundary') === 'viewport' ? document.documentElement : ('clippingParents' as const),
}
Dropdown.getOrCreateInstance(dropdownTriggerEl, options)
} catch (error) {
console.error(error)
}
Dropdown.getOrCreateInstance(dropdownTriggerEl, options)
} catch (error) {
console.error(error)
}
}
})
// js-docs-end dropdown-init
+12 -9
View File
@@ -1,16 +1,19 @@
import { Popover } from './bootstrap'
import Manipulator from './bootstrap/dom/manipulator'
import { onDOMContentLoaded } from './bootstrap/util/index'
// js-docs-start popover-init
for (const popoverTriggerEl of document.querySelectorAll<HTMLElement>('[data-bs-toggle="popover"], [data-tblr-toggle="popover"]')) {
try {
const options = {
delay: Manipulator.getDataAttribute(popoverTriggerEl, 'delay') ?? { show: 50, hide: 50 },
placement: Manipulator.getDataAttribute(popoverTriggerEl, 'placement') ?? 'auto',
onDOMContentLoaded(() => {
for (const popoverTriggerEl of document.querySelectorAll<HTMLElement>('[data-bs-toggle="popover"], [data-tblr-toggle="popover"]')) {
try {
const options = {
delay: Manipulator.getDataAttribute(popoverTriggerEl, 'delay') ?? { show: 50, hide: 50 },
placement: Manipulator.getDataAttribute(popoverTriggerEl, 'placement') ?? 'auto',
}
Popover.getOrCreateInstance(popoverTriggerEl, options)
} catch (error) {
console.error(error)
}
Popover.getOrCreateInstance(popoverTriggerEl, options)
} catch (error) {
console.error(error)
}
}
})
// js-docs-end popover-init
+2 -1
View File
@@ -1,4 +1,5 @@
import { Tab } from './bootstrap'
import { onDOMContentLoaded } from './bootstrap/util/index'
// js-docs-start tab-init
export const EnableActivationTabsFromLocationHash = (): void => {
@@ -23,5 +24,5 @@ export const EnableActivationTabsFromLocationHash = (): void => {
}
}
EnableActivationTabsFromLocationHash()
onDOMContentLoaded(EnableActivationTabsFromLocationHash)
// js-docs-end tab-init
+18 -15
View File
@@ -1,22 +1,25 @@
import { Toast } from './bootstrap'
import SelectorEngine from './bootstrap/dom/selector-engine'
import { onDOMContentLoaded } from './bootstrap/util/index'
// js-docs-start toast-init
for (const toastTriggerEl of document.querySelectorAll<HTMLElement>('[data-bs-toggle="toast"], [data-tblr-toggle="toast"]')) {
try {
// A trigger whose target is missing is left alone instead of getting a
// click handler that throws.
const toastEl = SelectorEngine.getElementFromSelector(toastTriggerEl)
if (!toastEl) {
continue
}
onDOMContentLoaded(() => {
for (const toastTriggerEl of document.querySelectorAll<HTMLElement>('[data-bs-toggle="toast"], [data-tblr-toggle="toast"]')) {
try {
// A trigger whose target is missing is left alone instead of getting a
// click handler that throws.
const toastEl = SelectorEngine.getElementFromSelector(toastTriggerEl)
if (!toastEl) {
continue
}
const toast = Toast.getOrCreateInstance(toastEl) as InstanceType<typeof Toast>
toastTriggerEl.addEventListener('click', () => {
toast.show()
})
} catch (error) {
console.error(error)
const toast = Toast.getOrCreateInstance(toastEl) as InstanceType<typeof Toast>
toastTriggerEl.addEventListener('click', () => {
toast.show()
})
} catch (error) {
console.error(error)
}
}
}
})
// js-docs-end toast-init
+12 -9
View File
@@ -1,19 +1,22 @@
import { Tooltip } from './bootstrap'
import Manipulator from './bootstrap/dom/manipulator'
import { onDOMContentLoaded } from './bootstrap/util/index'
// js-docs-start tooltip-init
// One element with a broken config must not stop the rest of the bundle, so
// every instance is created on its own. `data-bs-delay` on the element wins
// over the shorter default delay.
for (const tooltipTriggerEl of document.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"], [data-tblr-toggle="tooltip"]')) {
try {
const options = {
delay: Manipulator.getDataAttribute(tooltipTriggerEl, 'delay') ?? { show: 50, hide: 50 },
placement: Manipulator.getDataAttribute(tooltipTriggerEl, 'placement') ?? 'auto',
onDOMContentLoaded(() => {
for (const tooltipTriggerEl of document.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"], [data-tblr-toggle="tooltip"]')) {
try {
const options = {
delay: Manipulator.getDataAttribute(tooltipTriggerEl, 'delay') ?? { show: 50, hide: 50 },
placement: Manipulator.getDataAttribute(tooltipTriggerEl, 'placement') ?? 'auto',
}
Tooltip.getOrCreateInstance(tooltipTriggerEl, options)
} catch (error) {
console.error(error)
}
Tooltip.getOrCreateInstance(tooltipTriggerEl, options)
} catch (error) {
console.error(error)
}
}
})
// js-docs-end tooltip-init
+2
View File
@@ -233,6 +233,8 @@ Tabler activates the tab that matches the URL hash on page load, so you can link
<CodeDocs name="tab-init" file="core/js/src/tab.ts" />
This runs on `DOMContentLoaded`, so `show.bs.tab` and `shown.bs.tab` for the hash tab fire before any script that itself waits for `DOMContentLoaded`. Add the listener earlier (a plain `<script>`, not one that is deferred or waits for the load event), or read `Tab.getInstance(triggerEl)` after `DOMContentLoaded` to find the tab that was activated.
## Accessibility
- Wrap the tabs in an element with `role="tablist"`, give each trigger `role="tab"` and each panel `role="tabpanel"`, and link them with `aria-controls` and `aria-labelledby`.