Is it possible to trigger an action on mouse over or hover?

Inspired by Tooltip positioning - #7 by TW_Tones ands a previously raised desire to allow dragging over a tab to open the tab, I was wondering if there is a way to enable an action on mouseover?

  • The action would need to be triggered only once to stop multiple triggers

That is I am looking for a way to trigger a tiddlywiki action on mouse over. The fact is this sounds already like responding to a drop zone.

ANy thoughts on this apparently missing ferature in tiddlywiki?

1 Like

I can’t find where it was originally hosted, but Andreas Hahn (of Tinka fame) has a bare-bones plugin you can pick up here. It adds a <$hover> widget that functions very much like <$button>, but sends messages on mouse-in and/or mouse-out.

I imagine you’re not really looking for a Javascript solution, but maybe it will inspire something… or perhaps it will be useful to someone else.

2 Likes

Wow, Possibly just what was needed.

  • I note it will not trigger when dragging something over

This is great thanks @etardiff

If mobile devices are involved and the action is important, hover might not work well.

If no mobile devices are involved, or the action is benign, then you have no worries.

Thanks for the reminder, I am a “Desktop first kind of guy”, I love my 2xHD+ screens, but never the less, there are currently ways to do things with desktop and mobile, however less when on mobile. I am keen to make it a lot easier on Desktop without removing functionality on mobile, I may just not add the additional functions to mobile.

  • From experience I am more likely to copy/paste on mobile rather than drag, drop or mouse over anyway.

Thanks again

1 Like

You can always combine an $eventcatcher with e.g. the mouseenter or mouseover event…

@Yaisog this requires a JavaScript solution, yes?

Although your suggestion is somewhat helpful, I am always looking for native tiddlywiki solutions, if they don’t exist perhaps a plugin, but always with the view to reducing the gaps in tiddlywiki and ideally have them addressed in the core eventually.

@etardiff’s hover widget is an example, but there remain a few gaps.

I hope one day to allow tabs (effectively buttons) to contain custom dropzones such that you can drag content from somewhere, hover over a tab to open that tab, then drop on the tab contents to import.

  • Critically I would want the solution to be a macro/widget or what I call a WikiScript solution so every man, woman, child, cat and dog can use it :nerd_face:

Hi @TW_Tones, there is no JavaScript required: https://tiddlywiki.com/#EventCatcherWidget

Thanks, I now see why, I am familiar with event catcher, but not the javascript events you named. I have used it only to capture tiddlywiki events. I belive they are not documented in tiddlywiki beyond the parameter event-type The type property of the JavaScript event.

  • I imagine they are too numerous to add to the documentation?
  • Could you give a simple example how to use them?

[Edfited] I imagin these are equivalenmt to mousein , mouseout in the aforementioned hover widget.

Yes the hover widget is the solution to the OT, at least for tabs; thanks @etardiff Emily

Now mousing over any tab will change tabs :nerd_face:

  • Install the hover widget plugin, save and reload
  • edit $:/core/macros/tabs and wrap the top macro
  • Wrap contents of tabs-button macro in
<$hover set="$:/state/tab/sidebar--595412856" setTo=<<currentTab>> >

</$hover>
  • Note I established the state tiddler by changing tabs and seeing the above tiddler change and contain the currentTab.
  • We can just define a replacement macro for tabs-button

Package as of writing mouse-over-sidebar-tabs.json (8.5 KB)

  • [Edited] I just want to add if you have a lot of sidebar tabs, over three or more rows, using hover may prove difficult as you may be forced to cross other tabs after selecting one, thus selecting another. Best for smaller lists perhaps?

Still outstanding

I would like to see a way to do this while also dragging a payload so it can then dr4op it on a dropzone in the tab.

2 Likes

Sorry, but mouseenter doesn’t bubble. The event won’t reach a parent ($eventcatcher) element.

Any event you want to target with $eventcatcher, must support bubbling.

Hi @CodaCoder, you’re correct, of course.
I checked in an old revision where I had used it successfully, and it was the pointerover event that worked.

