From d104e7894792194eae9c1a1ef3eb7d7fbdb2f2a1 Mon Sep 17 00:00:00 2001 From: Bartosz-Do Date: Fri, 25 Sep 2026 14:56:06 +0200 Subject: [PATCH] Fix `Confetti` ignoring the trigger's options when `data-bs-target` is set (#3195) Co-authored-by: codecalm --- .changeset/fix-confetti-target-options.md | 6 +++++ core/js/src/confetti.ts | 12 +++++++++- core/js/tests/unit/confetti.spec.ts | 27 +++++++++++++++++++++++ docs/content/ui/components/confetti.mdx | 2 +- 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-confetti-target-options.md diff --git a/.changeset/fix-confetti-target-options.md b/.changeset/fix-confetti-target-options.md new file mode 100644 index 000000000..f0a55b8cb --- /dev/null +++ b/.changeset/fix-confetti-target-options.md @@ -0,0 +1,6 @@ +--- +"@tabler/core": patch +"@tabler/docs": patch +--- + +Fixed Confetti ignoring the trigger's `data-bs-*` options when `data-bs-target` points elsewhere. diff --git a/core/js/src/confetti.ts b/core/js/src/confetti.ts index 6da6056f7..8ae25b1e3 100644 --- a/core/js/src/confetti.ts +++ b/core/js/src/confetti.ts @@ -7,6 +7,7 @@ import BaseComponent from './bootstrap/base-component' import EventHandler from './bootstrap/dom/event-handler' +import Manipulator from './bootstrap/dom/manipulator' import SelectorEngine from './bootstrap/dom/selector-engine' import { isDisabled } from './bootstrap/util/index' import type { ComponentConfig as BaseConfig, ElementSelector } from './bootstrap/types' @@ -432,7 +433,16 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( } const target = SelectorEngine.getElementFromSelector(this) || this - const instance = Confetti.getOrCreateInstance(target) as Confetti + const config = Manipulator.getDataAttributes(this) + const instance = Confetti.getOrCreateInstance(target, config) as Confetti + + // The instance lives on the target and keeps the config it was created + // with, so re-read the trigger's options on every click; several triggers + // can then share one target with their own look. + if (target !== this) { + instance._config = instance._getConfig(config) as ComponentConfig + } + instance.burst() }) diff --git a/core/js/tests/unit/confetti.spec.ts b/core/js/tests/unit/confetti.spec.ts index 61d4e87d1..9cb73615b 100644 --- a/core/js/tests/unit/confetti.spec.ts +++ b/core/js/tests/unit/confetti.spec.ts @@ -171,6 +171,33 @@ describe('Confetti', () => { expect(Confetti.getInstance(fixtureEl.querySelector('a')!)).toBeNull() }) + it('should read options from the trigger even when data-bs-target points elsewhere', () => { + fixtureEl.innerHTML = '
' + reduceMotion(true) + + button().click() + + const instance = Confetti.getInstance(fixtureEl.querySelector('#order')!) as Confetti + expect(instance).not.toBeNull() + expect(instance._config.count).toBe(5) + expect(instance._config.colors).toEqual(['#f00']) + }) + + it("should apply each trigger's own options when several share a target", () => { + fixtureEl.innerHTML = '
' + reduceMotion(true) + const target = fixtureEl.querySelector('#order')! + + fixtureEl.querySelector('#small')!.click() + expect((Confetti.getInstance(target) as Confetti)._config.count).toBe(5) + + fixtureEl.querySelector('#big')!.click() + expect((Confetti.getInstance(target) as Confetti)._config.count).toBe(600) + + fixtureEl.querySelector('#small')!.click() + expect((Confetti.getInstance(target) as Confetti)._config.count).toBe(5) + }) + it('should ignore a disabled trigger', () => { fixtureEl.innerHTML = '' reduceMotion(true) diff --git a/docs/content/ui/components/confetti.mdx b/docs/content/ui/components/confetti.mdx index b15ad1672..abe01689b 100644 --- a/docs/content/ui/components/confetti.mdx +++ b/docs/content/ui/components/confetti.mdx @@ -58,7 +58,7 @@ Set `data-bs-speed` to scale how fast the pieces fall. `1` is the default. A low ### Target -Point `data-bs-target` at another element to run the shower from there. The element does not change how the shower looks; it is where the events fire and where the instance is stored, so a page can listen in one place. +Point `data-bs-target` at another element to run the shower from there. Options are still read from the trigger, so the target only changes where the events fire and where the instance is stored, letting a page listen in one place.