diff --git a/config.go b/config.go index 190a6fa..07cdf57 100644 --- a/config.go +++ b/config.go @@ -2,9 +2,10 @@ package gropdown // Config represents a dropdown menu component. type Config struct { - Open bool // Open indicates whether the dropdown menu is currently open. - Animation bool // Animation indicates whether the dropdown button should use animations on open and close. - Position Position // Position indicates the position of the dropdown content relative to the button. + Open bool // Open indicates whether the dropdown menu is currently open. + Position Position // Position indicates the position of the dropdown content relative to the button. + Animation bool // Animation indicates whether the dropdown button should use animations on open and close. + CloseOnOutsideClick bool // CloseOnOutsideClick indicates whether the dropdown should be closes when a click occurs outside of it. } // DropdownBuilder is used to construct Dropdown instances with options. @@ -19,9 +20,10 @@ type ConfigMap struct { // DefaultConfig returns the default configuration. func DefaultConfig() Config { return Config{ - Open: false, - Animation: true, - Position: Bottom, + Open: false, + Position: Bottom, + CloseOnOutsideClick: true, + Animation: true, } } @@ -38,14 +40,21 @@ func (b *ConfigBuilder) WithOpen(open bool) *ConfigBuilder { return b } -// WithAnimation sets the animations for the dropdown button icon when open/close. -func (b *ConfigBuilder) WithAnimation(animation bool) *ConfigBuilder { - b.config.Animation = animation +// WithPosition sets the opening position fro the dropdown menu. +func (b *ConfigBuilder) WithPosition(position Position) *ConfigBuilder { + b.config.Position = position return b } -func (b *ConfigBuilder) WithPosition(position Position) *ConfigBuilder { - b.config.Position = position +// WithCloseOnOutsideClick sets whether or not auto close when a click occurs outside of it.. +func (b *ConfigBuilder) WithCloseOnOutsideClick(close bool) *ConfigBuilder { + b.config.CloseOnOutsideClick = close + return b +} + +// WithAnimation sets the animations for the dropdown button icon when open/close. +func (b *ConfigBuilder) WithAnimation(animation bool) *ConfigBuilder { + b.config.Animation = animation return b } diff --git a/gropdown-js.templ b/gropdown-js.templ index 03fda56..a2f90ae 100644 --- a/gropdown-js.templ +++ b/gropdown-js.templ @@ -451,7 +451,7 @@ script GropdownJS(configMap *ConfigMap) { /** ***************** END - Event Handlers ******************************************** */ const dropdownBtn = node.querySelector(".gdd_button") - if (options?.enabled) { + if (dropdownBtn != null && options?.enabled) { initialize() node.addEventListener('click', onButtonClick) node.addEventListener('keydown', onButtonKeydown) @@ -497,30 +497,29 @@ script GropdownJS(configMap *ConfigMap) { * @param {boolean} options.animated - Flag indicating if the dropdown menu button should use icon animations. */ function clickOutsideAction(e, options) { - const animated = options?.animated || true - const dropdownContainer = document.querySelector('[class*="gddContainer"]') - if (!dropdownContainer) return + const animated = options?.animated || true; + const idSelector = `gropdown-container-${options?.id}`; + const dropdownContainer = document.getElementById(idSelector); + if (!dropdownContainer) return; - const isClickedInsideDropdown = dropdownContainer.contains(e.target) + const isClickedInsideDropdown = dropdownContainer.contains(e.target); if (!isClickedInsideDropdown) { - dropdownBtn = dropdownContainer.querySelector('[class*="gddButton"]') - dropdownBtn.setAttribute('aria-expanded', false) + dropdownBtn = dropdownContainer.querySelector('.gdd_button'); + dropdownBtn.setAttribute('aria-expanded', false); if (animated) { - const svgElement = dropdownBtn.querySelector('svg') - svgElement.classList.remove('iconToOpen') - svgElement.classList.remove('iconToClose') + const svgElement = dropdownBtn.querySelector('svg'); + svgElement.classList.remove('iconToOpen'); + svgElement.classList.remove('iconToClose'); } - const ulElement = dropdownContainer.querySelector('ul[role="menu"]') - if (!ulElement) return + const ulElement = dropdownContainer.querySelector('ul[role="menu"]'); + if (!ulElement) return; - ulElement.setAttribute('data-state', 'close') + ulElement.setAttribute('data-state', 'close'); } } - document.body.addEventListener('click', clickOutsideAction); - document.addEventListener('DOMContentLoaded', function() { // Loop over all tabber components in the page and initialise them. for (const key in configMap.Data) { @@ -532,7 +531,14 @@ script GropdownJS(configMap *ConfigMap) { enabled: true, open: gropdownConfig.Open, animated: gropdownConfig.Animation, - }) + }); + + if(configMap.Data[key].CloseOnOutsideClick) { + // Add event listener to each container + document.body.addEventListener('click', function(e) { + clickOutsideAction(e,{id: key} ); + }); + } } } }); diff --git a/gropdown-js_templ.go b/gropdown-js_templ.go index 34b009f..dae2d3e 100644 --- a/gropdown-js_templ.go +++ b/gropdown-js_templ.go @@ -9,8 +9,8 @@ import "github.com/a-h/templ" func GropdownJS(configMap *ConfigMap) templ.ComponentScript { return templ.ComponentScript{ - Name: `__templ_GropdownJS_c80d`, - Function: `function __templ_GropdownJS_c80d(configMap){// Utility function to check if a value is null or undefined + Name: `__templ_GropdownJS_3b6c`, + Function: `function __templ_GropdownJS_3b6c(configMap){// Utility function to check if a value is null or undefined function isNullish(value) { return value === null || value === undefined; } @@ -460,7 +460,7 @@ func GropdownJS(configMap *ConfigMap) templ.ComponentScript { /** ***************** END - Event Handlers ******************************************** */ const dropdownBtn = node.querySelector(".gdd_button") - if (options?.enabled) { + if (dropdownBtn != null && options?.enabled) { initialize() node.addEventListener('click', onButtonClick) node.addEventListener('keydown', onButtonKeydown) @@ -506,30 +506,29 @@ func GropdownJS(configMap *ConfigMap) templ.ComponentScript { * @param {boolean} options.animated - Flag indicating if the dropdown menu button should use icon animations. */ function clickOutsideAction(e, options) { - const animated = options?.animated || true - const dropdownContainer = document.querySelector('[class*="gddContainer"]') - if (!dropdownContainer) return + const animated = options?.animated || true; + const idSelector = ` + "`" + `gropdown-container-${options?.id}` + "`" + `; + const dropdownContainer = document.getElementById(idSelector); + if (!dropdownContainer) return; - const isClickedInsideDropdown = dropdownContainer.contains(e.target) + const isClickedInsideDropdown = dropdownContainer.contains(e.target); if (!isClickedInsideDropdown) { - dropdownBtn = dropdownContainer.querySelector('[class*="gddButton"]') - dropdownBtn.setAttribute('aria-expanded', false) + dropdownBtn = dropdownContainer.querySelector('.gdd_button'); + dropdownBtn.setAttribute('aria-expanded', false); if (animated) { - const svgElement = dropdownBtn.querySelector('svg') - svgElement.classList.remove('iconToOpen') - svgElement.classList.remove('iconToClose') + const svgElement = dropdownBtn.querySelector('svg'); + svgElement.classList.remove('iconToOpen'); + svgElement.classList.remove('iconToClose'); } - const ulElement = dropdownContainer.querySelector('ul[role="menu"]') - if (!ulElement) return + const ulElement = dropdownContainer.querySelector('ul[role="menu"]'); + if (!ulElement) return; - ulElement.setAttribute('data-state', 'close') + ulElement.setAttribute('data-state', 'close'); } } - document.body.addEventListener('click', clickOutsideAction); - document.addEventListener('DOMContentLoaded', function() { // Loop over all tabber components in the page and initialise them. for (const key in configMap.Data) { @@ -541,13 +540,20 @@ func GropdownJS(configMap *ConfigMap) templ.ComponentScript { enabled: true, open: gropdownConfig.Open, animated: gropdownConfig.Animation, - }) + }); + + if(configMap.Data[key].CloseOnOutsideClick) { + // Add event listener to each container + document.body.addEventListener('click', function(e) { + clickOutsideAction(e,{id: key} ); + }); + } } } }); }`, - Call: templ.SafeScript(`__templ_GropdownJS_c80d`, configMap), - CallInline: templ.SafeScriptInline(`__templ_GropdownJS_c80d`, configMap), + Call: templ.SafeScript(`__templ_GropdownJS_3b6c`, configMap), + CallInline: templ.SafeScriptInline(`__templ_GropdownJS_3b6c`, configMap), } }