It’s been a while, but I recently had a need for tooltips that allow for WikiText formatting, or, well, any formatting. I found this old topic which gave me some ideas of what to do and what not to do to make a pure WikiText solution work. With v5.3.x, it is now possible to devise a custom widget that shows a given tooltip text – which can be WikiText – for its content.
Since mouseenter cannot be used, we have to debounce the mouseover and mouseout events that keep firing when moving the mouse within the content (especially bad with SVG images as the events fire not only for the parent element but for all of its children, i.e. lines, circles, whatever). Otherwise the tooltip will flicker or blink.
So, here, for posterity, the widget I came up with. Feel free to use, adapt and extend.

\widget $.tooltip(text:"", mode:"inline", state:"$:/state/tooltip")
	\whitespace trim
	\procedure show-tooltip()
		<$action-popup $state=<<state>> />
		<$action-deletetiddler $tiddler=<<state>> />
		<$action-popup $state=<<state>> $coords=<<tv-popup-coords>> />
	\end show-tooltip
	\procedure hide-tooltip()
		<$action-deletetiddler $tiddler=<<state>> />
	\end hide-tooltip
	<$eventcatcher selector=".tooltip-trigger"
		  $mouseover="""<$action-timeout delay=500 actions="<<show-tooltip>>" tid="$:/temp/timers/show-tooltip" /><$action-timeout clear={{$:/temp/timers/hide-tooltip}} />"""
		  $mouseout="""<$action-timeout delay=100 actions="<<hide-tooltip>>" tid="$:/temp/timers/hide-tooltip" /><$action-timeout clear={{$:/temp/timers/show-tooltip}} />""" >
		<div class="tooltip-anchor" style="display: block; position: relative;">
			<$reveal state=<<state>> type="popup" position="belowright" tag="div">
				<div class="tooltip-content" style="white-space: nowrap; overflow: hidden; background: white; border: 1px solid black; padding-inline: 4px;">
					<$transclude $variable="text" $mode=<<mode>> />
				</div>
			</$reveal>
			<span class="tooltip-trigger">
				<$slot $name="ts-raw" />
			</span>
		</div>
	</$eventcatcher>
\end

The parameters are

  • text: The tooltip (Wiki)Text
  • mode: Set to block if text should be parsed in block mode, i.e. to allow for lists, etc.
  • state: The state tiddler for the tooltip RevealWidget. This must be unique for each tooltip or multiple tooltips will show when hovering over their elements

Adding a parameter for the appearance delay should be straightforward, if needed.

The widget would be called like so:

\define tooltip-2-text()
This is a //tooltip// with a list:

* Item 1
* Item 2
* Item 3
\end

<$.tooltip text="This is a ''tooltip''" state="$:/state/tooltip/1">Text with tooltip</$.tooltip>

<$.tooltip text=<<tooltip-2-text>> mode="block" state="$:/state/tooltip/2"><$button class="tc-btn-invisible" style="font-size: 2rem;">{{$:/core/images/spiral}}</$button></$.tooltip>

yielding
image
and
image

The one huge drawback is of course the state tiddler which must be unique for each tooltip. I tried a couple of approaches to avoid this:

  • Using some form of time code. This only has ms resolution and might result in the same state tiddler on fast machines. Also, since the timeout tiddlers can change very quickly when moving the mouse, the whole thing would be redrawn multiple times with different time codes. This might lead to a lot of orphaned state tiddlers.
  • Random numbers suffer from the same problem with updates as they would be randomized each time.
  • Use of sha256[] on the tooltip text. This might not be unique enough and filters don’t have access to the ts-raw slot to scramble that, too.
  • Increasing a counter doesn’t work, since multiple widgets don’t know anything about each other or how may there are before them, or how many there are at all. State tiddlers are only created on hover, so they cannot know about the state tiddlers of other instances. Since no actions can be called until the mouse actions fire, no list of state tiddlers can be created or incremented.

There might be no way around manual state-tiddlering, I’m afraid, unless someone else comes up with a good idea.

2 Likes

