From 1f844d3b0326b4cbc64f20496ab8270c0177c9d9 Mon Sep 17 00:00:00 2001 From: "Sam L. D" Date: Thu, 29 Feb 2024 12:15:53 -0500 Subject: [PATCH 1/3] Exposed internal functions to initialize specific component elements and allowed the initialization functions to apply to any node. --- src/components/accordion/index.ts | 85 +++++---- src/components/carousel/index.ts | 113 ++++++------ src/components/clipboard/index.ts | 85 ++++----- src/components/collapse/index.ts | 73 ++++---- src/components/dial/index.ts | 60 +++--- src/components/dismiss/index.ts | 33 ++-- src/components/drawer/index.ts | 255 ++++++++++++++------------ src/components/dropdown/index.ts | 95 +++++----- src/components/index.ts | 29 +-- src/components/input-counter/index.ts | 75 ++++---- src/components/modal/index.ts | 227 ++++++++++++----------- src/components/popover/index.ts | 57 +++--- src/components/tabs/index.ts | 79 ++++---- src/components/tooltip/index.ts | 53 +++--- src/config/global.ts | 57 ++++-- src/dom/types.ts | 2 + 16 files changed, 722 insertions(+), 656 deletions(-) diff --git a/src/components/accordion/index.ts b/src/components/accordion/index.ts index 268bcb82d..774cbce41 100644 --- a/src/components/accordion/index.ts +++ b/src/components/accordion/index.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import type { AccordionItem, AccordionOptions } from './types'; -import type { InstanceOptions } from '../../dom/types'; +import type { InstanceOptions, RootElement } from '../../dom/types'; import { AccordionInterface } from './interface'; import instances from '../../dom/instances'; @@ -185,54 +185,53 @@ class Accordion implements AccordionInterface { } } -export function initAccordions() { - document.querySelectorAll('[data-accordion]').forEach(($accordionEl) => { - const alwaysOpen = $accordionEl.getAttribute('data-accordion'); - const activeClasses = $accordionEl.getAttribute('data-active-classes'); - const inactiveClasses = $accordionEl.getAttribute( - 'data-inactive-classes' - ); - - const items = [] as AccordionItem[]; - $accordionEl - .querySelectorAll('[data-accordion-target]') - .forEach(($triggerEl) => { - // Consider only items that directly belong to $accordionEl - // (to make nested accordions work). - if ($triggerEl.closest('[data-accordion]') === $accordionEl) { - const item = { - id: $triggerEl.getAttribute('data-accordion-target'), - triggerEl: $triggerEl, - targetEl: document.querySelector( - $triggerEl.getAttribute('data-accordion-target') - ), - iconEl: $triggerEl.querySelector( - '[data-accordion-icon]' - ), - active: - $triggerEl.getAttribute('aria-expanded') === 'true' - ? true - : false, - } as AccordionItem; - items.push(item); - } - }); +export function initAccordions($rootElement: RootElement = document) { + $rootElement + .querySelectorAll('[data-accordion]') + .forEach(initAccordionByElement); +} - new Accordion($accordionEl as HTMLElement, items, { - alwaysOpen: alwaysOpen === 'open' ? true : false, - activeClasses: activeClasses - ? activeClasses - : Default.activeClasses, - inactiveClasses: inactiveClasses - ? inactiveClasses - : Default.inactiveClasses, - } as AccordionOptions); - }); +export function initAccordionByElement($accordionEl: HTMLElement) { + const alwaysOpen = $accordionEl.getAttribute('data-accordion'); + const activeClasses = $accordionEl.getAttribute('data-active-classes'); + const inactiveClasses = $accordionEl.getAttribute('data-inactive-classes'); + + const items = [] as AccordionItem[]; + $accordionEl + .querySelectorAll('[data-accordion-target]') + .forEach(($triggerEl) => { + // Consider only items that directly belong to $accordionEl + // (to make nested accordions work). + if ($triggerEl.closest('[data-accordion]') === $accordionEl) { + const item = { + id: $triggerEl.getAttribute('data-accordion-target'), + triggerEl: $triggerEl, + targetEl: document.querySelector( + $triggerEl.getAttribute('data-accordion-target') + ), + iconEl: $triggerEl.querySelector('[data-accordion-icon]'), + active: + $triggerEl.getAttribute('aria-expanded') === 'true' + ? true + : false, + } as AccordionItem; + items.push(item); + } + }); + + new Accordion($accordionEl as HTMLElement, items, { + alwaysOpen: alwaysOpen === 'open' ? true : false, + activeClasses: activeClasses ? activeClasses : Default.activeClasses, + inactiveClasses: inactiveClasses + ? inactiveClasses + : Default.inactiveClasses, + } as AccordionOptions); } if (typeof window !== 'undefined') { window.Accordion = Accordion; window.initAccordions = initAccordions; + window.initAccordionByElement = initAccordionByElement; } export default Accordion; diff --git a/src/components/carousel/index.ts b/src/components/carousel/index.ts index eb0e14fd8..f1e43c65f 100644 --- a/src/components/carousel/index.ts +++ b/src/components/carousel/index.ts @@ -5,7 +5,7 @@ import type { IndicatorItem, RotationItems, } from './types'; -import type { InstanceOptions } from '../../dom/types'; +import type { InstanceOptions, RootElement } from '../../dom/types'; import { CarouselInterface } from './interface'; import instances from '../../dom/instances'; @@ -308,20 +308,22 @@ class Carousel implements CarouselInterface { } } -export function initCarousels() { - document.querySelectorAll('[data-carousel]').forEach(($carouselEl) => { - const interval = $carouselEl.getAttribute('data-carousel-interval'); - const slide = - $carouselEl.getAttribute('data-carousel') === 'slide' - ? true - : false; - - const items: CarouselItem[] = []; - let defaultPosition = 0; - if ($carouselEl.querySelectorAll('[data-carousel-item]').length) { - Array.from( - $carouselEl.querySelectorAll('[data-carousel-item]') - ).map(($carouselItemEl: HTMLElement, position: number) => { +export function initCarousels($rootElement: RootElement = document) { + $rootElement + .querySelectorAll('[data-carousel]') + .forEach(initCarouselByElement); +} + +export function initCarouselByElement($carouselEl: Element) { + const interval = $carouselEl.getAttribute('data-carousel-interval'); + const slide = + $carouselEl.getAttribute('data-carousel') === 'slide' ? true : false; + + const items: CarouselItem[] = []; + let defaultPosition = 0; + if ($carouselEl.querySelectorAll('[data-carousel-item]').length) { + Array.from($carouselEl.querySelectorAll('[data-carousel-item]')).map( + ($carouselItemEl: HTMLElement, position: number) => { items.push({ position: position, el: $carouselItemEl, @@ -333,60 +335,57 @@ export function initCarousels() { ) { defaultPosition = position; } - }); - } + } + ); + } - const indicators: IndicatorItem[] = []; - if ($carouselEl.querySelectorAll('[data-carousel-slide-to]').length) { - Array.from( - $carouselEl.querySelectorAll('[data-carousel-slide-to]') - ).map(($indicatorEl: HTMLElement) => { - indicators.push({ - position: parseInt( - $indicatorEl.getAttribute('data-carousel-slide-to') - ), - el: $indicatorEl, - }); + const indicators: IndicatorItem[] = []; + if ($carouselEl.querySelectorAll('[data-carousel-slide-to]').length) { + Array.from( + $carouselEl.querySelectorAll('[data-carousel-slide-to]') + ).map(($indicatorEl: HTMLElement) => { + indicators.push({ + position: parseInt( + $indicatorEl.getAttribute('data-carousel-slide-to') + ), + el: $indicatorEl, }); - } + }); + } - const carousel = new Carousel($carouselEl as HTMLElement, items, { - defaultPosition: defaultPosition, - indicators: { - items: indicators, - }, - interval: interval ? interval : Default.interval, - } as CarouselOptions); + const carousel = new Carousel($carouselEl as HTMLElement, items, { + defaultPosition: defaultPosition, + indicators: { + items: indicators, + }, + interval: interval ? interval : Default.interval, + } as CarouselOptions); - if (slide) { - carousel.cycle(); - } + if (slide) { + carousel.cycle(); + } - // check for controls - const carouselNextEl = $carouselEl.querySelector( - '[data-carousel-next]' - ); - const carouselPrevEl = $carouselEl.querySelector( - '[data-carousel-prev]' - ); + // check for controls + const carouselNextEl = $carouselEl.querySelector('[data-carousel-next]'); + const carouselPrevEl = $carouselEl.querySelector('[data-carousel-prev]'); - if (carouselNextEl) { - carouselNextEl.addEventListener('click', () => { - carousel.next(); - }); - } + if (carouselNextEl) { + carouselNextEl.addEventListener('click', () => { + carousel.next(); + }); + } - if (carouselPrevEl) { - carouselPrevEl.addEventListener('click', () => { - carousel.prev(); - }); - } - }); + if (carouselPrevEl) { + carouselPrevEl.addEventListener('click', () => { + carousel.prev(); + }); + } } if (typeof window !== 'undefined') { window.Carousel = Carousel; window.initCarousels = initCarousels; + window.initCarouselByElement = initCarouselByElement; } export default Carousel; diff --git a/src/components/clipboard/index.ts b/src/components/clipboard/index.ts index 1eadd3707..50b4a708f 100644 --- a/src/components/clipboard/index.ts +++ b/src/components/clipboard/index.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import type { CopyClipboardOptions } from './types'; -import type { InstanceOptions } from '../../dom/types'; +import type { InstanceOptions, RootElement } from '../../dom/types'; import { CopyClipboardInterface } from './interface'; import instances from '../../dom/instances'; @@ -140,54 +140,55 @@ class CopyClipboard implements CopyClipboardInterface { } } -export function initCopyClipboards() { - document +export function initCopyClipboards($rootElement: RootElement = document) { + $rootElement .querySelectorAll('[data-copy-to-clipboard-target]') - .forEach(($triggerEl) => { - const targetId = $triggerEl.getAttribute( - 'data-copy-to-clipboard-target' - ); - const $targetEl = document.getElementById(targetId); - const contentType = $triggerEl.getAttribute( - 'data-copy-to-clipboard-content-type' - ); - const htmlEntities = $triggerEl.getAttribute( - 'data-copy-to-clipboard-html-entities' - ); + .forEach(initCopyClipboardByElement); +} - // check if the target element exists - if ($targetEl) { - if ( - !instances.instanceExists( - 'CopyClipboard', - $targetEl.getAttribute('id') - ) - ) { - new CopyClipboard( - $triggerEl as HTMLElement, - $targetEl as HTMLInputElement, - { - htmlEntities: - htmlEntities && htmlEntities === 'true' - ? true - : Default.htmlEntities, - contentType: contentType - ? contentType - : Default.contentType, - } as CopyClipboardOptions - ); - } - } else { - console.error( - `The target element with id "${targetId}" does not exist. Please check the data-copy-to-clipboard-target attribute.` - ); - } - }); +export function initCopyClipboardByElement($triggerEl: Element) { + const targetId = $triggerEl.getAttribute('data-copy-to-clipboard-target'); + const $targetEl = document.getElementById(targetId); + const contentType = $triggerEl.getAttribute( + 'data-copy-to-clipboard-content-type' + ); + const htmlEntities = $triggerEl.getAttribute( + 'data-copy-to-clipboard-html-entities' + ); + + // check if the target element exists + if ($targetEl) { + if ( + !instances.instanceExists( + 'CopyClipboard', + $targetEl.getAttribute('id') + ) + ) { + new CopyClipboard( + $triggerEl as HTMLElement, + $targetEl as HTMLInputElement, + { + htmlEntities: + htmlEntities && htmlEntities === 'true' + ? true + : Default.htmlEntities, + contentType: contentType + ? contentType + : Default.contentType, + } as CopyClipboardOptions + ); + } + } else { + console.error( + `The target element with id "${targetId}" does not exist. Please check the data-copy-to-clipboard-target attribute.` + ); + } } if (typeof window !== 'undefined') { window.CopyClipboard = CopyClipboard; window.initClipboards = initCopyClipboards; + window.initCopyClipboardByElement = initCopyClipboardByElement; } export default CopyClipboard; diff --git a/src/components/collapse/index.ts b/src/components/collapse/index.ts index 72cdc98b1..68ac6d795 100644 --- a/src/components/collapse/index.ts +++ b/src/components/collapse/index.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import type { CollapseOptions } from './types'; -import type { InstanceOptions } from '../../dom/types'; +import type { InstanceOptions, RootElement } from '../../dom/types'; import { CollapseInterface } from './interface'; import instances from '../../dom/instances'; @@ -127,50 +127,47 @@ class Collapse implements CollapseInterface { } } -export function initCollapses() { - document +export function initCollapses($rootElement: RootElement = document) { + $rootElement .querySelectorAll('[data-collapse-toggle]') - .forEach(($triggerEl) => { - const targetId = $triggerEl.getAttribute('data-collapse-toggle'); - const $targetEl = document.getElementById(targetId); - - // check if the target element exists - if ($targetEl) { - if ( - !instances.instanceExists( - 'Collapse', - $targetEl.getAttribute('id') - ) - ) { - new Collapse( - $targetEl as HTMLElement, - $triggerEl as HTMLElement - ); - } else { - // if instance exists already for the same target element then create a new one with a different trigger element - new Collapse( - $targetEl as HTMLElement, - $triggerEl as HTMLElement, - {}, - { - id: - $targetEl.getAttribute('id') + - '_' + - instances._generateRandomId(), - } - ); + .forEach(initCollapseByElement); +} + +export function initCollapseByElement($triggerEl: Element) { + const targetId = $triggerEl.getAttribute('data-collapse-toggle'); + const $targetEl = document.getElementById(targetId); + + // check if the target element exists + if ($targetEl) { + if ( + !instances.instanceExists('Collapse', $targetEl.getAttribute('id')) + ) { + new Collapse($targetEl as HTMLElement, $triggerEl as HTMLElement); + } else { + // if instance exists already for the same target element then create a new one with a different trigger element + new Collapse( + $targetEl as HTMLElement, + $triggerEl as HTMLElement, + {}, + { + id: + $targetEl.getAttribute('id') + + '_' + + instances._generateRandomId(), } - } else { - console.error( - `The target element with id "${targetId}" does not exist. Please check the data-collapse-toggle attribute.` - ); - } - }); + ); + } + } else { + console.error( + `The target element with id "${targetId}" does not exist. Please check the data-collapse-toggle attribute.` + ); + } } if (typeof window !== 'undefined') { window.Collapse = Collapse; window.initCollapses = initCollapses; + window.initCollapseByElement = initCollapseByElement; } export default Collapse; diff --git a/src/components/dial/index.ts b/src/components/dial/index.ts index 1e2ff5ad7..9fac34156 100644 --- a/src/components/dial/index.ts +++ b/src/components/dial/index.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import type { DialOptions, DialTriggerType } from './types'; -import type { InstanceOptions } from '../../dom/types'; +import type { InstanceOptions, RootElement } from '../../dom/types'; import { DialInterface } from './interface'; import instances from '../../dom/instances'; @@ -184,43 +184,45 @@ class Dial implements DialInterface { } } -export function initDials() { - document.querySelectorAll('[data-dial-init]').forEach(($parentEl) => { - const $triggerEl = $parentEl.querySelector('[data-dial-toggle]'); - - if ($triggerEl) { - const dialId = $triggerEl.getAttribute('data-dial-toggle'); - const $dialEl = document.getElementById(dialId); - - if ($dialEl) { - const triggerType = - $triggerEl.getAttribute('data-dial-trigger'); - new Dial( - $parentEl as HTMLElement, - $triggerEl as HTMLElement, - $dialEl as HTMLElement, - { - triggerType: triggerType - ? triggerType - : Default.triggerType, - } as DialOptions - ); - } else { - console.error( - `Dial with id ${dialId} does not exist. Are you sure that the data-dial-toggle attribute points to the correct modal id?` - ); - } +export function initDials($rootElement: RootElement = document) { + $rootElement.querySelectorAll('[data-dial-init]').forEach(initDialByElement); +} + +export function initDialByElement($parentEl: Element) { + const $triggerEl = $parentEl.querySelector('[data-dial-toggle]'); + + if ($triggerEl) { + const dialId = $triggerEl.getAttribute('data-dial-toggle'); + const $dialEl = document.getElementById(dialId); + + if ($dialEl) { + const triggerType = $triggerEl.getAttribute('data-dial-trigger'); + new Dial( + $parentEl as HTMLElement, + $triggerEl as HTMLElement, + $dialEl as HTMLElement, + { + triggerType: triggerType + ? triggerType + : Default.triggerType, + } as DialOptions + ); } else { console.error( - `Dial with id ${$parentEl.id} does not have a trigger element. Are you sure that the data-dial-toggle attribute exists?` + `Dial with id ${dialId} does not exist. Are you sure that the data-dial-toggle attribute points to the correct modal id?` ); } - }); + } else { + console.error( + `Dial with id ${$parentEl.id} does not have a trigger element. Are you sure that the data-dial-toggle attribute exists?` + ); + } } if (typeof window !== 'undefined') { window.Dial = Dial; window.initDials = initDials; + window.initDialByElement = initDialByElement; } export default Dial; diff --git a/src/components/dismiss/index.ts b/src/components/dismiss/index.ts index f87246017..52f5bf606 100644 --- a/src/components/dismiss/index.ts +++ b/src/components/dismiss/index.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import type { DismissOptions } from './types'; -import type { InstanceOptions } from '../../dom/types'; +import type { InstanceOptions, RootElement } from '../../dom/types'; import { DismissInterface } from './interface'; import instances from '../../dom/instances'; @@ -92,24 +92,29 @@ class Dismiss implements DismissInterface { } } -export function initDismisses() { - document.querySelectorAll('[data-dismiss-target]').forEach(($triggerEl) => { - const targetId = $triggerEl.getAttribute('data-dismiss-target'); - const $dismissEl = document.querySelector(targetId); - - if ($dismissEl) { - new Dismiss($dismissEl as HTMLElement, $triggerEl as HTMLElement); - } else { - console.error( - `The dismiss element with id "${targetId}" does not exist. Please check the data-dismiss-target attribute.` - ); - } - }); +export function initDismisses($rootElement: RootElement = document) { + $rootElement + .querySelectorAll('[data-dismiss-target]') + .forEach(initDismissByElement); +} + +export function initDismissByElement($triggerEl: Element) { + const targetId = $triggerEl.getAttribute('data-dismiss-target'); + const $dismissEl = document.querySelector(targetId); + + if ($dismissEl) { + new Dismiss($dismissEl as HTMLElement, $triggerEl as HTMLElement); + } else { + console.error( + `The dismiss element with id "${targetId}" does not exist. Please check the data-dismiss-target attribute.` + ); + } } if (typeof window !== 'undefined') { window.Dismiss = Dismiss; window.initDismisses = initDismisses; + window.initDismissByElement = initDismissByElement; } export default Dismiss; diff --git a/src/components/drawer/index.ts b/src/components/drawer/index.ts index 74b397be3..ba439ab85 100644 --- a/src/components/drawer/index.ts +++ b/src/components/drawer/index.ts @@ -1,6 +1,10 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import type { DrawerOptions, PlacementClasses } from './types'; -import type { InstanceOptions, EventListenerInstance } from '../../dom/types'; +import type { + InstanceOptions, + EventListenerInstance, + RootElement, +} from '../../dom/types'; import { DrawerInterface } from './interface'; import instances from '../../dom/instances'; @@ -313,149 +317,164 @@ class Drawer implements DrawerInterface { } } -export function initDrawers() { - document.querySelectorAll('[data-drawer-target]').forEach(($triggerEl) => { - // mandatory - const drawerId = $triggerEl.getAttribute('data-drawer-target'); - const $drawerEl = document.getElementById(drawerId); +export function initDrawers($rootElement: RootElement = document) { + $rootElement + .querySelectorAll('[data-drawer-target]') + .forEach(initDrawerByElement); - if ($drawerEl) { - const placement = $triggerEl.getAttribute('data-drawer-placement'); - const bodyScrolling = $triggerEl.getAttribute( - 'data-drawer-body-scrolling' - ); - const backdrop = $triggerEl.getAttribute('data-drawer-backdrop'); - const edge = $triggerEl.getAttribute('data-drawer-edge'); - const edgeOffset = $triggerEl.getAttribute( - 'data-drawer-edge-offset' - ); + $rootElement + .querySelectorAll('[data-drawer-toggle]') + .forEach(initDrawerToggleByElement); + + $rootElement + .querySelectorAll('[data-drawer-dismiss], [data-drawer-hide]') + .forEach(initDrawerHideByElement); + + $rootElement + .querySelectorAll('[data-drawer-show]') + .forEach(initDrawerShowByElement); +} + +export function initDrawerByElement($triggerEl: Element) { + // mandatory + const drawerId = $triggerEl.getAttribute('data-drawer-target'); + const $drawerEl = document.getElementById(drawerId); + + if ($drawerEl) { + const placement = $triggerEl.getAttribute('data-drawer-placement'); + const bodyScrolling = $triggerEl.getAttribute( + 'data-drawer-body-scrolling' + ); + const backdrop = $triggerEl.getAttribute('data-drawer-backdrop'); + const edge = $triggerEl.getAttribute('data-drawer-edge'); + const edgeOffset = $triggerEl.getAttribute('data-drawer-edge-offset'); + + new Drawer($drawerEl, { + placement: placement ? placement : Default.placement, + bodyScrolling: bodyScrolling + ? bodyScrolling === 'true' + ? true + : false + : Default.bodyScrolling, + backdrop: backdrop + ? backdrop === 'true' + ? true + : false + : Default.backdrop, + edge: edge ? (edge === 'true' ? true : false) : Default.edge, + edgeOffset: edgeOffset ? edgeOffset : Default.edgeOffset, + } as DrawerOptions); + } else { + console.error( + `Drawer with id ${drawerId} not found. Are you sure that the data-drawer-target attribute points to the correct drawer id?` + ); + } +} + +export function initDrawerToggleByElement($triggerEl: Element) { + const drawerId = $triggerEl.getAttribute('data-drawer-toggle'); + const $drawerEl = document.getElementById(drawerId); + + if ($drawerEl) { + const drawer: DrawerInterface = instances.getInstance( + 'Drawer', + drawerId + ); - new Drawer($drawerEl, { - placement: placement ? placement : Default.placement, - bodyScrolling: bodyScrolling - ? bodyScrolling === 'true' - ? true - : false - : Default.bodyScrolling, - backdrop: backdrop - ? backdrop === 'true' - ? true - : false - : Default.backdrop, - edge: edge ? (edge === 'true' ? true : false) : Default.edge, - edgeOffset: edgeOffset ? edgeOffset : Default.edgeOffset, - } as DrawerOptions); + if (drawer) { + const toggleDrawer = () => { + drawer.toggle(); + }; + $triggerEl.addEventListener('click', toggleDrawer); + drawer.addEventListenerInstance( + $triggerEl as HTMLElement, + 'click', + toggleDrawer + ); } else { console.error( - `Drawer with id ${drawerId} not found. Are you sure that the data-drawer-target attribute points to the correct drawer id?` + `Drawer with id ${drawerId} has not been initialized. Please initialize it using the data-drawer-target attribute.` ); } - }); + } else { + console.error( + `Drawer with id ${drawerId} not found. Are you sure that the data-drawer-target attribute points to the correct drawer id?` + ); + } +} - document.querySelectorAll('[data-drawer-toggle]').forEach(($triggerEl) => { - const drawerId = $triggerEl.getAttribute('data-drawer-toggle'); - const $drawerEl = document.getElementById(drawerId); +export function initDrawerHideByElement($triggerEl: Element) { + const drawerId = $triggerEl.getAttribute('data-drawer-dismiss') + ? $triggerEl.getAttribute('data-drawer-dismiss') + : $triggerEl.getAttribute('data-drawer-hide'); + const $drawerEl = document.getElementById(drawerId); - if ($drawerEl) { - const drawer: DrawerInterface = instances.getInstance( - 'Drawer', - drawerId - ); + if ($drawerEl) { + const drawer: DrawerInterface = instances.getInstance( + 'Drawer', + drawerId + ); - if (drawer) { - const toggleDrawer = () => { - drawer.toggle(); - }; - $triggerEl.addEventListener('click', toggleDrawer); - drawer.addEventListenerInstance( - $triggerEl as HTMLElement, - 'click', - toggleDrawer - ); - } else { - console.error( - `Drawer with id ${drawerId} has not been initialized. Please initialize it using the data-drawer-target attribute.` - ); - } + if (drawer) { + const hideDrawer = () => { + drawer.hide(); + }; + $triggerEl.addEventListener('click', hideDrawer); + drawer.addEventListenerInstance( + $triggerEl as HTMLElement, + 'click', + hideDrawer + ); } else { console.error( - `Drawer with id ${drawerId} not found. Are you sure that the data-drawer-target attribute points to the correct drawer id?` + `Drawer with id ${drawerId} has not been initialized. Please initialize it using the data-drawer-target attribute.` ); } - }); + } else { + console.error( + `Drawer with id ${drawerId} not found. Are you sure that the data-drawer-target attribute points to the correct drawer id` + ); + } +} - document - .querySelectorAll('[data-drawer-dismiss], [data-drawer-hide]') - .forEach(($triggerEl) => { - const drawerId = $triggerEl.getAttribute('data-drawer-dismiss') - ? $triggerEl.getAttribute('data-drawer-dismiss') - : $triggerEl.getAttribute('data-drawer-hide'); - const $drawerEl = document.getElementById(drawerId); - - if ($drawerEl) { - const drawer: DrawerInterface = instances.getInstance( - 'Drawer', - drawerId - ); - - if (drawer) { - const hideDrawer = () => { - drawer.hide(); - }; - $triggerEl.addEventListener('click', hideDrawer); - drawer.addEventListenerInstance( - $triggerEl as HTMLElement, - 'click', - hideDrawer - ); - } else { - console.error( - `Drawer with id ${drawerId} has not been initialized. Please initialize it using the data-drawer-target attribute.` - ); - } - } else { - console.error( - `Drawer with id ${drawerId} not found. Are you sure that the data-drawer-target attribute points to the correct drawer id` - ); - } - }); +export function initDrawerShowByElement($triggerEl: Element) { + const drawerId = $triggerEl.getAttribute('data-drawer-show'); + const $drawerEl = document.getElementById(drawerId); - document.querySelectorAll('[data-drawer-show]').forEach(($triggerEl) => { - const drawerId = $triggerEl.getAttribute('data-drawer-show'); - const $drawerEl = document.getElementById(drawerId); + if ($drawerEl) { + const drawer: DrawerInterface = instances.getInstance( + 'Drawer', + drawerId + ); - if ($drawerEl) { - const drawer: DrawerInterface = instances.getInstance( - 'Drawer', - drawerId + if (drawer) { + const showDrawer = () => { + drawer.show(); + }; + $triggerEl.addEventListener('click', showDrawer); + drawer.addEventListenerInstance( + $triggerEl as HTMLElement, + 'click', + showDrawer ); - - if (drawer) { - const showDrawer = () => { - drawer.show(); - }; - $triggerEl.addEventListener('click', showDrawer); - drawer.addEventListenerInstance( - $triggerEl as HTMLElement, - 'click', - showDrawer - ); - } else { - console.error( - `Drawer with id ${drawerId} has not been initialized. Please initialize it using the data-drawer-target attribute.` - ); - } } else { console.error( - `Drawer with id ${drawerId} not found. Are you sure that the data-drawer-target attribute points to the correct drawer id?` + `Drawer with id ${drawerId} has not been initialized. Please initialize it using the data-drawer-target attribute.` ); } - }); + } else { + console.error( + `Drawer with id ${drawerId} not found. Are you sure that the data-drawer-target attribute points to the correct drawer id?` + ); + } } if (typeof window !== 'undefined') { window.Drawer = Drawer; window.initDrawers = initDrawers; + window.initDrawerByElement = initDrawerByElement; + window.initDrawerToggleByElement = initDrawerToggleByElement; + window.initDrawerShowByElement = initDrawerShowByElement; } export default Drawer; diff --git a/src/components/dropdown/index.ts b/src/components/dropdown/index.ts index a4dec09ed..ebcb76ea5 100644 --- a/src/components/dropdown/index.ts +++ b/src/components/dropdown/index.ts @@ -5,7 +5,7 @@ import type { Instance as PopperInstance, } from '@popperjs/core'; import type { DropdownOptions } from './types'; -import type { InstanceOptions } from '../../dom/types'; +import type { InstanceOptions, RootElement } from '../../dom/types'; import { DropdownInterface } from './interface'; import instances from '../../dom/instances'; @@ -331,62 +331,59 @@ class Dropdown implements DropdownInterface { } } -export function initDropdowns() { - document +export function initDropdowns($rootElement: RootElement = document) { + $rootElement .querySelectorAll('[data-dropdown-toggle]') - .forEach(($triggerEl) => { - const dropdownId = $triggerEl.getAttribute('data-dropdown-toggle'); - const $dropdownEl = document.getElementById(dropdownId); + .forEach(initDropdownByElement); +} - if ($dropdownEl) { - const placement = $triggerEl.getAttribute( - 'data-dropdown-placement' - ); - const offsetSkidding = $triggerEl.getAttribute( - 'data-dropdown-offset-skidding' - ); - const offsetDistance = $triggerEl.getAttribute( - 'data-dropdown-offset-distance' - ); - const triggerType = $triggerEl.getAttribute( - 'data-dropdown-trigger' - ); - const delay = $triggerEl.getAttribute('data-dropdown-delay'); - const ignoreClickOutsideClass = $triggerEl.getAttribute( - 'data-dropdown-ignore-click-outside-class' - ); +export function initDropdownByElement($triggerEl: Element) { + const dropdownId = $triggerEl.getAttribute('data-dropdown-toggle'); + const $dropdownEl = document.getElementById(dropdownId); - new Dropdown( - $dropdownEl as HTMLElement, - $triggerEl as HTMLElement, - { - placement: placement ? placement : Default.placement, - triggerType: triggerType - ? triggerType - : Default.triggerType, - offsetSkidding: offsetSkidding - ? parseInt(offsetSkidding) - : Default.offsetSkidding, - offsetDistance: offsetDistance - ? parseInt(offsetDistance) - : Default.offsetDistance, - delay: delay ? parseInt(delay) : Default.delay, - ignoreClickOutsideClass: ignoreClickOutsideClass - ? ignoreClickOutsideClass - : Default.ignoreClickOutsideClass, - } as DropdownOptions - ); - } else { - console.error( - `The dropdown element with id "${dropdownId}" does not exist. Please check the data-dropdown-toggle attribute.` - ); - } - }); + if ($dropdownEl) { + const placement = $triggerEl.getAttribute('data-dropdown-placement'); + const offsetSkidding = $triggerEl.getAttribute( + 'data-dropdown-offset-skidding' + ); + const offsetDistance = $triggerEl.getAttribute( + 'data-dropdown-offset-distance' + ); + const triggerType = $triggerEl.getAttribute('data-dropdown-trigger'); + const delay = $triggerEl.getAttribute('data-dropdown-delay'); + const ignoreClickOutsideClass = $triggerEl.getAttribute( + 'data-dropdown-ignore-click-outside-class' + ); + + new Dropdown( + $dropdownEl as HTMLElement, + $triggerEl as HTMLElement, + { + placement: placement ? placement : Default.placement, + triggerType: triggerType ? triggerType : Default.triggerType, + offsetSkidding: offsetSkidding + ? parseInt(offsetSkidding) + : Default.offsetSkidding, + offsetDistance: offsetDistance + ? parseInt(offsetDistance) + : Default.offsetDistance, + delay: delay ? parseInt(delay) : Default.delay, + ignoreClickOutsideClass: ignoreClickOutsideClass + ? ignoreClickOutsideClass + : Default.ignoreClickOutsideClass, + } as DropdownOptions + ); + } else { + console.error( + `The dropdown element with id "${dropdownId}" does not exist. Please check the data-dropdown-toggle attribute.` + ); + } } if (typeof window !== 'undefined') { window.Dropdown = Dropdown; window.initDropdowns = initDropdowns; + window.initDropdownByElement = initDropdownByElement; } export default Dropdown; diff --git a/src/components/index.ts b/src/components/index.ts index a4d438c48..05e66275d 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -11,21 +11,22 @@ import { initModals } from './modal'; import { initPopovers } from './popover'; import { initTabs } from './tabs'; import { initTooltips } from './tooltip'; +import { RootElement } from '../dom/types'; -export function initFlowbite() { - initAccordions(); - initCollapses(); - initCarousels(); - initDismisses(); - initDropdowns(); - initModals(); - initDrawers(); - initTabs(); - initTooltips(); - initPopovers(); - initDials(); - initInputCounters(); - initCopyClipboards(); +export function initFlowbite($rootElement: RootElement = document) { + initAccordions($rootElement); + initCollapses($rootElement); + initCarousels($rootElement); + initDismisses($rootElement); + initDropdowns($rootElement); + initModals($rootElement); + initDrawers($rootElement); + initTabs($rootElement); + initTooltips($rootElement); + initPopovers($rootElement); + initDials($rootElement); + initInputCounters($rootElement); + initCopyClipboards($rootElement); } if (typeof window !== 'undefined') { diff --git a/src/components/input-counter/index.ts b/src/components/input-counter/index.ts index e6715cd49..3652212f9 100644 --- a/src/components/input-counter/index.ts +++ b/src/components/input-counter/index.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import type { InputCounterOptions } from './types'; -import type { InstanceOptions } from '../../dom/types'; +import type { InstanceOptions, RootElement } from '../../dom/types'; import { InputCounterInterface } from './interface'; import instances from '../../dom/instances'; @@ -180,50 +180,55 @@ class InputCounter implements InputCounterInterface { } } -export function initInputCounters() { - document.querySelectorAll('[data-input-counter]').forEach(($targetEl) => { - const targetId = $targetEl.id; +export function initInputCounters($rootElement: RootElement = document) { + $rootElement + .querySelectorAll('[data-input-counter]') + .forEach(initInputCounterByElement); +} - const $incrementEl = document.querySelector( - '[data-input-counter-increment="' + targetId + '"]' - ); +export function initInputCounterByElement($targetEl: Element) { + const targetId = $targetEl.id; - const $decrementEl = document.querySelector( - '[data-input-counter-decrement="' + targetId + '"]' - ); + const $incrementEl = document.querySelector( + '[data-input-counter-increment="' + targetId + '"]' + ); - const minValue = $targetEl.getAttribute('data-input-counter-min'); - const maxValue = $targetEl.getAttribute('data-input-counter-max'); - - // check if the target element exists - if ($targetEl) { - if ( - !instances.instanceExists( - 'InputCounter', - $targetEl.getAttribute('id') - ) - ) { - new InputCounter( - $targetEl as HTMLInputElement, - $incrementEl ? ($incrementEl as HTMLElement) : null, - $decrementEl ? ($decrementEl as HTMLElement) : null, - { - minValue: minValue ? parseInt(minValue) : null, - maxValue: maxValue ? parseInt(maxValue) : null, - } as InputCounterOptions - ); - } - } else { - console.error( - `The target element with id "${targetId}" does not exist. Please check the data-input-counter attribute.` + const $decrementEl = document.querySelector( + '[data-input-counter-decrement="' + targetId + '"]' + ); + + const minValue = $targetEl.getAttribute('data-input-counter-min'); + const maxValue = $targetEl.getAttribute('data-input-counter-max'); + + // check if the target element exists + if ($targetEl) { + if ( + !instances.instanceExists( + 'InputCounter', + $targetEl.getAttribute('id') + ) + ) { + new InputCounter( + $targetEl as HTMLInputElement, + $incrementEl ? ($incrementEl as HTMLElement) : null, + $decrementEl ? ($decrementEl as HTMLElement) : null, + { + minValue: minValue ? parseInt(minValue) : null, + maxValue: maxValue ? parseInt(maxValue) : null, + } as InputCounterOptions ); } - }); + } else { + console.error( + `The target element with id "${targetId}" does not exist. Please check the data-input-counter attribute.` + ); + } } if (typeof window !== 'undefined') { window.InputCounter = InputCounter; window.initInputCounters = initInputCounters; + window.initInputCounterByElement = initInputCounterByElement; } export default InputCounter; diff --git a/src/components/modal/index.ts b/src/components/modal/index.ts index d6096a1a2..d85782c00 100644 --- a/src/components/modal/index.ts +++ b/src/components/modal/index.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import type { ModalOptions } from './types'; -import type { InstanceOptions, EventListenerInstance } from '../../dom/types'; +import type { InstanceOptions, EventListenerInstance, RootElement } from "../../dom/types"; import { ModalInterface } from './interface'; import instances from '../../dom/instances'; @@ -278,132 +278,145 @@ class Modal implements ModalInterface { } } -export function initModals() { +export function initModals( + $rootElement: RootElement = document +) { // initiate modal based on data-modal-target - document.querySelectorAll('[data-modal-target]').forEach(($triggerEl) => { - const modalId = $triggerEl.getAttribute('data-modal-target'); - const $modalEl = document.getElementById(modalId); - - if ($modalEl) { - const placement = $modalEl.getAttribute('data-modal-placement'); - const backdrop = $modalEl.getAttribute('data-modal-backdrop'); - new Modal( - $modalEl as HTMLElement, - { - placement: placement ? placement : Default.placement, - backdrop: backdrop ? backdrop : Default.backdrop, - } as ModalOptions - ); - } else { - console.error( - `Modal with id ${modalId} does not exist. Are you sure that the data-modal-target attribute points to the correct modal id?.` - ); - } - }); + $rootElement + .querySelectorAll('[data-modal-target]') + .forEach(initModalByElement); // toggle modal visibility - document.querySelectorAll('[data-modal-toggle]').forEach(($triggerEl) => { - const modalId = $triggerEl.getAttribute('data-modal-toggle'); - const $modalEl = document.getElementById(modalId); - - if ($modalEl) { - const modal: ModalInterface = instances.getInstance( - 'Modal', - modalId - ); + $rootElement + .querySelectorAll('[data-modal-toggle]') + .forEach(initModalToggleTriggerByElement); - if (modal) { - const toggleModal = () => { - modal.toggle(); - }; - $triggerEl.addEventListener('click', toggleModal); - modal.addEventListenerInstance( - $triggerEl as HTMLElement, - 'click', - toggleModal - ); - } else { - console.error( - `Modal with id ${modalId} has not been initialized. Please initialize it using the data-modal-target attribute.` - ); - } + // show modal on click if exists based on id + $rootElement + .querySelectorAll('[data-modal-show]') + .forEach(initModalShowTriggerByElement); + + // hide modal on click if exists based on id + $rootElement + .querySelectorAll('[data-modal-hide]') + .forEach(initModalHideTriggerByElement); +} + +if (typeof window !== 'undefined') { + window.Modal = Modal; + window.initModals = initModals; + window.initModalByElement = initModalByElement; + window.initModalToggleTriggerByElement = initModalToggleTriggerByElement; + window.initModalShowTriggerByElement = initModalShowTriggerByElement; + window.initModalHideTriggerByElement = initModalHideTriggerByElement; +} + +export function initModalByElement($triggerEl: Element) { + const modalId = $triggerEl.getAttribute('data-modal-target'); + const $modalEl = document.getElementById(modalId); + + if ($modalEl) { + const placement = $modalEl.getAttribute('data-modal-placement'); + const backdrop = $modalEl.getAttribute('data-modal-backdrop'); + new Modal( + $modalEl as HTMLElement, + { + placement: placement ? placement : Default.placement, + backdrop: backdrop ? backdrop : Default.backdrop, + } as ModalOptions + ); + } else { + console.error( + `Modal with id ${modalId} does not exist. Are you sure that the data-modal-target attribute points to the correct modal id?.` + ); + } +} + +export function initModalToggleTriggerByElement($triggerEl: Element) { + const modalId = $triggerEl.getAttribute('data-modal-toggle'); + const $modalEl = document.getElementById(modalId); + + if ($modalEl) { + const modal: ModalInterface = instances.getInstance('Modal', modalId); + + if (modal) { + const toggleModal = () => { + modal.toggle(); + }; + $triggerEl.addEventListener('click', toggleModal); + modal.addEventListenerInstance( + $triggerEl as HTMLElement, + 'click', + toggleModal + ); } else { console.error( - `Modal with id ${modalId} does not exist. Are you sure that the data-modal-toggle attribute points to the correct modal id?` + `Modal with id ${modalId} has not been initialized. Please initialize it using the data-modal-target attribute.` ); } - }); + } else { + console.error( + `Modal with id ${modalId} does not exist. Are you sure that the data-modal-toggle attribute points to the correct modal id?` + ); + } +} - // show modal on click if exists based on id - document.querySelectorAll('[data-modal-show]').forEach(($triggerEl) => { - const modalId = $triggerEl.getAttribute('data-modal-show'); - const $modalEl = document.getElementById(modalId); - - if ($modalEl) { - const modal: ModalInterface = instances.getInstance( - 'Modal', - modalId - ); +export function initModalShowTriggerByElement($triggerEl: Element) { + const modalId = $triggerEl.getAttribute('data-modal-show'); + const $modalEl = document.getElementById(modalId); - if (modal) { - const showModal = () => { - modal.show(); - }; - $triggerEl.addEventListener('click', showModal); - modal.addEventListenerInstance( - $triggerEl as HTMLElement, - 'click', - showModal - ); - } else { - console.error( - `Modal with id ${modalId} has not been initialized. Please initialize it using the data-modal-target attribute.` - ); - } + if ($modalEl) { + const modal: ModalInterface = instances.getInstance('Modal', modalId); + + if (modal) { + const showModal = () => { + modal.show(); + }; + $triggerEl.addEventListener('click', showModal); + modal.addEventListenerInstance( + $triggerEl as HTMLElement, + 'click', + showModal + ); } else { console.error( - `Modal with id ${modalId} does not exist. Are you sure that the data-modal-show attribute points to the correct modal id?` + `Modal with id ${modalId} has not been initialized. Please initialize it using the data-modal-target attribute.` ); } - }); + } else { + console.error( + `Modal with id ${modalId} does not exist. Are you sure that the data-modal-show attribute points to the correct modal id?` + ); + } +} - // hide modal on click if exists based on id - document.querySelectorAll('[data-modal-hide]').forEach(($triggerEl) => { - const modalId = $triggerEl.getAttribute('data-modal-hide'); - const $modalEl = document.getElementById(modalId); - - if ($modalEl) { - const modal: ModalInterface = instances.getInstance( - 'Modal', - modalId - ); +export function initModalHideTriggerByElement($triggerEl: Element) { + const modalId = $triggerEl.getAttribute('data-modal-hide'); + const $modalEl = document.getElementById(modalId); - if (modal) { - const hideModal = () => { - modal.hide(); - }; - $triggerEl.addEventListener('click', hideModal); - modal.addEventListenerInstance( - $triggerEl as HTMLElement, - 'click', - hideModal - ); - } else { - console.error( - `Modal with id ${modalId} has not been initialized. Please initialize it using the data-modal-target attribute.` - ); - } + if ($modalEl) { + const modal: ModalInterface = instances.getInstance('Modal', modalId); + + if (modal) { + const hideModal = () => { + modal.hide(); + }; + $triggerEl.addEventListener('click', hideModal); + modal.addEventListenerInstance( + $triggerEl as HTMLElement, + 'click', + hideModal + ); } else { console.error( - `Modal with id ${modalId} does not exist. Are you sure that the data-modal-hide attribute points to the correct modal id?` + `Modal with id ${modalId} has not been initialized. Please initialize it using the data-modal-target attribute.` ); } - }); -} - -if (typeof window !== 'undefined') { - window.Modal = Modal; - window.initModals = initModals; + } else { + console.error( + `Modal with id ${modalId} does not exist. Are you sure that the data-modal-hide attribute points to the correct modal id?` + ); + } } export default Modal; diff --git a/src/components/popover/index.ts b/src/components/popover/index.ts index d696bdab1..7370b5ee9 100644 --- a/src/components/popover/index.ts +++ b/src/components/popover/index.ts @@ -5,7 +5,7 @@ import type { Instance as PopperInstance, } from '@popperjs/core'; import type { PopoverOptions } from './types'; -import type { InstanceOptions } from '../../dom/types'; +import type { InstanceOptions, RootElement } from '../../dom/types'; import { PopoverInterface } from './interface'; import instances from '../../dom/instances'; @@ -305,38 +305,41 @@ class Popover implements PopoverInterface { } } -export function initPopovers() { - document.querySelectorAll('[data-popover-target]').forEach(($triggerEl) => { - const popoverID = $triggerEl.getAttribute('data-popover-target'); - const $popoverEl = document.getElementById(popoverID); - - if ($popoverEl) { - const triggerType = $triggerEl.getAttribute('data-popover-trigger'); - const placement = $triggerEl.getAttribute('data-popover-placement'); - const offset = $triggerEl.getAttribute('data-popover-offset'); +export function initPopovers($rootElement: RootElement = document) { + $rootElement + .querySelectorAll('[data-popover-target]') + .forEach(initPopoverByElement); +} - new Popover( - $popoverEl as HTMLElement, - $triggerEl as HTMLElement, - { - placement: placement ? placement : Default.placement, - offset: offset ? parseInt(offset) : Default.offset, - triggerType: triggerType - ? triggerType - : Default.triggerType, - } as PopoverOptions - ); - } else { - console.error( - `The popover element with id "${popoverID}" does not exist. Please check the data-popover-target attribute.` - ); - } - }); +export function initPopoverByElement($triggerEl: Element) { + const popoverID = $triggerEl.getAttribute('data-popover-target'); + const $popoverEl = document.getElementById(popoverID); + + if ($popoverEl) { + const triggerType = $triggerEl.getAttribute('data-popover-trigger'); + const placement = $triggerEl.getAttribute('data-popover-placement'); + const offset = $triggerEl.getAttribute('data-popover-offset'); + + new Popover( + $popoverEl as HTMLElement, + $triggerEl as HTMLElement, + { + placement: placement ? placement : Default.placement, + offset: offset ? parseInt(offset) : Default.offset, + triggerType: triggerType ? triggerType : Default.triggerType, + } as PopoverOptions + ); + } else { + console.error( + `The popover element with id "${popoverID}" does not exist. Please check the data-popover-target attribute.` + ); + } } if (typeof window !== 'undefined') { window.Popover = Popover; window.initPopovers = initPopovers; + window.initPopoverByElement = initPopoverByElement; } export default Popover; diff --git a/src/components/tabs/index.ts b/src/components/tabs/index.ts index 6d5504eed..6569b98cd 100644 --- a/src/components/tabs/index.ts +++ b/src/components/tabs/index.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import type { TabItem, TabsOptions } from './types'; -import type { InstanceOptions } from '../../dom/types'; +import type { InstanceOptions, RootElement } from '../../dom/types'; import { TabsInterface } from './interface'; import instances from '../../dom/instances'; @@ -137,50 +137,51 @@ class Tabs implements TabsInterface { } } -export function initTabs() { - document.querySelectorAll('[data-tabs-toggle]').forEach(($parentEl) => { - const tabItems: TabItem[] = []; - const activeClasses = $parentEl.getAttribute( - 'data-tabs-active-classes' - ); - const inactiveClasses = $parentEl.getAttribute( - 'data-tabs-inactive-classes' - ); - let defaultTabId = null; - $parentEl - .querySelectorAll('[role="tab"]') - .forEach(($triggerEl: HTMLElement) => { - const isActive = - $triggerEl.getAttribute('aria-selected') === 'true'; - const tab: TabItem = { - id: $triggerEl.getAttribute('data-tabs-target'), - triggerEl: $triggerEl, - targetEl: document.querySelector( - $triggerEl.getAttribute('data-tabs-target') - ), - }; - tabItems.push(tab); - - if (isActive) { - defaultTabId = tab.id; - } - }); +export function initTabs($rootElement: RootElement = document) { + $rootElement + .querySelectorAll('[data-tabs-toggle]') + .forEach(initTabByElement); +} + +export function initTabByElement($parentEl: Element) { + const tabItems: TabItem[] = []; + const activeClasses = $parentEl.getAttribute('data-tabs-active-classes'); + const inactiveClasses = $parentEl.getAttribute( + 'data-tabs-inactive-classes' + ); + let defaultTabId = null; + $parentEl + .querySelectorAll('[role="tab"]') + .forEach(($triggerEl: HTMLElement) => { + const isActive = + $triggerEl.getAttribute('aria-selected') === 'true'; + const tab: TabItem = { + id: $triggerEl.getAttribute('data-tabs-target'), + triggerEl: $triggerEl, + targetEl: document.querySelector( + $triggerEl.getAttribute('data-tabs-target') + ), + }; + tabItems.push(tab); + + if (isActive) { + defaultTabId = tab.id; + } + }); - new Tabs($parentEl as HTMLElement, tabItems, { - defaultTabId: defaultTabId, - activeClasses: activeClasses - ? activeClasses - : Default.activeClasses, - inactiveClasses: inactiveClasses - ? inactiveClasses - : Default.inactiveClasses, - } as TabsOptions); - }); + new Tabs($parentEl as HTMLElement, tabItems, { + defaultTabId: defaultTabId, + activeClasses: activeClasses ? activeClasses : Default.activeClasses, + inactiveClasses: inactiveClasses + ? inactiveClasses + : Default.inactiveClasses, + } as TabsOptions); } if (typeof window !== 'undefined') { window.Tabs = Tabs; window.initTabs = initTabs; + window.initTabByElement = initTabByElement; } export default Tabs; diff --git a/src/components/tooltip/index.ts b/src/components/tooltip/index.ts index 52b0d62d5..df62658ae 100644 --- a/src/components/tooltip/index.ts +++ b/src/components/tooltip/index.ts @@ -5,7 +5,7 @@ import type { Instance as PopperInstance, } from '@popperjs/core'; import type { TooltipOptions } from './types'; -import type { InstanceOptions } from '../../dom/types'; +import type { InstanceOptions, RootElement } from '../../dom/types'; import { TooltipInterface } from './interface'; import instances from '../../dom/instances'; @@ -294,36 +294,39 @@ class Tooltip implements TooltipInterface { } } -export function initTooltips() { - document.querySelectorAll('[data-tooltip-target]').forEach(($triggerEl) => { - const tooltipId = $triggerEl.getAttribute('data-tooltip-target'); - const $tooltipEl = document.getElementById(tooltipId); - - if ($tooltipEl) { - const triggerType = $triggerEl.getAttribute('data-tooltip-trigger'); - const placement = $triggerEl.getAttribute('data-tooltip-placement'); +export function initTooltips($rootElement: RootElement = document) { + $rootElement + .querySelectorAll('[data-tooltip-target]') + .forEach(initTooltipByElement); +} - new Tooltip( - $tooltipEl as HTMLElement, - $triggerEl as HTMLElement, - { - placement: placement ? placement : Default.placement, - triggerType: triggerType - ? triggerType - : Default.triggerType, - } as TooltipOptions - ); - } else { - console.error( - `The tooltip element with id "${tooltipId}" does not exist. Please check the data-tooltip-target attribute.` - ); - } - }); +export function initTooltipByElement($triggerEl: Element) { + const tooltipId = $triggerEl.getAttribute('data-tooltip-target'); + const $tooltipEl = document.getElementById(tooltipId); + + if ($tooltipEl) { + const triggerType = $triggerEl.getAttribute('data-tooltip-trigger'); + const placement = $triggerEl.getAttribute('data-tooltip-placement'); + + new Tooltip( + $tooltipEl as HTMLElement, + $triggerEl as HTMLElement, + { + placement: placement ? placement : Default.placement, + triggerType: triggerType ? triggerType : Default.triggerType, + } as TooltipOptions + ); + } else { + console.error( + `The tooltip element with id "${tooltipId}" does not exist. Please check the data-tooltip-target attribute.` + ); + } } if (typeof window !== 'undefined') { window.Tooltip = Tooltip; window.initTooltips = initTooltips; + window.initTooltipByElement = initTooltipByElement; } export default Tooltip; diff --git a/src/config/global.ts b/src/config/global.ts index e34e134fe..578baf9db 100644 --- a/src/config/global.ts +++ b/src/config/global.ts @@ -5,12 +5,13 @@ import Dial from '../components/dial'; import Dismiss from '../components/dismiss'; import Drawer from '../components/drawer'; import Dropdown from '../components/dropdown'; -import Modal from '../components/modal'; -import Popover from '../components/popover'; -import Tabs from '../components/tabs'; -import Tooltip from '../components/tooltip'; -import InputCounter from '../components/input-counter'; +import Modal, { initModalToggleTriggerByElement } from '../components/modal'; +import Popover, { initPopoverByElement } from "../components/popover"; +import Tabs, { initTabByElement } from "../components/tabs"; +import Tooltip, { initTooltipByElement } from "../components/tooltip"; +import InputCounter, { initInputCounterByElement } from "../components/input-counter"; import Clipboard from '../components/clipboard'; +import { RootElement } from '../dom/types'; declare global { interface Window { @@ -27,20 +28,38 @@ declare global { Tooltip: typeof Tooltip; InputCounter: typeof InputCounter; CopyClipboard: typeof Clipboard; - initAccordions: () => void; - initCarousels: () => void; - initCollapses: () => void; - initDials: () => void; - initDismisses: () => void; - initDrawers: () => void; - initDropdowns: () => void; - initModals: () => void; - initPopovers: () => void; - initTabs: () => void; - initTooltips: () => void; - initInputCounters: () => void; - initClipboards: () => void; - initFlowbite: () => void; + initAccordions: ($rootElement?: RootElement) => void; + initAccordionByElement: ($accordionEl: HTMLElement) => void; + initCarousels: ($rootElement?: RootElement) => void; + initCarouselByElement: ($carouselEl: Element) => void; + initCollapses: ($rootElement?: RootElement) => void; + initCollapseByElement: ($triggerEl: Element) => void; + initDials: ($rootElement?: RootElement) => void; + initDialByElement: ($parentEl: Element) => void; + initDismisses: ($rootElement?: RootElement) => void; + initDismissByElement: ($triggerEl: Element) => void; + initDrawers: ($rootElement?: RootElement) => void; + initDrawerByElement: ($triggerEl: Element) => void; + initDrawerToggleByElement: ($triggerEl: Element) => void; + initDrawerShowByElement: ($triggerEl: Element) => void; + initDropdowns: ($rootElement?: RootElement) => void; + initDropdownByElement: ($triggerEl: Element) => void; + initModals: ($rootElement?: RootElement) => void; + initModalByElement: ($triggerEl: Element) => void; + initModalToggleTriggerByElement: ($triggerEl: Element) => void; + initModalShowTriggerByElement: ($triggerEl: Element) => void; + initModalHideTriggerByElement: ($triggerEl: Element) => void; + initPopovers: ($rootElement?: RootElement) => void; + initPopoverByElement: ($triggerEl: Element) => void; + initTabs: ($rootElement?: RootElement) => void; + initTabByElement: ($parentEl: Element) => void; + initTooltips: ($rootElement?: RootElement) => void; + initTooltipByElement: ($triggerEl: Element) => void; + initInputCounters: ($rootElement?: RootElement) => void; + initInputCounterByElement: ($targetEl: Element) => void; + initClipboards: ($rootElement?: RootElement) => void; + initCopyClipboardByElement: ($triggerEl: Element) => void; + initFlowbite: ($rootElement?: RootElement) => void; FlowbiteInstances: any; } } diff --git a/src/dom/types.ts b/src/dom/types.ts index fde01b29e..61f959c3d 100644 --- a/src/dom/types.ts +++ b/src/dom/types.ts @@ -8,3 +8,5 @@ export declare type EventListenerInstance = { type: string; handler: EventListenerOrEventListenerObject; }; + +export declare type RootElement = Element | HTMLElement | Document; From e02118e74de69247a41679b6550901b3a231257d Mon Sep 17 00:00:00 2001 From: "Sam L. D" Date: Thu, 29 Feb 2024 13:47:39 -0500 Subject: [PATCH 2/3] Adjusted the events. --- src/index.phoenix.ts | 52 ++++++++++++++++++++++---------------------- src/index.ts | 26 +++++++++++----------- src/index.turbo.ts | 10 +++++---- src/index.umd.ts | 26 +++++++++++----------- 4 files changed, 58 insertions(+), 56 deletions(-) diff --git a/src/index.phoenix.ts b/src/index.phoenix.ts index 4ed466756..73db2880d 100644 --- a/src/index.phoenix.ts +++ b/src/index.phoenix.ts @@ -16,36 +16,36 @@ import './components/index'; import Events from './dom/events'; const liveViewLoadEvents = new Events('phx:page-loading-stop', [ - initAccordions, - initCollapses, - initCarousels, - initDismisses, - initDropdowns, - initModals, - initDrawers, - initTabs, - initTooltips, - initPopovers, - initDials, - initInputCounters, - initCopyClipboards, + () => initAccordions(), + () => initCollapses(), + () => initCarousels(), + () => initDismisses(), + () => initDropdowns(), + () => initModals(), + () => initDrawers(), + () => initTabs(), + () => initTooltips(), + () => initPopovers(), + () => initDials(), + () => initInputCounters(), + () => initCopyClipboards(), ]); liveViewLoadEvents.init(); const regularViewLoadEvents = new Events('load', [ - initAccordions, - initCollapses, - initCarousels, - initDismisses, - initDropdowns, - initModals, - initDrawers, - initTabs, - initTooltips, - initPopovers, - initDials, - initInputCounters, - initCopyClipboards, + () => initAccordions(), + () => initCollapses(), + () => initCarousels(), + () => initDismisses(), + () => initDropdowns(), + () => initModals(), + () => initDrawers(), + () => initTabs(), + () => initTooltips(), + () => initPopovers(), + () => initDials(), + () => initInputCounters(), + () => initCopyClipboards(), ]); regularViewLoadEvents.init(); diff --git a/src/index.ts b/src/index.ts index a9d5f30fc..df5d41f84 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,19 +16,19 @@ import './components/index'; // setup events for data attributes const events = new Events('load', [ - initAccordions, - initCollapses, - initCarousels, - initDismisses, - initDropdowns, - initModals, - initDrawers, - initTabs, - initTooltips, - initPopovers, - initDials, - initInputCounters, - initCopyClipboards, + () => initAccordions(), + () => initCollapses(), + () => initCarousels(), + () => initDismisses(), + () => initDropdowns(), + () => initModals(), + () => initDrawers(), + () => initTabs(), + () => initTooltips(), + () => initPopovers(), + () => initDials(), + () => initInputCounters(), + () => initCopyClipboards, ]); events.init(); diff --git a/src/index.turbo.ts b/src/index.turbo.ts index 0504f1817..8707990db 100644 --- a/src/index.turbo.ts +++ b/src/index.turbo.ts @@ -12,7 +12,7 @@ import Tabs from './components/tabs'; import Tooltip from './components/tooltip'; import InputCounter from './components/input-counter'; import CopyClipboard from './components/clipboard'; -import { initFlowbite } from './components/index'; +import { initFlowbite } from './components'; import Events from './dom/events'; // Since turbo maintainers refuse to add this event, we'll add it ourselves @@ -27,14 +27,16 @@ addEventListener('turbo:before-stream-render', (event: CustomEvent) => { }; }); -const turboLoadEvents = new Events('turbo:load', [initFlowbite]); +const turboLoadEvents = new Events('turbo:load', [() => initFlowbite()]); turboLoadEvents.init(); -const turboFrameLoadEvents = new Events('turbo:frame-load', [initFlowbite]); +const turboFrameLoadEvents = new Events('turbo:frame-load', [ + () => initFlowbite(), +]); turboFrameLoadEvents.init(); const turboStreamLoadEvents = new Events('turbo:after-stream-render', [ - initFlowbite, + () => initFlowbite, ]); turboStreamLoadEvents.init(); diff --git a/src/index.umd.ts b/src/index.umd.ts index ee49fb1a9..381c85f12 100644 --- a/src/index.umd.ts +++ b/src/index.umd.ts @@ -18,19 +18,19 @@ import './components/index'; import Events from './dom/events'; const events = new Events('load', [ - initAccordions, - initCollapses, - initCarousels, - initDismisses, - initDropdowns, - initModals, - initDrawers, - initTabs, - initTooltips, - initPopovers, - initDials, - initCopyClipboards, - initInputCounters, + () => initAccordions(), + () => initCollapses(), + () => initCarousels(), + () => initDismisses(), + () => initDropdowns(), + () => initModals(), + () => initDrawers(), + () => initTabs(), + () => initTooltips(), + () => initPopovers(), + () => initDials(), + () => initCopyClipboards(), + () => initInputCounters(), ]); events.init(); From 5ff8476a050750d91804f18b3c298a7c21757c2f Mon Sep 17 00:00:00 2001 From: "Sam L. D" Date: Fri, 29 Mar 2024 10:42:56 -0400 Subject: [PATCH 3/3] Added documentation on the initialisation methods. --- content/getting-started/javascript.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/content/getting-started/javascript.md b/content/getting-started/javascript.md index bf0a70915..2723548d8 100644 --- a/content/getting-started/javascript.md +++ b/content/getting-started/javascript.md @@ -202,6 +202,23 @@ initFlowbite(); Basically, the `initFlowbite()` function parses your DOM for all of the data attributes and creates new instances of the appropriate components like modals or dropdowns and sets up the behaviour of the examples from the Flowbite Docs - applying the functionality of showing and hiding the components such as hiding the modal when clicking on the "X" (close) button. +### Targeted initialisation + +All the initialisation functions can be called with an optional argument. This optional argument allows you to specify which node from the DOM you want to target for initialisation. It can be especially useful when content is created or loaded after the document has been parsed and needs to be made interactive. + +Consider the following example for [HTMX](https://htmx.org/): + +```javascript +import { initFlowbite } from 'flowbite' + +// this method is called when new content is loaded +htmx.onLoad(function (content) { + initFlowbite(content); +}) +``` + +**Be mindful that only the ***descendants*** of the node will be made interactive, and not the node itself.** + ## Instance manager Since version `2.0.0`, the Flowbite JS API also provides a way to globally access all of the instances even if they were created via the data attributes interface. This allows you to programmatically handle the components while also giving you the possibility to use the recommended and quick way of accessing the data attributes interface and UI component examples.