just_tooltip 0.5.1
just_tooltip: ^0.5.1 copied to clipboard
A lightweight, customizable Flutter tooltip with flexible placement, hover, tap & keyboard triggers, screen-reader support, programmatic control, and RTL support.
just_tooltip #
A lightweight, customizable Flutter tooltip widget. Combine direction (top/bottom/left/right) and alignment (start/center/end/startTargetCenter/endTargetCenter) for 20 positioning combinations with arrow indicators, viewport-aware auto-flipping, and programmatic control.
Features #
- 20 positioning combinations — 4 directions × 5 alignments
- Reusable theme —
JustTooltipThemegroups all visual styling - Arrow indicator — unified shape with border support
- Viewport overflow protection — auto-flip direction and clamp position
- Hover, tap & keyboard triggers — independently toggleable; Escape dismisses
- Screen readers — the tooltip's text is heard with its child
- Interactive tooltips — stays visible while hovering tooltip content
- Programmatic control — show/hide/toggle via controller
- Custom content — any widget via
tooltipBuilder - RTL support — start/end automatically swapped for top/bottom directions
- Single instance — only one tooltip visible at a time
Install #
dependencies:
just_tooltip: ^0.5.1
Basic Usage #
Pass a message string. Defaults to hover trigger, top-center position.
JustTooltip(
message: 'Hello!',
child: Text('Hover me'),
)
Anchoring to the pointer #
By default the visible part of the child's rect is both the hover region and the anchor, which is right for a button or an icon. For a child much wider than the pointer's neighbourhood — a table row, a wide card — the two roles come apart: its centre is wherever the scroll viewport happens to be centred, not where the user is looking.
anchor: TooltipAnchor.pointer keeps the whole child as the hover region, so the tooltip does not blink off as the cursor crosses it, and anchors the tooltip at the cursor:
JustTooltip(
anchor: TooltipAnchor.pointer,
tooltipBuilder: (context) => RowDetailsCard(row),
child: TableRow(row), // 3000px wide, scrolled horizontally
)
The anchor is captured when the tooltip appears and does not follow the cursor afterwards, so an interactive tooltip stays reachable. A tap-triggered tooltip anchors at the tap. A programmatic controller.show() with no pointer present falls back to the visible part of the child's rect; if the child is clipped away entirely it anchors at the clip edge the child lies beyond.
A child-anchored tooltip re-aims whenever its child moves — a scroll, a resize, a layout animation — and hides once the child is clipped out of sight. A pointer-anchored one does not: its anchor is a point at the cursor, and a pointer inside the child proves the child shows.
Against a point there are no target edges to align to, so alignment picks which of the tooltip's own edges lands on the pointer: start extends it to the trailing side, end to the leading side, center centres it.
Direction & Alignment #
direction controls which side the tooltip appears on. alignment controls where it aligns along that side.
JustTooltip(
message: 'Top left aligned',
direction: TooltipDirection.top,
alignment: TooltipAlignment.start,
child: MyWidget(),
)
20 combinations are available:
| direction | alignment | position |
|---|---|---|
top |
start |
above, left-aligned |
top |
center |
above, centered |
top |
end |
above, right-aligned |
top |
startTargetCenter |
above, left-aligned, arrow at target center |
top |
endTargetCenter |
above, right-aligned, arrow at target center |
bottom |
start |
below, left-aligned |
bottom |
center |
below, centered |
bottom |
end |
below, right-aligned |
bottom |
startTargetCenter |
below, left-aligned, arrow at target center |
bottom |
endTargetCenter |
below, right-aligned, arrow at target center |
left |
start |
left, top-aligned |
left |
center |
left, centered |
left |
end |
left, bottom-aligned |
left |
startTargetCenter |
left, top-aligned, arrow at target center |
left |
endTargetCenter |
left, bottom-aligned, arrow at target center |
right |
start |
right, top-aligned |
right |
center |
right, centered |
right |
end |
right, bottom-aligned |
right |
startTargetCenter |
right, top-aligned, arrow at target center |
right |
endTargetCenter |
right, bottom-aligned, arrow at target center |
In RTL environments, start/end are automatically swapped for top/bottom directions. The direction is the one around the JustTooltip, so an RTL section inside an LTR app mirrors its tooltips; the tooltip's content likewise inherits the Theme, DefaultTextStyle and MediaQuery around it.
Viewport Overflow Protection #
When there isn't enough space in the preferred direction, the tooltip automatically flips to the opposite side. The tooltip position is also clamped to stay within screen bounds.
Use screenMargin to control the minimum distance from viewport edges. This also affects the maximum size of the tooltip.
JustTooltip(
message: 'Safe tooltip',
direction: TooltipDirection.top,
screenMargin: 16.0, // 16px minimum distance from screen edges (default: 8.0)
child: MyWidget(),
)
Theme #
Use JustTooltipTheme to group all visual styling parameters. The theme is reusable across multiple tooltips.
// Define a reusable theme
const myTheme = JustTooltipTheme(
backgroundColor: Colors.black87,
textStyle: TextStyle(color: Colors.white),
showArrow: true,
borderColor: Colors.white,
borderWidth: 1.0,
);
// Reuse across widgets
JustTooltip(message: 'A', theme: myTheme, child: WidgetA())
JustTooltip(message: 'B', theme: myTheme, child: WidgetB())
Use copyWith() to derive variations:
final warningTheme = myTheme.copyWith(
backgroundColor: Colors.orange,
borderColor: Colors.deepOrange,
);
Bringing your own surface #
When tooltipBuilder returns a widget that already draws its own surface — a card, a
popover, a menu — the tooltip should contribute positioning and nothing else. Use
JustTooltipTheme.bare(), which has no background, padding, shadow, border, or arrow:
JustTooltip(
theme: const JustTooltipTheme.bare(),
tooltipBuilder: (_) => MyCard(),
child: MyWidget(),
)
Prefer this over zeroing backgroundColor, padding, and elevation by hand: omitting
any one of the three silently leaves chrome behind.
bare() is a starting point, not a restriction. copyWith() layers styling back on — a
transparent background with a borderColor, for instance, gives an outlined tooltip,
arrow included.
Arrow #
Enable showArrow in the theme to display a triangular arrow pointing at the target widget. The arrow is rendered as a unified shape with the tooltip body, so background, shadow, and border all follow the combined outline.
JustTooltip(
message: 'With arrow',
theme: JustTooltipTheme(
showArrow: true,
arrowBaseWidth: 12.0, // arrow base width (default: 12.0)
arrowLength: 6.0, // arrow protrusion length (default: 6.0)
),
child: MyWidget(),
)
For start/end alignments, arrowPositionRatio controls where the arrow sits along the tooltip edge (0.0 = near the aligned edge, 1.0 = far end).
JustTooltip(
message: 'Arrow near edge',
alignment: TooltipAlignment.start,
theme: JustTooltipTheme(
showArrow: true,
arrowPositionRatio: 0.25, // 25% from the start edge (default: 0.25)
),
child: MyWidget(),
)
Use startTargetCenter or endTargetCenter to keep the arrow pointing at the target widget's center. This is useful when the tooltip is wider than the target.
JustTooltip(
message: 'A long tooltip message that is wider than the target',
alignment: TooltipAlignment.startTargetCenter,
theme: JustTooltipTheme(showArrow: true),
child: SmallIcon(),
)
The arrow auto-flips along with the tooltip when direction changes due to viewport constraints.
Border #
Add an outline that follows the tooltip shape, including the arrow.
JustTooltip(
message: 'Bordered',
theme: JustTooltipTheme(
showArrow: true,
borderColor: Colors.white,
borderWidth: 1.5,
),
child: MyWidget(),
)
Trigger #
Hover and tap triggers can be toggled independently.
// Tap only
JustTooltip(
message: 'Tap tooltip',
tap: TooltipTap.claim,
enableHover: false,
child: MyButton(),
)
TooltipTap.claim takes the tap, so a tap handler around the tooltip does not
fire. Inside something tappable — a list row, a card — use TooltipTap.observe:
the tooltip toggles and the surrounding tap fires. A drag or scroll that
starts on the child does not toggle it.
GestureDetector(
onTap: selectRow,
child: JustTooltip(
message: 'Full name',
tap: TooltipTap.observe,
child: Text(label),
),
)
Accessibility #
A screen reader hears the tooltip's text with its child, without opening anything, as with Flutter's Tooltip. The text is semanticsTooltip when given, otherwise message; a tooltipBuilder tooltip is heard only through semanticsTooltip. Nested, only the innermost tooltip is heard.
JustTooltip(
tooltipBuilder: (_) => UserCard(user),
semanticsTooltip: user.fullName, // what a screen reader hears
child: Avatar(user),
)
Set excludeFromSemantics: true where the child already says what the tooltip says — a cell whose tooltip repeats its text — and on a tooltip a screen reader cannot reach (under ExcludeSemantics, Offstage or a hidden IndexedStack child), which would otherwise silence the tooltips around it.
enableFocus: true shows the tooltip while keyboard focus is inside the child — focus that arrived by a key such as Tab, not by a click or a tap. It behaves as a hover does: waitDuration, nesting and content all apply, and losing focus hides it.
JustTooltip(
message: 'Save (Ctrl+S)',
enableFocus: true,
child: IconButton(icon: Icon(Icons.save), onPressed: save),
)
Escape closes the most recently shown tooltip, however it was opened, one per press. When focus is inside that tooltip the key is consumed, so a dialog behind it closes on the next press.
Controller #
Use JustTooltipController for programmatic control.
final controller = JustTooltipController();
// Widget
JustTooltip(
message: 'Controlled',
controller: controller,
enableHover: false,
child: MyWidget(),
)
// Control
controller.show();
controller.hide();
controller.toggle();
// Read the live visibility (reflects hover/tap/auto-hide too)
if (controller.isShowing) { /* ... */ }
The tooltip is the single source of truth for its visibility — the controller drives it and reads its live state through isShowing. There is no separate controller state to keep in sync, so show()/hide()/toggle() behave correctly no matter how the tooltip was last dismissed (hover-out, tap, or auto-hide). Calling show() before the widget has mounted is honoured once it mounts.
To observe visibility changes, use the widget's onShow/onHide callbacks.
Migrating from ≤ 0.2.x:
JustTooltipControlleris no longer aChangeNotifier. Replace the removedshouldShowgetter withisShowing, and replaceaddListenerwith the widget'sonShow/onHidecallbacks. See the migration section below.
Single Instance & Scoping #
By default, showing any tooltip dismisses any other — only one is visible at a time, app-wide. This is enforced by a shared TooltipRegistry.
To scope the policy to a group (so tooltips in different groups don't dismiss each other), pass a shared TooltipRegistry instance:
final registry = TooltipRegistry();
JustTooltip(message: 'A', registry: registry, child: WidgetA())
JustTooltip(message: 'B', registry: registry, child: WidgetB())
// A and B dismiss each other, but not tooltips outside this registry.
Omit registry to use the app-global default. A tooltip inside another tooltip's tooltipBuilder content defaults to a registry that host gives its content instead, so it can show without dismissing the tooltip it is drawn in.
Custom Content #
Use tooltipBuilder to render any widget instead of plain text. The caller is responsible for managing the size of the content.
JustTooltip(
tooltipBuilder: (context) => Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.info, color: Colors.white, size: 16),
SizedBox(width: 8),
Text('Custom content', style: TextStyle(color: Colors.white)),
],
),
child: MyWidget(),
)
Cross-Axis Offset #
Use crossAxisOffset to shift the tooltip along the cross-axis from the aligned edge. For start/end and their *TargetCenter variants, a positive value pushes the tooltip inward (toward center). For center, a positive value moves toward the end direction.
JustTooltip(
message: 'Shifted inward',
direction: TooltipDirection.top,
alignment: TooltipAlignment.start,
crossAxisOffset: 10, // left-aligned but shifted 10px to the right
child: MyWidget(),
)
| direction | alignment | crossAxisOffset: 10 |
|---|---|---|
| top/bottom | start |
shifts right |
| top/bottom | center |
shifts right |
| top/bottom | end |
shifts left (inward) |
| left/right | start |
shifts down |
| left/right | center |
shifts down |
| left/right | end |
shifts up (inward) |
Interactive & Timing #
interactive keeps the tooltip visible when the cursor moves from the child to the tooltip itself — and back again. Useful for selectable text or clickable content inside the tooltip. When combined with showDuration, the auto-hide timer pauses while the cursor is on the tooltip.
Either crossing is covered by a 100 ms grace window, so the gap set by offset never hides the tooltip mid-journey.
With interactive: false the tooltip body is transparent to the pointer: hovers and taps pass through it to whatever lies beneath. A body that has to be placed over its own child — one too large for either side — therefore stays shown while the cursor rests on the child, and content inside it cannot be clicked.
waitDuration adds a delay before the tooltip appears. showDuration auto-hides the tooltip after a set time.
JustTooltip(
message: 'Interactive tooltip',
interactive: true, // stay visible when hovering tooltip (default: true)
waitDuration: Duration(milliseconds: 300), // delay before showing
showDuration: Duration(seconds: 3), // auto-hide after 3s
child: MyWidget(),
)
Animation #
Control the show/hide animation type, curve, and fine-tune parameters.
JustTooltip(
message: 'Animated',
animation: TooltipAnimation.fadeScale,
animationCurve: Curves.elasticOut,
animationDuration: Duration(milliseconds: 300),
child: MyWidget(),
)
Available animation types:
| Type | Description |
|---|---|
none |
No animation, appears instantly |
fade |
Opacity fade (default) |
scale |
Scale from center |
slide |
Slide in from the opposite side of direction |
fadeScale |
Fade + scale combined |
fadeSlide |
Fade + slide combined |
rotation |
Fade + rotation combined |
Fine-tune each animation with optional parameters:
JustTooltip(
message: 'Fine-tuned',
animation: TooltipAnimation.fadeScale,
animationCurve: Curves.bounceOut,
fadeBegin: 0.0, // starting opacity (default: 0.0)
scaleBegin: 0.8, // starting scale (default: 0.0) — 0.8 gives a subtle grow
slideOffset: 0.3, // slide distance ratio (default: 0.3)
rotationBegin: -0.05, // starting rotation in turns (default: -0.05)
child: MyWidget(),
)
Box Shadow #
Use boxShadow in the theme for fine-grained shadow control. When provided, elevation is ignored.
JustTooltip(
message: 'Custom shadow',
theme: JustTooltipTheme(
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.3),
blurRadius: 8.0,
spreadRadius: 1.0,
offset: Offset(0, 4),
),
],
),
child: MyWidget(),
)
Styling #
All visual styling is configured through JustTooltipTheme:
JustTooltip(
message: 'Styled',
offset: 12.0, // gap between child and tooltip
theme: JustTooltipTheme(
backgroundColor: Colors.indigo,
borderRadius: BorderRadius.circular(12),
padding: EdgeInsets.all(16),
elevation: 8.0,
textStyle: TextStyle(color: Colors.white, fontSize: 16),
borderColor: Colors.white,
borderWidth: 1.0,
showArrow: true,
),
child: MyWidget(),
)
Hide on Empty Message #
By default, the tooltip is suppressed when message is an empty string. This prevents showing an empty tooltip box when the message content is not yet available or intentionally cleared.
// Tooltip will NOT appear when message is ''
JustTooltip(
message: '', // empty → tooltip suppressed
child: MyWidget(),
)
// Explicitly allow empty-message tooltips
JustTooltip(
message: '',
hideOnEmptyMessage: false, // shows an empty tooltip box
child: MyWidget(),
)
This only affects message-based tooltips. When tooltipBuilder is used, this parameter has no effect.
Callbacks #
JustTooltip(
message: 'With callbacks',
onShow: () => print('shown'),
onHide: () => print('hidden'),
child: MyWidget(),
)
API Reference #
JustTooltip #
| Parameter | Type | Default | Description |
|---|---|---|---|
child |
Widget |
required | Target widget the tooltip is anchored to |
message |
String? |
null |
Text content (one of message or tooltipBuilder required) |
tooltipBuilder |
WidgetBuilder? |
null |
Custom widget builder |
direction |
TooltipDirection |
top |
Which side the tooltip appears on |
alignment |
TooltipAlignment |
center |
Alignment along the cross-axis (start, center, end, startTargetCenter, endTargetCenter) |
offset |
double |
8.0 |
Gap between child and tooltip |
crossAxisOffset |
double |
0.0 |
Shift along the cross-axis (inward for start/end) |
screenMargin |
double |
8.0 |
Minimum distance from viewport edges |
theme |
JustTooltipTheme |
JustTooltipTheme() |
Visual styling (see below) |
controller |
JustTooltipController? |
null |
Programmatic control |
tap |
TooltipTap? |
null |
Tap trigger: none, claim (takes the tap) or observe (leaves it to ancestors) |
enableTap |
bool |
false |
Deprecated — use tap: TooltipTap.claim |
enableHover |
bool |
true |
Hover trigger |
enableFocus |
bool |
false |
Keyboard-focus trigger (focus that arrives in the child by a key, such as Tab). Escape dismisses any shown tooltip regardless |
interactive |
bool |
true |
Keep tooltip visible when hovering over it; false makes the body transparent to the pointer |
waitDuration |
Duration? |
null |
Delay before tooltip appears |
showDuration |
Duration? |
null |
Auto-hide after this duration |
animation |
TooltipAnimation |
fade |
Animation type (none, fade, scale, slide, fadeScale, fadeSlide, rotation) |
animationCurve |
Curve? |
null |
Curve applied to the animation |
fadeBegin |
double |
0.0 |
Starting opacity for fade-based animations |
scaleBegin |
double |
0.0 |
Starting scale for scale-based animations |
slideOffset |
double |
0.3 |
Slide distance as a fraction of tooltip size |
rotationBegin |
double |
-0.05 |
Starting rotation in turns |
animationDuration |
Duration |
150ms |
Animation duration |
hideOnEmptyMessage |
bool |
true |
Suppress tooltip when message is empty |
semanticsTooltip |
String? |
null |
What a screen reader hears, in place of message; the only text a tooltipBuilder tooltip is heard by |
excludeFromSemantics |
bool |
false |
Keep screen readers from hearing the tooltip (for a child that already says it, or a tooltip a screen reader cannot reach) |
onShow |
VoidCallback? |
null |
Called when tooltip is shown |
onHide |
VoidCallback? |
null |
Called when tooltip is hidden |
registry |
TooltipRegistry? |
null |
Scopes the "one visible at a time" policy; null uses the app-global default, or the host's inside tooltip content |
JustTooltipTheme #
| Parameter | Type | Default | Description |
|---|---|---|---|
backgroundColor |
Color |
Color(0xFF616161) |
Background color |
borderRadius |
BorderRadius |
circular(6) |
Corner radius |
padding |
EdgeInsets |
h:12, v:8 |
Inner padding |
elevation |
double |
4.0 |
Shadow elevation (ignored if boxShadow is set) |
boxShadow |
List<BoxShadow>? |
null |
Custom box shadows |
borderColor |
Color? |
null |
Border color |
borderWidth |
double |
0.0 |
Border stroke width |
textStyle |
TextStyle? |
null |
Text style for message |
showArrow |
bool |
false |
Display triangular arrow pointing at target |
arrowBaseWidth |
double |
12.0 |
Arrow base width |
arrowLength |
double |
6.0 |
Arrow protrusion length |
arrowPositionRatio |
double |
0.25 |
Arrow position along the edge for start/end (0.0-1.0) |
JustTooltipTheme.bare() is a const constructor for a theme that draws no chrome at all —
see Bringing your own surface.
Upgrading to 0.5.1 #
No API was removed and no signature changed. Two defaults are new for every tooltip:
- A screen reader hears the tooltip's text on its child. Where the child already says it, set
excludeFromSemantics: true, or the text is read twice. - Escape closes a shown tooltip, whether or not
enableFocusis set. A consumer that keeps a tooltip up on purpose re-shows it.
Migration to 0.5.0 #
Requires Flutter 3.32 (Dart 3.8), up from 3.13: the tooltip is positioned during layout through OverlayPortal.overlayChildLayoutBuilder, so a shown tooltip follows a moving child in the same frame instead of one frame late. ^0.4.4 does not admit 0.5.0: raise your constraint to ^0.5.0.
enableTapis deprecated in favour oftap: TooltipTap.claim, which behaves identically. Code still passingenableTapgets adeprecated_member_useinfo, which failsflutter analyzeby default. ReplaceenableTap: xwithtap: x ? TooltipTap.claim : TooltipTap.none. Where the tooltip sits inside something tappable — a selectable row — considerTooltipTap.observe, which toggles the tooltip without taking the tap from the ancestor (screen readers get no tap action in that mode).- A tooltip follows the
Directionality,Theme,DefaultTextStyleandMediaQueryaround it, not only those above the app'sOverlay. An RTL section inside an LTR app now mirrors its tooltips, and a localThemeor text scale reachestooltipBuilder. If a tooltip should keep the app-level look under a local override, set what it needs insidetooltipBuilder, or place theJustTooltipoutside the override. A tooltip inside another tooltip's content still takes no part in nesting suppression, and the content never joins a surroundingSelectionArea. - A positive
crossAxisOffsetmoves anendTargetCentertooltip inward, as it does forend(in RTL this includesstartTargetCenterontop/bottom). A value tuned against the old direction now shifts the other way: flip its sign. - An
interactive: falsetooltip body is transparent to the pointer. Hovers and taps pass through it to whatever lies beneath, so a tap on such a tooltip now reaches the widget under it, and buttons inside its content can no longer be pressed. A body too large for either side of its child no longer flickers under the cursor. Useinteractive: true(the default) for content the user should reach. - A tooltip inside another tooltip's
tooltipBuildercontent no longer closes its host. Without an explicitregistryit now shares a registry with the other tooltips in that content, not with the app, so it shows beside the host. If you gave such tooltips their ownTooltipRegistry()to get this, you can drop it.
Migration to 0.4.4 #
No API was removed, no signature changed, and the Flutter floor is unchanged. Two behaviours changed, both driven by whether a tooltip has anything to draw — a tooltipBuilder, a non-empty message, or hideOnEmptyMessage: false:
- A tooltip with nothing to draw no longer suppresses its ancestors. Migration to 0.4.0 introduced "nested tooltips show only the innermost one under the pointer"; that rule now reads "the innermost one that has something to draw". Previously an empty-message tooltip nested inside another took the ancestor's place and then drew nothing, so hovering it showed neither. Nothing that displayed correctly moves: a tooltip that draws suppresses exactly as before,
hideOnEmptyMessage: falseincluded. - A shown tooltip follows its own configuration. Changing
message,tooltipBuilder,theme,directionoralignmentwhile the tooltip is on screen now redraws it on the next frame, where it used to keep whatever it was built with. Emptying themessageof a shown tooltip therefore hides it rather than blanking it — however it was opened,controller.show()included, since no pointer exit will ever undo a programmatic show.
That last point is about content, not suppression. Suppression still applies to hover only, exactly as Migration to 0.4.0 says: a descendant's hover never suppresses a programmatic controller.show().
If you added a local guard that refuses to build a JustTooltip with an empty message — to stop it silencing an enclosing tooltip — you can drop it. That is now the package's own behaviour.
Migration to 0.4.2 #
Requires Flutter 3.13 (Dart 3.1), up from 3.10: the ancestor clip walk reads RenderObject.parent, which was AbstractNode? on older versions.
No API was removed and no signature changed. Two behaviours changed, both for TooltipAnchor.child only — TooltipAnchor.pointer is untouched:
- The anchor is now the child's visible rect, its painted rect intersected with every clip an ancestor applies. Any
JustTooltipinside aListVieworSingleChildScrollViewmay therefore shift, always toward the visible part. A child nothing clips is unaffected. Previously a child wider than the viewport clipping it aimed its tooltip at a centre off screen — the bug this fixes, so nothing correct moves. - A visible tooltip re-aims when its child moves — a scroll, a resize, a layout animation — and hides once a child it was pointing at is clipped entirely out of sight. A
controller.show()against a child that was already invisible anchors at the clip edge and waits for it, rather than refusing.
If you adopted TooltipAnchor.pointer purely to work around the off-screen anchor, the default is now safe. Keep pointer where you actually want the tooltip beside the cursor: on a child much wider than the pointer's neighbourhood, the visible centre is the viewport's centre, still not where the user is looking.
Migration to 0.4.0 #
No API was removed and no signature changed, so most apps upgrade by bumping the constraint. One behaviour changed:
- Nested tooltips now show only the innermost one under the pointer. Previously this mostly held anyway, because the "one tooltip at a time" registry dismissed the outer tooltip once the inner one appeared. It broke when an ancestor had a
waitDuration(its timer fired later and replaced the inner tooltip), when the cursor moved from the inner child back onto the outer one (neither showed), and when the two tooltips were scoped to separateTooltipRegistryinstances (both showed). If you deliberately relied on that last case to display a nested tooltip alongside its ancestor, it no longer happens. Suppression applies to hover only — a programmaticcontroller.show()is unaffected.
Migration to 0.3.0 #
JustTooltipController is no longer a ChangeNotifier; it drives the tooltip and reads its live state instead of holding its own.
// Before (≤ 0.2.x)
if (controller.shouldShow) { ... }
controller.addListener(() { /* visibility changed */ });
// After (0.3.0)
if (controller.isShowing) { ... } // shouldShow → isShowing
// observe via the widget's callbacks instead of addListener:
JustTooltip(onShow: () {...}, onHide: () {...}, controller: controller, child: ...)
shouldShow→isShowing(now reflects hover/tap/auto-hide too).addListener/removeListener/disposeremoved — useonShow/onHide. You no longer need to dispose the controller.- The internal
resetShouldShow()was removed (it is no longer needed). JustTooltipPositionDelegate,TooltipShapePainter, andJustTooltipOverlayare no longer exported (internal implementation details).
Migration from 0.1.x #
Individual styling parameters have been moved into JustTooltipTheme:
// Before (0.1.x)
JustTooltip(
message: 'Hello',
backgroundColor: Colors.blue,
showArrow: true,
borderColor: Colors.white,
borderWidth: 1.0,
child: MyWidget(),
)
// After (0.2.0)
JustTooltip(
message: 'Hello',
theme: JustTooltipTheme(
backgroundColor: Colors.blue,
showArrow: true,
borderColor: Colors.white,
borderWidth: 1.0,
),
child: MyWidget(),
)
Example #
An interactive playground app is included in the example/ folder.
cd example
flutter run
A second entry point demonstrates pointer anchoring and nested tooltips on a horizontally scrolling table whose rows are far wider than the viewport — the case TooltipAnchor.pointer exists for. Flip the switch in its app bar to watch the card snap back to the centre of whatever part of the row is on screen: visible, but unrelated to where you are pointing along the row.
cd example
flutter run -t lib/spike_wide_row.dart