Small update.

  1. I forgot to mention that this widget uses the $action-timeout widget from @EricShulman’s TiddlyTools library: action-timeout.js (TiddlyTools)
  2. When (if) PR #8919 is merged, the $eventcatcher can be used to trigger popups using the mouseenter and mouseleave events that trigger on its own DOM node (by not setting the selector attribute). In this scenario, we do not need to debounce all the mouseover and mouseout events and avoid all the updates of the tiddlers containing the timeout IDs.
  3. When calculating a hash over the tooltip text and the transclusion variable, the resulting ID should be pretty unique to distinguish the various tooltips that might appear, so only a single one will ever be revealed at a time. Should this in edge cases not be enough, a pepper attribute may be provided that is included in the hashed string, to make multiple otherwise identical hashes unique (by choosing a unique pepper value for any affected instances).
  4. When using absolute popup coordinates, there’s no longer the need to have a relatively positioned anchor element.
  5. Keeping the debouncing also for mouseenter and mouseleave avoids tooltips appearing spuriously when moving the mouse quickly across the triggering element.
  6. Moved the $reveal inside the $eventcatcher. Together with the debouncing from item 5 this keeps the tooltip open when moving the mouse from the trigger into it (quickly enough). This allows for actually clickable links in the tooltip.
  7. Changed the timer ID tiddlers to live in the $:/temp/volatile namespace to avoid frequent UI updates.

Here is the updated code:

\widget $.tooltip(text:"", mode:"inline", pepper:"")
	\whitespace trim
	\procedure show-tooltip()
		<$action-popup $state=<<state>> />
		<$action-deletetiddler $tiddler=<<state>> />
		<$action-popup $state=<<state>> $coords=<<tv-popup-abs-coords>> />
	\end show-tooltip
	\procedure hide-tooltip()
		<$action-deletetiddler $tiddler=<<state>> />
	\end hide-tooltip
	<$let state={{{ [[$:/state/tooltip/]] [<transclusion>addsuffix<text>addsuffix<pepper>sha256[32]] +[join[]] }}}>
		<$eventcatcher style="display: inline-flex; align-items: center; width: auto; height: auto;"
			  $mouseenter="""<$action-timeout delay=500 actions="<<show-tooltip>>" tid="$:/temp/volatile/timers/show-tooltip" /><$action-timeout clear={{$:/temp/volatile/timers/hide-tooltip}} />"""
			  $mouseleave="""<$action-timeout delay=100 actions="<<hide-tooltip>>" tid="$:/temp/volatile/timers/hide-tooltip" /><$action-timeout clear={{$:/temp/volatile/timers/show-tooltip}} />""" >
			<$slot $name="ts-raw" />
			<$reveal state=<<state>> type="popup" position="belowright" tag="div">
				<div class="tooltip-content">
					<$transclude $variable="text" $mode=<<mode>> />
				</div>
			</$reveal>
		</$eventcatcher>
	</$let>
\end

The inline styles are now kept to a minimum to make sure $eventcatcher hugs the ts-raw slot as tightly as possible. Tooltip styles are defined in a stylesheet somewhere else,

.tooltip-content {
	background: white;
	border: 1px solid black;
	padding-inline: 4px;
}

Update (2025-03-26): Added item 4-7 after some more tinkering.

