From 1e43fdf67d465e93a5a6eb7894ed8ab56a483076 Mon Sep 17 00:00:00 2001 From: Pavel Klibani Date: Tue, 6 Feb 2024 16:34:18 +0100 Subject: [PATCH 1/3] Feat(web-React): Tooltip can be opened by hover - Add `enableHover` prop to TooltipModern - solves #DS-1140 --- .../src/components/Tooltip/TooltipModern.tsx | 4 ++- .../src/components/Tooltip/useFloating.ts | 27 ++++++++++++++++--- .../TooltipModern/demo/TooltipHover.tsx | 18 +++++++++++++ .../components/TooltipModern/demo/index.tsx | 4 +++ packages/web-react/src/types/tooltip.ts | 1 + 5 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 packages/web-react/src/components/TooltipModern/demo/TooltipHover.tsx diff --git a/packages/web-react/src/components/Tooltip/TooltipModern.tsx b/packages/web-react/src/components/Tooltip/TooltipModern.tsx index e9e6b23d81..c8f3a11276 100644 --- a/packages/web-react/src/components/Tooltip/TooltipModern.tsx +++ b/packages/web-react/src/components/Tooltip/TooltipModern.tsx @@ -10,9 +10,10 @@ const TooltipModern = (props: TooltipModernProps) => { const { children, enableFlipping: flipProp = true, + enableFlippingCrossAxis: flipCrossAxis = true, + enableHover = false, enableShifting: shiftProp = true, enableSizing: sizeProp = false, - enableFlippingCrossAxis: flipCrossAxis = true, flipFallbackAxisSideDirection = 'none', flipFallbackPlacements = ['bottom', 'top'], id, @@ -52,6 +53,7 @@ const TooltipModern = (props: TooltipModernProps) => { const { getFloatingProps, getReferenceProps, maxWidth, middlewareData, placement, refs, x, y } = useFloating({ arrowRef, cornerOffset: tooltipCornerOffset, + enableHover, flipCrossAxis, flipFallbackAxisSideDirection, flipFallbackPlacements, diff --git a/packages/web-react/src/components/Tooltip/useFloating.ts b/packages/web-react/src/components/Tooltip/useFloating.ts index 11319cb2db..0d9406136c 100644 --- a/packages/web-react/src/components/Tooltip/useFloating.ts +++ b/packages/web-react/src/components/Tooltip/useFloating.ts @@ -9,7 +9,9 @@ import { shift, size, useClick, + useDismiss, useFloating as useFloatingUI, + useHover, useInteractions, useRole, } from '@floating-ui/react'; @@ -18,6 +20,7 @@ import { useState } from 'react'; type UseTooltipUIProps = { arrowRef: React.MutableRefObject; cornerOffset?: number; + enableHover?: boolean; flipCrossAxis: boolean; flipFallbackAxisSideDirection: 'none' | 'start' | 'end'; flipFallbackPlacements?: Placement | Placement[]; @@ -40,6 +43,7 @@ export const useFloating = (props: UseTooltipUIProps) => { const { arrowRef, cornerOffset = 0, + enableHover, flipCrossAxis, flipFallbackAxisSideDirection = 'none', flipFallbackPlacements, @@ -54,12 +58,27 @@ export const useFloating = (props: UseTooltipUIProps) => { } = props; const [maxWidth, setMaxWidth] = useState(undefined); + const [isClicked, setIsClicked] = useState(false); const mainAxisOffset = cornerOffset + tooltipArrowWidth; // Floating UI library settings const { x, y, refs, context, placement, middlewareData } = useFloatingUI({ open: isOpen, - onOpenChange: onToggle, + onOpenChange: (open, event, reason) => { + if (enableHover) { + // if tooltip is opened by click, do not close until clicked again or outside press or escape key + if (reason === 'click') setIsClicked((prev) => !prev); + if (isOpen && isClicked && reason === 'hover') return; + if (isOpen && isClicked && (reason === 'click' || reason === 'outside-press' || reason === 'escape-key')) { + setIsClicked(false); + onToggle(false); + + return; + } + } + + onToggle(open); + }, placement: tooltipPlacement, whileElementsMounted: autoUpdate, middleware: [ @@ -91,9 +110,11 @@ export const useFloating = (props: UseTooltipUIProps) => { }); // Floating UI library interaction hooks - const click = useClick(context); + const click = useClick(context, { enabled: true }); + const hover = useHover(context, { enabled: enableHover }); + const dismiss = useDismiss(context); const role = useRole(context, { role: 'tooltip' }); - const { getReferenceProps, getFloatingProps } = useInteractions([click, role]); + const { getReferenceProps, getFloatingProps } = useInteractions([click, hover, dismiss, role]); return { context, diff --git a/packages/web-react/src/components/TooltipModern/demo/TooltipHover.tsx b/packages/web-react/src/components/TooltipModern/demo/TooltipHover.tsx new file mode 100644 index 0000000000..0532df8bc3 --- /dev/null +++ b/packages/web-react/src/components/TooltipModern/demo/TooltipHover.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { TooltipModern, TooltipPopover, TooltipTrigger } from '..'; + +const TooltipHover = () => { + const [open, setOpen] = React.useState(false); + + return ( + + Trigger + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Velit, labore eveniet deleniti rerum quod voluptatem + autem aperiam officiis qui officia! Suscipit aspernatur minima dolorum rerum harum ipsa neque culpa facilis? + + + ); +}; + +export default TooltipHover; diff --git a/packages/web-react/src/components/TooltipModern/demo/index.tsx b/packages/web-react/src/components/TooltipModern/demo/index.tsx index 5a8bbd4cf8..a3fdd0dac3 100644 --- a/packages/web-react/src/components/TooltipModern/demo/index.tsx +++ b/packages/web-react/src/components/TooltipModern/demo/index.tsx @@ -13,10 +13,14 @@ import TooltipDismissibleViaJS from './TooltipDismissibleViaJS'; import TooltipOnHover from './TooltipOnHover'; import TooltipPlacements from './TooltipPlacements'; import TooltipWithFloatingUI from './TooltipWithFloatingUI'; +import TooltipHover from './TooltipHover'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + diff --git a/packages/web-react/src/types/tooltip.ts b/packages/web-react/src/types/tooltip.ts index a4a6234e05..ca84b7ed4c 100644 --- a/packages/web-react/src/types/tooltip.ts +++ b/packages/web-react/src/types/tooltip.ts @@ -46,6 +46,7 @@ export interface SpiritTooltipModernProps extends TooltipModernProps, ChildrenPr enableFlippingCrossAxis?: boolean; enableShifting?: boolean; enableSizing?: boolean; + enableHover?: boolean; flipFallbackAxisSideDirection?: 'none' | 'start' | 'end'; flipFallbackPlacements?: Placement | Placement[]; } From e0513dfc36642cdafb40f18f88b027fd0fa6ac6b Mon Sep 17 00:00:00 2001 From: Pavel Klibani Date: Fri, 9 Feb 2024 09:59:51 +0100 Subject: [PATCH 2/3] Feat(web): Tooltip hover --- .../TooltipModern/demo/TooltipHover.tsx | 3 +- packages/web/src/js/Tooltip.ts | 72 ++- .../web/src/js/utils/ComponentFunctions.ts | 20 +- .../src/scss/components/Tooltip/index.html | 576 +----------------- yarn.lock | 46 +- 5 files changed, 117 insertions(+), 600 deletions(-) diff --git a/packages/web-react/src/components/TooltipModern/demo/TooltipHover.tsx b/packages/web-react/src/components/TooltipModern/demo/TooltipHover.tsx index 0532df8bc3..83e59b2a89 100644 --- a/packages/web-react/src/components/TooltipModern/demo/TooltipHover.tsx +++ b/packages/web-react/src/components/TooltipModern/demo/TooltipHover.tsx @@ -1,12 +1,13 @@ import React from 'react'; import { TooltipModern, TooltipPopover, TooltipTrigger } from '..'; +import { Button } from '../../Button'; const TooltipHover = () => { const [open, setOpen] = React.useState(false); return ( - Trigger + I have a tooltip 😎 Lorem ipsum dolor sit amet consectetur adipisicing elit. Velit, labore eveniet deleniti rerum quod voluptatem autem aperiam officiis qui officia! Suscipit aspernatur minima dolorum rerum harum ipsa neque culpa facilis? diff --git a/packages/web/src/js/Tooltip.ts b/packages/web/src/js/Tooltip.ts index 8eaaa9a358..4d033709ef 100644 --- a/packages/web/src/js/Tooltip.ts +++ b/packages/web/src/js/Tooltip.ts @@ -1,8 +1,13 @@ import * as FloatingUI from '@floating-ui/dom'; import BaseComponent from './BaseComponent'; -import EventHandler from './dom/EventHandler'; -import SelectorEngine from './dom/SelectorEngine'; -import { enableDismissTrigger, enableToggleTrigger, SpiritConfig } from './utils'; +import { EventHandler, SelectorEngine } from './dom'; +import { + enableDismissTrigger, + enableToggleAutoloader, + enableToggleTrigger, + SpiritConfig, + clickOutsideElement, +} from './utils'; const NAME = 'tooltip'; const DATA_KEY = 'tooltip'; @@ -19,9 +24,10 @@ const CLASS_NAME_HIDDEN = 'is-hidden'; type Config = { enableFlipping: boolean; + enableFlippingCrossAxis: boolean; + enableHover: boolean; enableShifting: boolean; enableSizing: boolean; - enableFlippingCrossAxis: boolean; flipFallbackAxisSideDirection: 'none' | 'start' | 'end'; flipFallbackPlacements: string; placement: FloatingUI.Placement; @@ -33,13 +39,14 @@ export const transformStringToArray = (str: string) => class Tooltip extends BaseComponent { arrow?: HTMLElement; - arrowWidth?: number; arrowCornerOffset?: number; + arrowWidth?: number; tip: HTMLElement; tooltipComputedStyle?: CSSStyleDeclaration; tooltipMaxWidth?: number; tooltipOffset?: number; trigger?: HTMLElement; + isToggled: boolean; constructor(element: SpiritElement, config?: SpiritConfig) { if (typeof FloatingUI === 'undefined') { @@ -49,6 +56,7 @@ class Tooltip extends BaseComponent { super(element, config); this.tip = this.getTipElement(); + this.isToggled = false; if (this.isPlacementControlled()) { this.trigger = this.getTipTooltipWrapper(); @@ -69,6 +77,8 @@ class Tooltip extends BaseComponent { ); } } + + this.addEventListeners(); } static get NAME() { @@ -322,9 +332,59 @@ class Tooltip extends BaseComponent { }); } } + + autoCloseHandler = (event: Event) => { + const shouldClose = this.trigger && clickOutsideElement(this.trigger, event); + + if (event.target && shouldClose) { + this.hide(); + } + + this.isToggled = false; + }; + + changeToggle() { + console.log('this.isToggled(before):', this.isToggled); + this.isToggled = !this.isToggled; + console.log('this.isToggled(after):', this.isToggled); + } + + addEventListeners() { + const button = this.trigger?.querySelector('button') as HTMLButtonElement; + const { enableHover } = this.config as Config; + + EventHandler.on(document, 'click', (event: Event) => this.autoCloseHandler(event)); + + if (!enableHover) { + EventHandler.on(button, 'click', this.toggle.bind(this)); + } else { + EventHandler.on(button, 'click', this.changeToggle.bind(this)); + + this.addMouseEventListeners(); + } + } + + addMouseEventListeners() { + const button = this.trigger?.querySelector('button') as HTMLButtonElement; + EventHandler.on(button, 'mouseenter', () => { + console.log('this.isToggled(mouseenter):', this.isToggled); + + if (!this.isToggled) { + this.show(); + } + }); + EventHandler.on(button, 'mouseleave', () => { + console.log('this.isToggled(mouseleave):', this.isToggled); + + if (!this.isToggled) { + this.hide(); + } + }); + } } -enableToggleTrigger(Tooltip, 'toggle'); +enableToggleAutoloader(Tooltip, 'hide', 'target'); +// enableToggleTrigger(Tooltip, 'toggle'); enableDismissTrigger(Tooltip, 'hide'); export default Tooltip; diff --git a/packages/web/src/js/utils/ComponentFunctions.ts b/packages/web/src/js/utils/ComponentFunctions.ts index 1310efd9f5..de281341ac 100644 --- a/packages/web/src/js/utils/ComponentFunctions.ts +++ b/packages/web/src/js/utils/ComponentFunctions.ts @@ -26,8 +26,20 @@ const onClickHandler = ( }); }; -const onLoadHandler = (element: HTMLElement, component: typeof BaseComponent) => { - component.getOrCreateInstance(element); +const onLoadHandler = ( + element: HTMLElement, + component: typeof BaseComponent, + method: string, + event: Event, + aim: Aim = 'trigger', +) => { + if (aim === 'target') { + const target = getTriggerOrTarget(getElement(element), aim); + const instance = component.getOrCreateInstance(target); + instance[method](target, event); + } else { + component.getOrCreateInstance(element); + } }; const enableDataTrigger = ( @@ -54,8 +66,8 @@ const enableDismissTrigger = (component: typeof BaseComponent, method = 'dismiss enableDataTrigger(ATTRIBUTE_DATA_DISMISS, component, onClickHandler, method, aim); }; -const enableToggleAutoloader = (component: typeof BaseComponent, method = 'toggle') => { - enableDataTrigger(ATTRIBUTE_DATA_TOGGLE, component, onLoadHandler, method); +const enableToggleAutoloader = (component: typeof BaseComponent, method = 'toggle', aim: Aim = 'trigger') => { + enableDataTrigger(ATTRIBUTE_DATA_TOGGLE, component, onLoadHandler, method, aim); }; const clickOutsideElement = (target: Element, event: Event) => !event.composedPath().includes(target); diff --git a/packages/web/src/scss/components/Tooltip/index.html b/packages/web/src/scss/components/Tooltip/index.html index 3b8f4c7802..e956b2b038 100644 --- a/packages/web/src/scss/components/Tooltip/index.html +++ b/packages/web/src/scss/components/Tooltip/index.html @@ -2,295 +2,31 @@
-

