mirror of
https://github.com/tabler/tabler.git
synced 2026-10-13 21:57:00 +04:00
Add Toggler plugin
- Ported from Bootstrap v6, with demo page and docs - Raised the unminified JS size budget to 59 kB
This commit is contained in:
1 parent
a15d5d71a1
commit
b9cb13b6b6
9 files changed
+643
-2
No files matched your search
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@tabler/core": minor
|
||||
"@tabler/preview": minor
|
||||
"@tabler/docs": minor
|
||||
---
|
||||
|
||||
Added the `Toggler` plugin, which toggles a class or an attribute on click with `data-bs-toggle="toggler"`.
|
||||
@@ -11,6 +11,7 @@ export { default as Popover } from './bootstrap/popover'
|
||||
export { default as ScrollSpy } from './bootstrap/scrollspy'
|
||||
export { default as Tab } from './bootstrap/tab'
|
||||
export { default as Toast } from './bootstrap/toast'
|
||||
export { default as Toggler } from './bootstrap/toggler'
|
||||
export { default as Tooltip } from './bootstrap/tooltip'
|
||||
|
||||
import Alert from './bootstrap/alert'
|
||||
@@ -24,6 +25,7 @@ import Popover from './bootstrap/popover'
|
||||
import ScrollSpy from './bootstrap/scrollspy'
|
||||
import Tab from './bootstrap/tab'
|
||||
import Toast from './bootstrap/toast'
|
||||
import Toggler from './bootstrap/toggler'
|
||||
import Tooltip from './bootstrap/tooltip'
|
||||
|
||||
export const bootstrap = {
|
||||
@@ -38,5 +40,6 @@ export const bootstrap = {
|
||||
ScrollSpy,
|
||||
Tab,
|
||||
Toast,
|
||||
Toggler,
|
||||
Tooltip,
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap toggler.ts
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import BaseComponent from './base-component'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import { eventActionOnPlugin } from './util/component-functions'
|
||||
import { defineJQueryPlugin } from './util/index.js'
|
||||
import type { JQueryCollectionLike } from './types'
|
||||
|
||||
type TogglerConfig = {
|
||||
attribute: string
|
||||
value: string | number | boolean | null
|
||||
}
|
||||
|
||||
type TogglerConfigInput = Partial<TogglerConfig> & Record<string, unknown>
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'toggler'
|
||||
const DATA_KEY = 'bs.toggler'
|
||||
const EVENT_KEY = `.${DATA_KEY}`
|
||||
|
||||
const EVENT_TOGGLE = `toggle${EVENT_KEY}`
|
||||
const EVENT_TOGGLED = `toggled${EVENT_KEY}`
|
||||
const EVENT_CLICK = 'click'
|
||||
|
||||
const SELECTOR_DATA_TOGGLE = ':is([data-bs-toggle="toggler"], [data-tblr-toggle="toggler"])'
|
||||
|
||||
const DefaultType: Record<keyof TogglerConfig, string> = {
|
||||
attribute: 'string',
|
||||
value: '(string|number|boolean|null)',
|
||||
}
|
||||
|
||||
const Default: TogglerConfig = {
|
||||
attribute: 'class',
|
||||
value: null,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Toggler extends BaseComponent {
|
||||
declare _element: HTMLElement
|
||||
declare _config: TogglerConfig
|
||||
|
||||
constructor(element?: Element | string | null, config?: TogglerConfigInput | null) {
|
||||
super(element as string | HTMLElement, (config ?? undefined) as Record<string, unknown>)
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default(): TogglerConfig {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType(): Record<keyof TogglerConfig, string> {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME(): string {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
toggle(): void {
|
||||
const toggleEvent = EventHandler.trigger(this._element, EVENT_TOGGLE)
|
||||
|
||||
if (toggleEvent?.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
this._execute()
|
||||
|
||||
EventHandler.trigger(this._element, EVENT_TOGGLED)
|
||||
}
|
||||
|
||||
// Private
|
||||
_execute(): void {
|
||||
const { attribute, value } = this._config
|
||||
|
||||
if (attribute === 'id') {
|
||||
return // You have to be kidding
|
||||
}
|
||||
|
||||
// Nothing to toggle without a value (e.g. missing `data-bs-value`)
|
||||
if (value === null || value === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (attribute === 'class') {
|
||||
this._element.classList.toggle(String(value))
|
||||
return
|
||||
}
|
||||
|
||||
// Compare as strings since getAttribute() always returns a string
|
||||
if (this._element.getAttribute(attribute) === String(value)) {
|
||||
this._element.removeAttribute(attribute)
|
||||
return
|
||||
}
|
||||
|
||||
this._element.setAttribute(attribute, String(value))
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(this: JQueryCollectionLike, config?: unknown): unknown {
|
||||
return this.each(function (this: HTMLElement) {
|
||||
const data = Toggler.getOrCreateInstance(this) as Toggler
|
||||
|
||||
if (config === 'toggle') {
|
||||
data.toggle()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
eventActionOnPlugin(Toggler, EVENT_CLICK, SELECTOR_DATA_TOGGLE, 'toggle')
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Toggler)
|
||||
|
||||
export default Toggler
|
||||
export type { TogglerConfig }
|
||||
@@ -0,0 +1,289 @@
|
||||
import { describe, it, expect, beforeAll, afterEach, vi } from 'vitest'
|
||||
import Toggler from '../../src/bootstrap/toggler'
|
||||
import { clearFixture, getFixture } from '../helpers/fixture'
|
||||
|
||||
describe('Toggler', () => {
|
||||
let fixtureEl: HTMLElement
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('VERSION', () => {
|
||||
it('should return plugin version', () => {
|
||||
expect(typeof Toggler.VERSION).toBe('string')
|
||||
})
|
||||
})
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should take care of element either passed as a CSS selector or DOM element', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="bg-warning" data-bs-attribute="class"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const togglerBySelector = new Toggler('[data-bs-toggle="toggler"]')
|
||||
expect(togglerBySelector._element).toEqual(togglerEl)
|
||||
|
||||
const togglerByElement = new Toggler(togglerEl)
|
||||
expect(togglerByElement._element).toEqual(togglerEl)
|
||||
})
|
||||
})
|
||||
|
||||
describe('toggle', () => {
|
||||
it('should toggle class on the element', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="bg-warning" data-bs-attribute="class"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const toggler = new Toggler(togglerEl)
|
||||
|
||||
toggler.toggle()
|
||||
expect(togglerEl.classList.contains('bg-warning')).toBe(true)
|
||||
|
||||
toggler.toggle()
|
||||
expect(togglerEl.classList.contains('bg-warning')).toBe(false)
|
||||
})
|
||||
|
||||
it('should toggle attribute on the element', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="true" data-bs-attribute="hidden"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const toggler = new Toggler(togglerEl)
|
||||
|
||||
toggler.toggle()
|
||||
expect(togglerEl.getAttribute('hidden')).toEqual('true')
|
||||
|
||||
toggler.toggle()
|
||||
expect(togglerEl.hasAttribute('hidden')).toBe(false)
|
||||
})
|
||||
|
||||
it('should not throw and should be a no-op when no value is provided', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-attribute="class"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const toggler = new Toggler(togglerEl)
|
||||
const classNameBefore = togglerEl.className
|
||||
|
||||
expect(() => toggler.toggle()).not.toThrow()
|
||||
expect(togglerEl.className).toEqual(classNameBefore)
|
||||
})
|
||||
|
||||
it('should not toggle id attribute', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="new-id" data-bs-attribute="id"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const toggler = new Toggler(togglerEl)
|
||||
|
||||
toggler.toggle()
|
||||
expect(togglerEl.getAttribute('id')).toBeNull()
|
||||
})
|
||||
|
||||
it('should trigger events', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="bg-warning" data-bs-attribute="class"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const toggler = new Toggler(togglerEl)
|
||||
|
||||
const toggleSpy = vi.fn()
|
||||
const toggledSpy = vi.fn()
|
||||
|
||||
togglerEl.addEventListener('toggle.bs.toggler', toggleSpy)
|
||||
togglerEl.addEventListener('toggled.bs.toggler', toggledSpy)
|
||||
|
||||
toggler.toggle()
|
||||
|
||||
expect(toggleSpy).toHaveBeenCalled()
|
||||
expect(toggledSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not trigger toggled event if toggle is prevented', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="bg-warning" data-bs-attribute="class"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const toggler = new Toggler(togglerEl)
|
||||
|
||||
const toggledSpy = vi.fn()
|
||||
|
||||
togglerEl.addEventListener('toggle.bs.toggler', (event) => {
|
||||
event.preventDefault()
|
||||
})
|
||||
togglerEl.addEventListener('toggled.bs.toggler', toggledSpy)
|
||||
|
||||
toggler.toggle()
|
||||
|
||||
expect(toggledSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispose', () => {
|
||||
it('should dispose a toggler', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="bg-warning" data-bs-attribute="class"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const toggler = new Toggler(togglerEl)
|
||||
|
||||
expect(Toggler.getInstance(togglerEl)).not.toBeNull()
|
||||
|
||||
toggler.dispose()
|
||||
|
||||
expect(Toggler.getInstance(togglerEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInstance', () => {
|
||||
it('should return null if there is no instance', () => {
|
||||
expect(Toggler.getInstance(fixtureEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return this instance', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="bg-warning" data-bs-attribute="class"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const toggler = new Toggler(togglerEl)
|
||||
|
||||
expect(Toggler.getInstance(togglerEl)).toEqual(toggler)
|
||||
expect(Toggler.getInstance(togglerEl)).toBeInstanceOf(Toggler)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should return toggler instance', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="bg-warning" data-bs-attribute="class"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const toggler = new Toggler(togglerEl)
|
||||
|
||||
expect(Toggler.getOrCreateInstance(togglerEl)).toEqual(toggler)
|
||||
expect(Toggler.getInstance(togglerEl)).toEqual(Toggler.getOrCreateInstance(togglerEl, {}))
|
||||
expect(Toggler.getOrCreateInstance(togglerEl)).toBeInstanceOf(Toggler)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no toggler instance', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="bg-warning" data-bs-attribute="class"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
|
||||
expect(Toggler.getInstance(togglerEl)).toBeNull()
|
||||
expect(Toggler.getOrCreateInstance(togglerEl)).toBeInstanceOf(Toggler)
|
||||
})
|
||||
})
|
||||
|
||||
describe('data-api', () => {
|
||||
it('should toggle class on click', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="bg-warning" data-bs-attribute="class"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
|
||||
togglerEl.click()
|
||||
expect(togglerEl.classList.contains('bg-warning')).toBe(true)
|
||||
|
||||
togglerEl.click()
|
||||
expect(togglerEl.classList.contains('bg-warning')).toBe(false)
|
||||
})
|
||||
|
||||
it('should toggle attribute on click', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="true" data-bs-attribute="hidden"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
|
||||
togglerEl.click()
|
||||
expect(togglerEl.getAttribute('hidden')).toEqual('true')
|
||||
|
||||
togglerEl.click()
|
||||
expect(togglerEl.hasAttribute('hidden')).toBe(false)
|
||||
})
|
||||
|
||||
it('should not toggle id attribute on click', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="toggler" data-bs-value="new-id" data-bs-attribute="id"></div>'
|
||||
|
||||
const togglerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
|
||||
togglerEl.click()
|
||||
expect(togglerEl.getAttribute('id')).toBeNull()
|
||||
})
|
||||
|
||||
it('should toggle class on target element via data-bs-target with ID selector', () => {
|
||||
fixtureEl.innerHTML = ['<button data-bs-toggle="toggler" data-bs-target="#target-element"></button>', '<div id="target-element" data-bs-value="bg-info" data-bs-attribute="class"></div>'].join('')
|
||||
|
||||
const triggerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const targetEl = fixtureEl.querySelector<HTMLElement>('#target-element')!
|
||||
|
||||
triggerEl.click()
|
||||
expect(targetEl.classList.contains('bg-info')).toBe(true)
|
||||
|
||||
triggerEl.click()
|
||||
expect(targetEl.classList.contains('bg-info')).toBe(false)
|
||||
})
|
||||
|
||||
it('should toggle class on multiple target elements via data-bs-target with class selector', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<button data-bs-toggle="toggler" data-bs-target=".target-class"></button>',
|
||||
'<div class="target-class" data-bs-value="bg-warning" data-bs-attribute="class"></div>',
|
||||
'<div class="target-class" data-bs-value="bg-info" data-bs-attribute="class"></div>',
|
||||
'<div class="target-class" data-bs-value="bg-danger" data-bs-attribute="class"></div>',
|
||||
].join('')
|
||||
|
||||
const triggerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const targetEls = fixtureEl.querySelectorAll('.target-class')
|
||||
|
||||
triggerEl.click()
|
||||
expect(targetEls[0].classList.contains('bg-warning')).toBe(true)
|
||||
expect(targetEls[1].classList.contains('bg-info')).toBe(true)
|
||||
expect(targetEls[2].classList.contains('bg-danger')).toBe(true)
|
||||
|
||||
triggerEl.click()
|
||||
expect(targetEls[0].classList.contains('bg-warning')).toBe(false)
|
||||
expect(targetEls[1].classList.contains('bg-info')).toBe(false)
|
||||
expect(targetEls[2].classList.contains('bg-danger')).toBe(false)
|
||||
})
|
||||
|
||||
it('should toggle attribute on target element via data-bs-target', () => {
|
||||
fixtureEl.innerHTML = ['<button data-bs-toggle="toggler" data-bs-target="#target-fieldset"></button>', '<fieldset id="target-fieldset" data-bs-value="disabled" data-bs-attribute="disabled"></fieldset>'].join('')
|
||||
|
||||
const triggerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const targetEl = fixtureEl.querySelector<HTMLElement>('#target-fieldset')!
|
||||
|
||||
triggerEl.click()
|
||||
expect(targetEl.getAttribute('disabled')).toEqual('disabled')
|
||||
|
||||
triggerEl.click()
|
||||
expect(targetEl.hasAttribute('disabled')).toBe(false)
|
||||
})
|
||||
|
||||
it('should toggle target element via href attribute as fallback', () => {
|
||||
fixtureEl.innerHTML = ['<a data-bs-toggle="toggler" href="#target-via-href"></a>', '<div id="target-via-href" data-bs-value="active" data-bs-attribute="class"></div>'].join('')
|
||||
|
||||
const triggerEl = fixtureEl.querySelector<HTMLElement>('[data-bs-toggle="toggler"]')!
|
||||
const targetEl = fixtureEl.querySelector<HTMLElement>('#target-via-href')!
|
||||
|
||||
triggerEl.click()
|
||||
expect(targetEl.classList.contains('active')).toBe(true)
|
||||
|
||||
triggerEl.click()
|
||||
expect(targetEl.classList.contains('active')).toBe(false)
|
||||
})
|
||||
it('should work with the data-tblr- prefix', () => {
|
||||
fixtureEl.innerHTML = ['<button data-tblr-toggle="toggler" data-tblr-target="#target-tblr"></button>', '<div id="target-tblr" data-tblr-value="active"></div>'].join('')
|
||||
|
||||
const triggerEl = fixtureEl.querySelector<HTMLElement>('[data-tblr-toggle="toggler"]')!
|
||||
const targetEl = fixtureEl.querySelector<HTMLElement>('#target-tblr')!
|
||||
|
||||
triggerEl.click()
|
||||
expect(targetEl.classList.contains('active')).toBe(true)
|
||||
})
|
||||
|
||||
it('should not toggle when the trigger is disabled', () => {
|
||||
fixtureEl.innerHTML = ['<button data-bs-toggle="toggler" data-bs-value="active" disabled></button>', '<a href="#" class="disabled" data-tblr-toggle="toggler" data-tblr-value="active"></a>'].join('')
|
||||
|
||||
const buttonEl = fixtureEl.querySelector<HTMLElement>('button')!
|
||||
const linkEl = fixtureEl.querySelector<HTMLElement>('a')!
|
||||
|
||||
buttonEl.click()
|
||||
linkEl.click()
|
||||
expect(buttonEl.classList.contains('active')).toBe(false)
|
||||
expect(linkEl.classList.contains('active')).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
+2
-2
@@ -212,7 +212,7 @@
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/tabler.js",
|
||||
"maxSize": "58 kB"
|
||||
"maxSize": "59 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/tabler.min.js",
|
||||
@@ -220,7 +220,7 @@
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/tabler.esm.js",
|
||||
"maxSize": "58 kB"
|
||||
"maxSize": "59 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/tabler.esm.min.js",
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: Toggler
|
||||
summary: Toggler switches a class or an attribute on click. It needs only data attributes, so you do not have to write small one-off scripts.
|
||||
description: Toggle a class or any attribute on click with data attributes only - no custom JavaScript needed.
|
||||
added-in: '1.7.0'
|
||||
related: [/ui/components/switch-icon, /ui/components/button]
|
||||
source: core/js/src/bootstrap/toggler.ts
|
||||
---
|
||||
import Example from '@components/Example.astro';
|
||||
|
||||
## Overview
|
||||
|
||||
Add `data-bs-toggle="toggler"` to an element and put the class name in `data-bs-value`. Each click adds the class, and the next click removes it.
|
||||
|
||||
<Example centered>
|
||||
<button type="button" class="btn" data-bs-toggle="toggler" data-bs-value="btn-primary">Toggle btn-primary</button>
|
||||
</Example>
|
||||
|
||||
## Usage
|
||||
|
||||
### Remove an existing class
|
||||
|
||||
If the element already has the class, the first click removes it.
|
||||
|
||||
<Example centered>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="toggler" data-bs-value="btn-primary">Starts with btn-primary</button>
|
||||
</Example>
|
||||
|
||||
### Target element
|
||||
|
||||
Use `data-bs-target` to change another element. Put `data-bs-value` on the target, not on the button.
|
||||
|
||||
<Example>
|
||||
<button type="button" class="btn mb-3" data-bs-toggle="toggler" data-bs-target="#toggler-target">Toggle</button>
|
||||
<div id="toggler-target" class="p-3 rounded border" data-bs-value="bg-primary-lt">The button toggles bg-primary-lt here.</div>
|
||||
</Example>
|
||||
|
||||
### Multiple elements
|
||||
|
||||
A class selector in `data-bs-target` toggles every matching element. Each one can have its own value.
|
||||
|
||||
<Example>
|
||||
<button type="button" class="btn mb-3" data-bs-toggle="toggler" data-bs-target=".toggler-target">Toggle all</button>
|
||||
<div class="toggler-target p-3 mb-2 rounded border" data-bs-value="bg-blue-lt">Toggles bg-blue-lt</div>
|
||||
<div class="toggler-target p-3 rounded border" data-bs-value="bg-green-lt">Toggles bg-green-lt</div>
|
||||
</Example>
|
||||
|
||||
### Toggle an attribute
|
||||
|
||||
Set `data-bs-attribute` to toggle an attribute instead of a class. The attribute gets the value from `data-bs-value`, and the next click removes the attribute.
|
||||
|
||||
<Example>
|
||||
<button type="button" class="btn mb-3" data-bs-toggle="toggler" data-bs-target="#toggler-hidden">Show or hide</button>
|
||||
<div id="toggler-hidden" class="p-3 rounded border" data-bs-attribute="hidden" data-bs-value="true">Now you see me.</div>
|
||||
</Example>
|
||||
|
||||
You can use the same pattern to toggle `disabled` on a `fieldset` and lock all controls inside it. Every attribute works except `id`.
|
||||
|
||||
### Data attributes
|
||||
|
||||
| Attribute | Description |
|
||||
| --- | --- |
|
||||
| `data-bs-toggle="toggler"` | Turns the element into a trigger. |
|
||||
| `data-bs-target` | Optional CSS selector of the element to change. Without it, the trigger changes itself. |
|
||||
| `data-bs-attribute` | Attribute to toggle. Default is `class`. |
|
||||
| `data-bs-value` | Class name or attribute value to toggle. Without it, nothing happens. |
|
||||
|
||||
### JavaScript
|
||||
|
||||
Toggler is made to avoid writing JavaScript, but you can also use it from code.
|
||||
|
||||
```js
|
||||
const toggler = tabler.Toggler.getOrCreateInstance('#my-element', {
|
||||
attribute: 'class',
|
||||
value: 'active',
|
||||
})
|
||||
|
||||
toggler.toggle()
|
||||
```
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| `toggle()` | Toggles the class or the attribute. |
|
||||
| `dispose()` | Removes the instance from the element. |
|
||||
| `getInstance()` | Static. Returns the instance of an element, or `null`. |
|
||||
| `getOrCreateInstance()` | Static. Returns the instance of an element, or creates one. |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description |
|
||||
| --- | --- |
|
||||
| `toggle.bs.toggler` | Fires before the change. Call `event.preventDefault()` to stop it. |
|
||||
| `toggled.bs.toggler` | Fires after the change. |
|
||||
|
||||
```js
|
||||
document.getElementById('my-element').addEventListener('toggled.bs.toggler', () => {
|
||||
// do something
|
||||
})
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Use a `<button>` as the trigger, so it works with the keyboard.
|
||||
- Toggler does not change ARIA attributes. If the change shows or hides content, keep `aria-expanded` or `aria-pressed` in sync in a `toggled.bs.toggler` listener, or use [Collapse](/ui/components/accordion) instead.
|
||||
- A trigger with the `disabled` attribute or the `.disabled` class does nothing.
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import CardSubtitle from '@ui/CardSubtitle.astro'
|
||||
import DocsLink from '@ui/DocsLink.astro'
|
||||
|
||||
const colors = ['blue-lt', 'green-lt', 'orange-lt'] as const
|
||||
---
|
||||
|
||||
<DefaultLayout title="Toggler" pageMenu="base.toggler" description="Toggler switches a class or an attribute on click, with data attributes only and no custom JavaScript.">
|
||||
<DocsLink slot="page-header-actions" path="/ui/components/toggler" />
|
||||
|
||||
<div class="row row-cards">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Toggle a class</CardTitle>
|
||||
<CardSubtitle>The button toggles the class from <code>data-bs-value</code> on itself.</CardSubtitle>
|
||||
<button type="button" class="btn" data-bs-toggle="toggler" data-bs-value="btn-primary">Toggle <code>btn-primary</code></button>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Remove an existing class</CardTitle>
|
||||
<CardSubtitle>When the element already has the class, the first click removes it.</CardSubtitle>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="toggler" data-bs-value="btn-primary">Starts with <code>btn-primary</code></button>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Target element</CardTitle>
|
||||
<CardSubtitle>Point <code>data-bs-target</code> at another element. The value lives on the target.</CardSubtitle>
|
||||
<button type="button" class="btn mb-3" data-bs-toggle="toggler" data-bs-target="#toggler-target">Toggle</button>
|
||||
<div id="toggler-target" class="p-3 rounded border" data-bs-value="bg-primary-lt">The button toggles <code>bg-primary-lt</code> here.</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Multiple elements</CardTitle>
|
||||
<CardSubtitle>A class selector toggles every match, each with its own value.</CardSubtitle>
|
||||
<button type="button" class="btn mb-3" data-bs-toggle="toggler" data-bs-target=".toggler-target">Toggle all</button>
|
||||
<div class="space-y-2">
|
||||
{
|
||||
colors.map((color) => (
|
||||
<div class="toggler-target p-3 rounded border" data-bs-value={`bg-${color}`}>
|
||||
Toggles <code>bg-{color}</code>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Toggle an attribute</CardTitle>
|
||||
<CardSubtitle>Set <code>data-bs-attribute</code> to toggle an attribute instead, here <code>hidden</code>.</CardSubtitle>
|
||||
<button type="button" class="btn mb-3" data-bs-toggle="toggler" data-bs-target="#toggler-hidden">Show or hide</button>
|
||||
<div id="toggler-hidden" class="p-3 rounded border" data-bs-attribute="hidden" data-bs-value="true">Now you see me.</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Disable a form</CardTitle>
|
||||
<CardSubtitle>Toggle <code>disabled</code> on a <code>fieldset</code> to lock every control inside.</CardSubtitle>
|
||||
<button type="button" class="btn mb-3" data-bs-toggle="toggler" data-bs-target="#toggler-fieldset">Lock or unlock</button>
|
||||
<fieldset id="toggler-fieldset" data-bs-attribute="disabled" data-bs-value="disabled">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="toggler-name">Name</label>
|
||||
<input type="text" class="form-control" id="toggler-name" placeholder="Your name" />
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary">Save</button>
|
||||
</fieldset>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
@@ -326,6 +326,10 @@
|
||||
"title": "Toast",
|
||||
"url": "/ui/components/toast"
|
||||
},
|
||||
{
|
||||
"title": "Toggler",
|
||||
"url": "/ui/components/toggler"
|
||||
},
|
||||
{
|
||||
"title": "Tooltip",
|
||||
"url": "/ui/components/tooltip"
|
||||
|
||||
@@ -261,6 +261,10 @@
|
||||
"title": "Toasts",
|
||||
"url": "toasts.html"
|
||||
},
|
||||
"toggler": {
|
||||
"title": "Toggler",
|
||||
"url": "toggler.html"
|
||||
},
|
||||
"tracking": {
|
||||
"title": "Tracking",
|
||||
"url": "tracking.html"
|
||||
|
||||
Reference in new issue
Block a user