for my own understanding, why can’t you use the qualify macro (https://tiddlywiki.com/#qualify%20Macro) for making unique state tiddler names?

By default, the <<qualify ...>> macro uses the transclusion variable to compute the value suffix that is added to the state tiddler title. For many use-cases, this is sufficient. However, if there are several instances of <<qualify ...>> used within the same content tiddler, then they can all have the same transclusion value.

To ensure that each tooltip has a truly unique state tiddler title, @yaisog has implemented his own custom equivalent to <<qualify ...>> that combines the transclusion variable PLUS the actual text of the tooltip and an optional “pepper” value which is then converted to a sha256 base64-encoded hash value.

-e

1 Like

(post deleted by author)

Turns out, there are a couple of cases where the tooltip might not show as expected. Mainly, if some other page element overlaps the area where the tooltip would show, so that it becomes obscured. A high z-index doesn’t always help if there are CSS block formatting contexts involved.
I’ve had this happen with tooltips in a left-side sidebar that get clipped by the story river.

A few iterations later, here is a more universal solution.

Taking a lesson from Implementing a contextmenu with wikitext, we can just put the $reveal containing the tooltip into the PageTemplate and set its state with our various $eventcatchers (one per element with a tooltip). The neat thing is that we no longer need to care about making the state tiddler names unique. Only one tooltip will ever be shown at a time, so we only need one state tiddler for our one dynamic tooltip.
The new tiddler becomes:

list-after: $:/core/ui/PageTemplate/story
tags: $:/tags/PageTemplate
title: $:/yaisog/ui/PageTemplate/tooltip
type: text/vnd.tiddlywiki

\whitespace trim

<$let popupState="$:/state/tooltip"
	  tooltipText={{{ [<popupState>get[data-text]] }}}
	  mode={{{ [<popupState>get[data-mode]] }}} >
	<$reveal state=<<popupState>> type="popup" tag="div" clamp="both">
		<$eventcatcher
			  $mouseenter="<$action-timeout clear={{$:/temp/volatile/timers/hide-tooltip}} />"
			  $mouseleave="""<$action-timeout delay=100 actions="<$action-deletetiddler $tiddler=<<popupState>> />" tid="$:/temp/volatile/timers/hide-tooltip" />""" >
			<div class="tooltip-content">
				<$transclude $variable="tooltipText" $mode=<<mode>> />
			</div>
		</$eventcatcher>
	</$reveal>
</$let>

and we remove the $reveal from the widget definition:

tags: $:/tags/Global
title: $:/yaisog/widgets/tooltip
type: text/vnd.tiddlywiki

\whitespace trim

\widget $.tooltip(text:"", mode:"inline")
	\function .bottom-edge() [<event-fromviewport-posy>subtract<event-fromselected-posy>add<tv-selectednode-height>]
	\function .right-edge() [<event-fromviewport-posx>subtract<event-fromselected-posx>add<tv-selectednode-width>]
	\procedure show-tooltip()
		<$action-popup $state=<<popupState>> />
		<$action-deletetiddler $tiddler=<<popupState>> />
		<$action-popup $state=<<popupState>> $coords={{{ [[@(]] [.right-edge[]] [[,]] [.bottom-edge[]] [[,0,0)]] +[join[]] }}} />
		<$action-setfield $tiddler=<<popupState>> data-text=<<text>> data-mode=<<mode>> />
	\end show-tooltip
	\procedure hide-tooltip()
		<$action-deletetiddler $tiddler=<<popupState>> />
	\end hide-tooltip
	<% if [<text>!is[blank]] %>
		<$let popupState="$:/state/tooltip">
			<$eventcatcher style="display: inline-flex; align-items: center; width: auto; height: auto;"
				  $mouseenter="""<$action-timeout delay=500 actions="<<show-tooltip>>" tid="$:/temp/volatile/timers/show-tooltip" /><$action-timeout clear={{$:/temp/volatile/timers/hide-tooltip}} />"""
				  $mouseleave="""<$action-timeout delay=100 actions="<<hide-tooltip>>" tid="$:/temp/volatile/timers/hide-tooltip" /><$action-timeout clear={{$:/temp/volatile/timers/show-tooltip}} />""" >
				<$slot $name="ts-raw" />
			</$eventcatcher>
		</$let>
	<% endif %>
\end

The information about the tooltip text and mode is passed into the $reveal by setting corresponding fields in the state tiddler.

Using the various variables supplied by the $eventcatcher to its actions, we manually calculate the position of the bottom right corner of the element and place the tooltip there - using tv-popup-abs-coords didn’t work for me in conjunction with scrolling.

The recyclable $reveal with the tooltip content also has an $eventcatcher the prevents the tooltip from disappearing when the mouse moves into it. Links within the tooltip will still work.

To create a tooltip, simply wrap the content that should receive the tooltip with the widget:

<$.tooltip text="This is a //tooltip//">This text has a tooltip</$.tooltip> while this has not.

I would have liked to set up a Sharing Edition link containing these tiddlers, but since we need the $action-timeout JavaScript widget and the modified $eventcatcher widget, it isn’t possible to share this way.