Placements

- -
- -
-
-
- - - - - -
-
- - - - - -
-
- - - - - -
-
- - - - - -
-
- -
-
- Click
- the dots! -
-
- bottom - -
-
- -
-
-
- - - -
-
- -
- -

Static Tooltip (No Interaction)

+

Tooltip on Hover with Floating UI

-
-
- Tooltips
- all day long… -
-
- Hello there! - -
-
- Hello there! - -
-
- Hello there! There is slightly more text in this tooltip. - -
-
- Hello there! - -
-
- -
- -
- -
- -

Tooltip on Hover (Pure CSS)

- -
- -
-
- -
- Hello there! - -
-
-
- -
- Hello there! - -
-
-
- -
- Hello there! - -
-
-
- -
- Hello there! - -
-
-
- -
- -
- -
- -

Tooltip with JS plugin

- -
- -

Without Floating UI

- - - - -
- - -
- -

With Floating UI and placement fallbacks

- - - - - -
@@ -299,296 +35,4 @@

With Floating UI and placement fallbacks

-
- -

Tooltip on Click (JavaScript)

- -
- -

Without Floating UI

- - - -
-
- I have an externally-triggered tooltip -
- -
- -

With Floating UI and placement fallbacks

- - - -
-
- I have an externally-triggered tooltip -
- -
- -
- -
- -
- -

