From 981c1ac36af39c5680a498a2f66baba1d559acb2 Mon Sep 17 00:00:00 2001 From: Sage Abdullah Date: Tue, 23 Jan 2024 16:50:12 +0000 Subject: [PATCH] Fix drilldown component closing when interacting with datetime pickers --- client/src/controllers/DropdownController.ts | 62 +++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/client/src/controllers/DropdownController.ts b/client/src/controllers/DropdownController.ts index 770023ad08..6e7bc24e37 100644 --- a/client/src/controllers/DropdownController.ts +++ b/client/src/controllers/DropdownController.ts @@ -117,6 +117,8 @@ export class DropdownController extends Controller { theme: { default: 'dropdown' as TippyTheme, type: String }, }; + // Hide on click *inside* the dropdown. Differs from tippy's hideOnClick + // option for outside clicks that defaults to true and we don't yet expose it. declare hideOnClickValue: boolean; declare offsetValue: [number, number]; @@ -176,6 +178,7 @@ export class DropdownController extends Controller { trigger: 'click', interactive: true, ...(this.hasOffsetValue && { offset: this.offsetValue }), + hideOnClick: !this.useCustomHideOnClickAway, getReferenceClientRect: () => this.reference.getBoundingClientRect(), theme: this.themeValue, plugins: this.plugins, @@ -196,12 +199,67 @@ export class DropdownController extends Controller { }; } + get useCustomHideOnClickAway() { + // Tippy's default hideOnClick option for "hiding on click away" doesn't + // work well with our datetime libraries. Instead, we use a custom plugin + // to hide the tooltip when clicking outside the dropdown. It's unlikely + // we'll render a datetime picker for themes other than drilldown, so use + // the custom solution for this theme only. + return this.themeValue === 'drilldown'; + } + + /** + * Hides tooltip on click away from the reference element. + * A custom implementation to replace tippy's default hideOnClick option, + * which doesn't play well with our datetime libraries (or others that render + * elements outside of the dropdown's DOM). + */ + get hideTooltipOnClickAway() { + // We can't use `this` to access the controller instance inside the plugin + // function, so we need to get these ahead in time. + const reference = this.reference; + const toggleTarget = this.toggleTarget; + + return { + name: 'hideTooltipOnClickAway', + fn(instance: Instance) { + const onClick = (e: MouseEvent) => { + if ( + instance.state.isShown && + // Hide if the click is outside of the reference element, + // or if the click is on the toggle button itself. + (!reference.contains(e.target as Node) || + toggleTarget.contains(e.target as Node)) + ) { + instance.hide(); + } + }; + + return { + onShow() { + document.addEventListener('click', onClick); + }, + onHide() { + document.removeEventListener('click', onClick); + }, + }; + }, + }; + } + get plugins() { - return [ + const plugins = [ hideTooltipOnBreadcrumbsChange, hideTooltipOnEsc, rotateToggleIcon, - ].concat(this.hideOnClickValue ? [hideTooltipOnClickInside] : []); + ]; + if (this.hideOnClickValue) { + plugins.push(hideTooltipOnClickInside); + } + if (this.useCustomHideOnClickAway) { + plugins.push(this.hideTooltipOnClickAway); + } + return plugins; } /**