Dismissible Tooltip

- -
- -
- -
- Close me - - -
-
- -
- -
- -
- -

Dismissible Tooltip via JS API and Floating UI

- -
-

- Saves data to local storage. -

- - - -
- - -
- -
- -
- -
- - - - - -

Advanced Floating Functionality

- -

- Try scrolling the frame or resizing the window to see how the Tooltip behaves. The Floating UI - library is trying to keep the Tooltip in the viewport and it is also flipping, shifting and - resizing the Tooltip when it is not possible to keep it in the viewport. -

- -
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
-
- -
- - - - -
- -
-
- -
-
- {{/ layout/plain }} diff --git a/yarn.lock b/yarn.lock index 93b1faae89..9ddd3f06f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3337,12 +3337,12 @@ resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.3.0.tgz#113bc85fa102cf890ae801668f43ee265c547a09" integrity sha512-vX1WVAdPjZg9DkDkC+zEx/tKtnST6/qcNpwcjeBgco3XRNHz5PUA+ivi/yr6G3o0kMR60uKBJcfOdfzOFI7PMQ== -"@floating-ui/core@^1.5.3": - version "1.5.3" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.3.tgz#b6aa0827708d70971c8679a16cf680a515b8a52a" - integrity sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q== +"@floating-ui/core@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" + integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== dependencies: - "@floating-ui/utils" "^0.2.0" + "@floating-ui/utils" "^0.2.1" "@floating-ui/dom@^1.3.0": version "1.3.0" @@ -3351,13 +3351,13 @@ dependencies: "@floating-ui/core" "^1.3.0" -"@floating-ui/dom@^1.5.4": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.4.tgz#28df1e1cb373884224a463235c218dcbd81a16bb" - integrity sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ== +"@floating-ui/dom@^1.5.3", "@floating-ui/dom@^1.6.1": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.1.tgz#d552e8444f77f2d88534372369b3771dc3a2fa5d" + integrity sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ== dependencies: - "@floating-ui/core" "^1.5.3" - "@floating-ui/utils" "^0.2.0" + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.1" "@floating-ui/react-dom@^2.0.0": version "2.0.1" @@ -3366,23 +3366,23 @@ dependencies: "@floating-ui/dom" "^1.3.0" -"@floating-ui/react-dom@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.5.tgz#851522899c34e3e2be1e29f3294f150834936e28" - integrity sha512-UsBK30Bg+s6+nsgblXtZmwHhgS2vmbuQK22qgt2pTQM6M3X6H1+cQcLXqgRY3ihVLcZJE6IvqDQozhsnIVqK/Q== +"@floating-ui/react-dom@^2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" + integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== dependencies: - "@floating-ui/dom" "^1.5.4" + "@floating-ui/dom" "^1.6.1" -"@floating-ui/react@0.26.5": - version "0.26.5" - resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.5.tgz#6a8658c88ff017b7530594b94433d614bff66e06" - integrity sha512-LJeSQa+yOwV0Tdpc/C3Vr92QMrwRqRMTk4yOwsRJKc57x3Lcw317GE0EV+ECM7+Z89yEAPBe7nzbDEWfkWCrBA== +"@floating-ui/react@^0.26.5": + version "0.26.9" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.9.tgz#bbccbefa0e60c8b7f4c0387ba0fc0607bb65f2cc" + integrity sha512-p86wynZJVEkEq2BBjY/8p2g3biQ6TlgT4o/3KgFKyTWoJLU1GZ8wpctwRqtkEl2tseYA+kw7dBAIDFcednfI5w== dependencies: - "@floating-ui/react-dom" "^2.0.5" - "@floating-ui/utils" "^0.2.0" + "@floating-ui/react-dom" "^2.0.8" + "@floating-ui/utils" "^0.2.1" tabbable "^6.0.1" -"@floating-ui/utils@^0.2.0": +"@floating-ui/utils@^0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== From 8cfdee46a888bcb2e0201ac98296df1fba05acad Mon Sep 17 00:00:00 2001 From: literat Date: Fri, 9 Feb 2024 16:00:46 +0100 Subject: [PATCH 3/3] Refactor(web): Tooltip on hover --- packages/web/src/js/Tooltip.ts | 127 +++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 39 deletions(-) diff --git a/packages/web/src/js/Tooltip.ts b/packages/web/src/js/Tooltip.ts index 4d033709ef..e6bb1254e3 100644 --- a/packages/web/src/js/Tooltip.ts +++ b/packages/web/src/js/Tooltip.ts @@ -1,13 +1,7 @@ import * as FloatingUI from '@floating-ui/dom'; import BaseComponent from './BaseComponent'; import { EventHandler, SelectorEngine } from './dom'; -import { - enableDismissTrigger, - enableToggleAutoloader, - enableToggleTrigger, - SpiritConfig, - clickOutsideElement, -} from './utils'; +import { SpiritConfig, clickOutsideElement, enableDismissTrigger, enableToggleAutoloader } from './utils'; const NAME = 'tooltip'; const DATA_KEY = 'tooltip'; @@ -17,6 +11,12 @@ const EVENT_HIDE = `hide${EVENT_KEY}`; const EVENT_HIDDEN = `hidden${EVENT_KEY}`; const EVENT_SHOW = `show${EVENT_KEY}`; const EVENT_SHOWN = `shown${EVENT_KEY}`; +const EVENT_CLICK = 'click'; +const EVENT_MOUSEENTER = 'mouseenter'; +const EVENT_MOUSELEAVE = 'mouseleave'; + +const TRIGGER_HOVER = 'hover'; +const TRIGGER_CLICK = 'click'; const SELECTOR_ARROW = '[data-spirit-element="arrow"]'; const CLASS_NAME_VISIBLE = 'is-visible'; @@ -47,16 +47,20 @@ class Tooltip extends BaseComponent { tooltipOffset?: number; trigger?: HTMLElement; isToggled: boolean; + isHovered: boolean; + activeTrigger: object; constructor(element: SpiritElement, config?: SpiritConfig) { if (typeof FloatingUI === 'undefined') { throw new TypeError('Floating UI dependency is missing. Please, install it (https://floating-ui.com/)'); } - + console.log('construct', element); super(element, config); this.tip = this.getTipElement(); this.isToggled = false; + this.isHovered = false; + this.activeTrigger = {}; if (this.isPlacementControlled()) { this.trigger = this.getTipTooltipWrapper(); @@ -78,6 +82,8 @@ class Tooltip extends BaseComponent { } } + console.log(this.trigger); + this.addEventListeners(); } @@ -86,11 +92,17 @@ class Tooltip extends BaseComponent { } toggle() { + this.activeTrigger[TRIGGER_CLICK] = 'click' in this.activeTrigger ? !this.activeTrigger[TRIGGER_CLICK] : true; + this.activeTrigger[TRIGGER_HOVER] = 'hover' in this.activeTrigger ? !this.activeTrigger[TRIGGER_HOVER] : true; + console.log('toggle', this.activeTrigger); + if (this.isShown()) { - this.hide(); - } else { - this.show(); + this.leave(); + + return; } + + this.enter(); } isPlacementControlled() { @@ -143,9 +155,16 @@ class Tooltip extends BaseComponent { } EventHandler.trigger(this.element, Tooltip.eventName(EVENT_SHOWN)); + + if (this.isHovered === false) { + this.leave(); + } + + this.isHovered = false; } hide() { + console.log('hide', this.activeTrigger); if (!this.isShown()) { return; } @@ -167,6 +186,14 @@ class Tooltip extends BaseComponent { } } + this.activeTrigger[TRIGGER_CLICK] = false; + this.activeTrigger[TRIGGER_HOVER] = false; + this.isHovered = false; + + if (this.isWithActiveTrigger()) { + return; + } + this.element.removeAttribute('aria-describedby'); EventHandler.trigger(this.element, Tooltip.eventName(EVENT_HIDDEN)); } @@ -334,57 +361,79 @@ class Tooltip extends BaseComponent { } autoCloseHandler = (event: Event) => { + console.log('autoclose'); const shouldClose = this.trigger && clickOutsideElement(this.trigger, event); - + console.log(shouldClose); if (event.target && shouldClose) { - this.hide(); - } + this.activeTrigger[TRIGGER_CLICK] = false; + this.activeTrigger[TRIGGER_HOVER] = false; - this.isToggled = false; + this.leave(); + } }; - changeToggle() { - console.log('this.isToggled(before):', this.isToggled); - this.isToggled = !this.isToggled; - console.log('this.isToggled(after):', this.isToggled); + isWithActiveTrigger() { + return Object.values(this.activeTrigger).includes(true); + } + + enter() { + console.log('enter'); + if (this.isShown() || this.isHovered) { + this.isHovered = true; + + return; + } + + this.isHovered = true; + + this.show(); + } + + leave() { + if (this.isWithActiveTrigger()) { + return; + } + console.log('leave', this.activeTrigger, this.isWithActiveTrigger()); + + this.isHovered = false; + + this.hide(); } addEventListeners() { const button = this.trigger?.querySelector('button') as HTMLButtonElement; const { enableHover } = this.config as Config; - EventHandler.on(document, 'click', (event: Event) => this.autoCloseHandler(event)); + EventHandler.on(document, EVENT_CLICK, (event: Event) => this.autoCloseHandler(event)); - if (!enableHover) { - EventHandler.on(button, 'click', this.toggle.bind(this)); - } else { - EventHandler.on(button, 'click', this.changeToggle.bind(this)); + EventHandler.on(button, EVENT_CLICK, (event) => { + const context = Tooltip.getOrCreateInstance(this.element as HTMLElement); + console.log('click instance', context); + // context.activeTrigger[TRIGGER_CLICK] = true; + context.toggle(); + }); - this.addMouseEventListeners(); - } + this.addMouseEventListeners(); } addMouseEventListeners() { const button = this.trigger?.querySelector('button') as HTMLButtonElement; - EventHandler.on(button, 'mouseenter', () => { - console.log('this.isToggled(mouseenter):', this.isToggled); - - if (!this.isToggled) { - this.show(); - } + EventHandler.on(button, EVENT_MOUSEENTER, (event) => { + const context = Tooltip.getOrCreateInstance(this.element as HTMLElement); + console.log('hover enter', context); + context.activeTrigger[TRIGGER_HOVER] = true; + context.enter(); }); - EventHandler.on(button, 'mouseleave', () => { - console.log('this.isToggled(mouseleave):', this.isToggled); - - if (!this.isToggled) { - this.hide(); - } + EventHandler.on(button, EVENT_MOUSELEAVE, (event) => { + const context = Tooltip.getOrCreateInstance(this.element as HTMLElement); + console.log('hover leave', context, context.activeTrigger); + context.activeTrigger[TRIGGER_HOVER] = false; + context.leave(); }); } } enableToggleAutoloader(Tooltip, 'hide', 'target'); -// enableToggleTrigger(Tooltip, 'toggle'); enableDismissTrigger(Tooltip, 'hide'); export default Tooltip;