Skip to content

Commit f307192

Browse files
authoredNov 9, 2023
Merge pull request #20 from svelte-plugins/add-prop-driven-visibility
refactor(tooltips): add show prop to interface for manual control of showing/hiding
·
v3.0.3v0.1.9
2 parents cb9068c + 934079a commit f307192

11 files changed

+359
-241
lines changed
 

‎README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,15 @@ Checkout out my <u use:tooltip={{ content: 'Hello World!' }}>tooltip</u>
4242
### Props
4343
| Prop | Description | Value |
4444
| :----------- | :------------------------------------------------------------------ | :---------------------------------------------- |
45-
| action | The action that triggers the tooltip (hover | click) | `string` (default: `hover`) |
45+
| action | The action that triggers the tooltip (hover | click | prop) | `string` (default: `hover`) |
4646
| animation | The animation to apply to the tooltip | `string` (default: ``) |
4747
| arrow | If `false`, the tooltip arrow will not be shown. | `boolean` (default: `true`) |
4848
| autoPosition | Adjust tooltip position if viewport clipping occurs | `string` (default: `false`) |
4949
| content | The string or object containing componentref and props | `string` | `object` component (default: ``) |
5050
| maxWidth | The max allowable width of the tooltip content | `number` (default: `200`) |
5151
| position | The position where the tooltip should appear relative to its parent | `string` (default: `top`) |
5252
| theme | The CSS theme class name | `string` (default: ``) |
53+
| show | Allows you to manually control the tooltip visibility | `boolean` (default: `false`) |
5354
| style | The object containing theme variable overrides | `object` (default: `null`) |
5455

5556
#### Using components as content

‎docs/src/App.svelte

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import Prism from 'svelte-prismjs';
33
import { Tooltip, tooltip } from '@svelte-plugins/tooltips';
44
import ComponentAsContent from './ComponentAsContent.svelte';
5+
6+
let showTooltip = false;
57
</script>
68

79
<main>
@@ -54,7 +56,7 @@
5456
</div>
5557

5658
<div class="example">
57-
<p>This tooltip should appear to the <u use:tooltip={{ content: { component: ComponentAsContent, props: { title: 'Title from props' }}, position: 'left', style: { backgroundColor: 'blue' } }}>left</u> and render the passed component as the tooltip content.</p>
59+
<p>This tooltip should appear to the <u use:tooltip={{ content: { component: ComponentAsContent, props: { title: 'Title from props' }}, position: 'left', animation: 'slide', style: { backgroundColor: 'blue' } }}>left</u> and render the passed component as the tooltip content.</p>
5860
<Prism showLineNumbers={true} code={`
5961
<script>
6062
import ComponentAsContent from './ComponentAsContent.svelte';
@@ -86,6 +88,35 @@
8688
This tooltip should appear on the <Tooltip content="<b>Tooltip Top</b><p>This is an example of using HTML and content wrapping.</p>" position="top" animation="slide" arrow={false}><i>top</i></Tooltip> when you hover.
8789
</div>
8890

91+
<div class="example">
92+
This tooltip should appear <Tooltip content="<b>Tooltip Top</b><p>This is an example of using the 'show' prop.</p>" position="top" animation="slide" bind:show={showTooltip} autoPosition arrow={false} action="prop">on top</Tooltip> when the show button is clicked
93+
<button on:click={() => (showTooltip = true)}>Show</button>
94+
<button on:click={() => (showTooltip = false)}>Hide</button>
95+
96+
<Prism showLineNumbers={true} code={`
97+
98+
<script>
99+
import { Tooltip } from '@svelte-plugins/tooltips';
100+
101+
let showTooltip = false;
102+
</script>
103+
104+
<Tooltip
105+
content="<b>Tooltip Top</b><p>This is an example of using the 'show' prop.</p>"
106+
position="top"
107+
animation="slide"
108+
bind:show={showTooltip}
109+
autoPosition
110+
arrow={false}
111+
action="prop">
112+
Should show here
113+
</Tooltip>
114+
115+
<button on:click={() => (showTooltip = true)}>Show</button>
116+
<button on:click={() => (showTooltip = false)}>Hide</button>
117+
`} />
118+
</div>
119+
89120
<div class="example">
90121
<p>
91122
This tooltip should appear to the
@@ -94,8 +125,9 @@
94125
position="right"
95126
action="click"
96127
theme="tooltip-theme">
97-
<b>right</b> when clicked.
128+
<b>right</b>
98129
</Tooltip>
130+
when clicked.
99131
</p>
100132
<Prism showLineNumbers={true} code={`
101133
<Tooltip

‎package-lock.json

Lines changed: 128 additions & 124 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@svelte-plugins/tooltips",
3-
"version": "0.1.8",
3+
"version": "0.1.9",
44
"license": "MIT",
55
"description": "A simple tooltip action and component designed for Svelte.",
66
"author": "Kieran Boyle (https://github.com/dysfunc)",

‎src/action-tooltip.snap.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ exports[`Components: Tooltip should render the component 1`] = `
1010
Hello World!
1111
1212
</div>
13+
1314
</div>
1415
</body>
1516
`;

‎src/action-tooltip.svelte

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,76 @@
55
import { formatVariableKey, getMinWidth, isInViewport } from './helpers';
66
import { inverse } from './constants';
77
8+
/** @type {'hover' | 'click' | 'prop' | string} */
89
export let action = 'hover';
10+
11+
/** @type {string | {component: any, props?: Record<string, any>}} */
912
export let content = '';
13+
14+
/** @type {'left' | string} */
1015
export let align = 'left';
16+
17+
/** @type {'top' | string} */
1118
export let position = 'top';
19+
20+
/** @type {number} */
1221
export let maxWidth = 200;
13-
/**
14-
* @type {{ [x: string]: any; } | null}
15-
*/
16-
export let style = null;
22+
23+
/** @type {Record<string, string> | null} */
24+
export let style = null;
25+
26+
/** @type {string} */
1727
export let theme = '';
28+
29+
/** @type {string} */
1830
export let animation = '';
31+
32+
/** @type {boolean} */
1933
export let arrow = true;
34+
35+
/** @type {boolean} */
2036
export let autoPosition = false;
2137
22-
/**
23-
* @type {HTMLDivElement | null}
24-
*/
25-
let ref = null;
38+
/** @type {boolean} */
39+
export let show = false;
40+
41+
/** @type {HTMLDivElement | null} */
42+
let tooltipRef = null;
43+
44+
/** @type {number} */
2645
let minWidth = 0;
27-
/**
28-
* @type {{ $destroy: () => void; } | null}
29-
*/
46+
47+
/** @type {any} */
3048
let component = null;
31-
/**
32-
* @type {string | null}
33-
*/
49+
50+
/** @type {string | null} */
3451
let animationEffect = null;
35-
let show = false;
3652
37-
onMount(() => {
38-
const delay = animation ? 200 : 0;
53+
/** @type {boolean} */
54+
let visible = false;
3955
40-
if (ref !== null) {
56+
const delay = animation ? 200 : 0;
57+
58+
onMount(() => {
59+
if (tooltipRef !== null) {
4160
if (isComponent && !component) {
4261
// @ts-ignore
43-
component = new content.component({ target: ref, props: { action, ...content.props } });
62+
component = new content.component({ target: tooltipRef, props: { action, ...content.props } });
4463
}
4564
46-
minWidth = getMinWidth(ref, maxWidth);
65+
minWidth = getMinWidth(tooltipRef, maxWidth);
4766
4867
if (style && typeof style === 'object') {
4968
for (let prop in style) {
5069
const key = formatVariableKey(prop);
5170
const value = style[prop];
5271
53-
ref.style.setProperty(`--tooltip-${key}`, value);
72+
tooltipRef.style.setProperty(`--tooltip-${key}`, value);
5473
}
5574
}
5675
}
5776
58-
if (autoPosition && !isInViewport(ref)) {
77+
if (autoPosition && !isInViewport(tooltipRef)) {
5978
// @ts-ignore
6079
position = inverse[position];
6180
}
@@ -64,7 +83,7 @@
6483
animationEffect = animation;
6584
}
6685
67-
setTimeout(() => (show = true), delay);
86+
setTimeout(() => (visible = true), delay);
6887
});
6988
7089
onDestroy(() => {
@@ -75,13 +94,14 @@
7594
});
7695
7796
$: isComponent = typeof content === 'object';
97+
$: tooltipRef && show ? setTimeout(() => (visible = true), delay) : (visible = false);
7898
</script>
7999

80100
{#if content}
81101
<div
82-
bind:this={ref}
102+
bind:this={tooltipRef}
83103
class="tooltip animation-{animationEffect} {position} {theme}"
84-
class:show
104+
class:show={visible}
85105
class:arrowless={!arrow}
86106
style="min-width: {minWidth}px; max-width: {maxWidth}px; text-align: {align};"
87107
>

‎src/action-tooltip.svelte.d.ts

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,70 @@ import type { SvelteComponentTyped } from 'svelte';
22

33
export interface ComponentProps {
44
/**
5-
* The content of the tooltip.
6-
* @default ''
5+
* The action to trigger the tooltip
6+
* @default 'hover'
77
*/
8-
content?: string;
8+
action: 'hover' | 'click' | 'prop' | string;
99

1010
/**
11-
* The position of the tooltip.
12-
* Allowed values are 'top', 'bottom', 'left' or 'right'.
13-
* @default 'top'
11+
* The alignment of the tooltip.
12+
* @default 'left'
1413
*/
15-
position?: string;
14+
align?: 'left' | 'right' | 'center' | string;
1615

1716
/**
18-
* The maximum width of the tooltip.
19-
* @default 200
17+
* The animation style of the tooltip.
18+
* @default ''
2019
*/
21-
maxWidth?: number;
20+
animation?: string;
2221

2322
/**
24-
* The style of the tooltip.
25-
* @default null
23+
* Whether to show the arrow of the tooltip.
24+
* @default true
2625
*/
27-
style?: undefined;
26+
arrow?: boolean;
2827

2928
/**
30-
* The theme of the tooltip.
31-
* @default ''
29+
* Whether to automatically position the tooltip when clipping occurs.
30+
* @default false
3231
*/
33-
theme?: string;
32+
autoPosition?: boolean;
3433

3534
/**
36-
* The animation style of the tooltip.
35+
* The content of the tooltip.
3736
* @default ''
3837
*/
39-
animation?: string;
38+
content?: string;
4039

4140
/**
42-
* Whether to show the arrow of the tooltip.
43-
* @default true
41+
* The maximum width of the tooltip.
42+
* @default 200
4443
*/
45-
arrow?: boolean;
44+
maxWidth?: number;
4645

4746
/**
48-
* Whether to automatically position the tooltip.
47+
* The position of the tooltip.
48+
* @default 'top'
49+
*/
50+
position?: 'bottom' | 'left' | 'right' | 'top' | string;
51+
52+
/**
53+
* Control the visibility of the tooltip.
4954
* @default false
5055
*/
51-
autoPosition?: boolean;
56+
show?: boolean;
57+
58+
/**
59+
* The style of the tooltip.
60+
* @default null
61+
*/
62+
style?: undefined;
63+
64+
/**
65+
* The theme of the tooltip.
66+
* @default ''
67+
*/
68+
theme?: string;
5269
}
5370

5471
export default class Component extends SvelteComponentTyped<

‎src/action.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import Tooltip from './action-tooltip.svelte';
22

33
export const tooltip = (element, props) => {
4-
54
let component = null;
65
let title = element.getAttribute('title');
76
let action = props?.action || element.getAttribute('action') || 'hover';
@@ -17,13 +16,13 @@ export const tooltip = (element, props) => {
1716

1817
const onClick = () => {
1918
if (component) {
20-
onMouseLeave();
19+
onHide();
2120
} else {
22-
onMouseEnter();
21+
onShow();
2322
}
2423
};
2524

26-
const onMouseEnter = () => {
25+
const onShow = () => {
2726
if (!component) {
2827
component = new Tooltip({
2928
target: element,
@@ -32,7 +31,7 @@ export const tooltip = (element, props) => {
3231
}
3332
};
3433

35-
const onMouseLeave = () => {
34+
const onHide = () => {
3635
if (component) {
3736
component.$destroy();
3837
component = null;
@@ -45,25 +44,31 @@ export const tooltip = (element, props) => {
4544

4645
if (action === 'click') {
4746
element.addEventListener('click', onClick);
48-
} else {
49-
element.addEventListener('mouseenter', onMouseEnter);
50-
element.addEventListener('mouseleave', onMouseLeave);
47+
}
48+
49+
if (action === 'hover') {
50+
element.addEventListener('mouseenter', onShow);
51+
element.addEventListener('mouseleave', onHide);
5152
}
5253
}
5354
}
5455

5556
const removeListeners = () => {
5657
if (element !== null) {
5758
element.removeEventListener('click', onClick);
58-
element.removeEventListener('mouseenter', onMouseEnter);
59-
element.removeEventListener('mouseleave', onMouseLeave);
59+
element.removeEventListener('mouseenter', onShow);
60+
element.removeEventListener('mouseleave', onHide);
6061
}
6162
};
6263

6364
addListeners();
6465

6566
element.style.position = 'relative';
6667

68+
if (props.show) {
69+
onShow();
70+
}
71+
6772
return {
6873
destroy() {
6974
removeListeners();

‎src/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export { default } from "./tooltip.svelte";
1+
export { default as Tooltip } from './tooltip.svelte';
2+
export { tooltip } from './action';

‎src/tooltip.svelte

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,72 @@
55
import { formatVariableKey, getMinWidth, isInViewport } from './helpers';
66
import { inverse } from './constants';
77
8+
/** @type {'hover' | 'click' | 'prop' | string} */
89
export let action = 'hover';
10+
11+
/** @type {string | {component: any, props?: Record<string, any>}} */
912
export let content = '';
13+
14+
/** @type {'left' | 'center' | 'right' | string} */
1015
export let align = 'left';
16+
17+
/** @type {'top' | 'bottom' | 'left' | 'right' | string} */
1118
export let position = 'top';
19+
20+
/** @type {number} */
1221
export let maxWidth = 200;
13-
/**
14-
* @type {{ [x: string]: any; } | null}
15-
*/
22+
23+
/** @type {{ [x: string]: any; } | null} */
1624
export let style = null;
25+
26+
/** @type {string} */
1727
export let theme = '';
28+
29+
/** @type {string} */
1830
export let animation = '';
31+
32+
/** @type {boolean} */
1933
export let arrow = true;
34+
35+
/** @type {boolean} */
2036
export let autoPosition = false;
2137
22-
/**
23-
* @type {HTMLSpanElement | null}
24-
*/
38+
/** @type {boolean} */
39+
export let show = false;
40+
41+
/** @type {HTMLSpanElement | null} */
2542
let containerRef = null;
26-
/**
27-
* @type {HTMLDivElement | null}
28-
*/
43+
44+
/** @type {HTMLDivElement | null} */
2945
let tooltipRef = null;
46+
47+
/** @type {number} */
3048
let minWidth = 0;
31-
/**
32-
* @type {{ $destroy: () => void; } | null}
33-
*/
49+
50+
/** @type {{ $destroy: () => void; } | null} */
3451
let component = null;
52+
53+
/** @type {'top' | string} */
3554
let initialPosition = position;
36-
/**
37-
* @type {string | null}
38-
*/
55+
56+
/** @type {string | null} */
3957
let animationEffect = null;
40-
let show = false;
41-
/**
42-
* @type {number | null | undefined}
43-
*/
58+
59+
/** @type {number | null} */
4460
let timer = null;
4561
62+
/** @type {boolean} */
63+
let visible = false;
64+
4665
const onClick = () => {
47-
if (show) {
48-
onMouseLeave();
66+
if (visible) {
67+
onHide();
4968
} else {
50-
onMouseEnter();
69+
onShow();
5170
}
5271
};
5372
54-
const onMouseEnter = () => {
73+
const onShow = () => {
5574
const delay = animation ? 200 : 0;
5675
5776
if (autoPosition && !isInViewport(tooltipRef)) {
@@ -63,11 +82,11 @@
6382
animationEffect = animation;
6483
}
6584
66-
timer = setTimeout(() => (show = true), delay);
85+
timer = setTimeout(() => (visible = true), delay);
6786
};
6887
69-
const onMouseLeave = () => {
70-
show = false;
88+
const onHide = () => {
89+
visible = false;
7190
position = initialPosition;
7291
animationEffect = null;
7392
@@ -86,17 +105,17 @@
86105
}
87106
88107
if (action === 'hover') {
89-
containerRef.addEventListener('mouseenter', onMouseEnter);
90-
containerRef.addEventListener('mouseleave', onMouseLeave);
108+
containerRef.addEventListener('mouseenter', onShow);
109+
containerRef.addEventListener('mouseleave', onHide);
91110
}
92111
}
93112
}
94113
95114
const removeListeners = () => {
96115
if (containerRef !== null) {
97116
containerRef.removeEventListener('click', onClick);
98-
containerRef.removeEventListener('mouseenter', onMouseEnter);
99-
containerRef.removeEventListener('mouseleave', onMouseLeave);
117+
containerRef.removeEventListener('mouseenter', onShow);
118+
containerRef.removeEventListener('mouseleave', onHide);
100119
}
101120
};
102121
@@ -137,6 +156,7 @@
137156
138157
$: isComponent = typeof content === 'object';
139158
$: action, addListeners();
159+
$: tooltipRef && show ? onShow() : onHide();
140160
</script>
141161

142162
{#if content}
@@ -146,7 +166,7 @@
146166
bind:this={tooltipRef}
147167
class="tooltip animation-{animationEffect} {position} {theme}"
148168
class:arrowless={!arrow}
149-
class:show
169+
class:show={visible}
150170
style="min-width: {minWidth}px; max-width: {maxWidth}px; text-align: {align};"
151171
>
152172
{#if !isComponent}

‎src/tooltip.svelte.d.ts

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,70 @@ import type { SvelteComponentTyped } from 'svelte';
22

33
export interface ComponentProps {
44
/**
5-
* The content of the tooltip.
6-
* @default ''
5+
* The action to trigger the tooltip
6+
* @default 'hover'
77
*/
8-
content?: string;
8+
action: 'hover' | 'click' | 'prop' | string;
99

1010
/**
11-
* The position of the tooltip.
12-
* Allowed values are 'top', 'bottom', 'left' or 'right'.
13-
* @default 'top'
11+
* The alignment of the tooltip.
12+
* @default 'left'
1413
*/
15-
position?: string;
14+
align?: 'left' | 'right' | 'center' | string;
1615

1716
/**
18-
* The maximum width of the tooltip.
19-
* @default 200
17+
* The animation style of the tooltip.
18+
* @default ''
2019
*/
21-
maxWidth?: number;
20+
animation?: string;
2221

2322
/**
24-
* The style of the tooltip.
25-
* @default null
23+
* Whether to show the arrow of the tooltip.
24+
* @default true
2625
*/
27-
style?: undefined;
26+
arrow?: boolean;
2827

2928
/**
30-
* The theme of the tooltip.
31-
* @default ''
29+
* Whether to automatically position the tooltip when clipping occurs.
30+
* @default false
3231
*/
33-
theme?: string;
32+
autoPosition?: boolean;
3433

3534
/**
36-
* The animation style of the tooltip.
35+
* The content of the tooltip.
3736
* @default ''
3837
*/
39-
animation?: string;
38+
content?: string;
4039

4140
/**
42-
* Whether to show the arrow of the tooltip.
43-
* @default true
41+
* The maximum width of the tooltip.
42+
* @default 200
4443
*/
45-
arrow?: boolean;
44+
maxWidth?: number;
4645

4746
/**
48-
* Whether to automatically position the tooltip.
47+
* The position of the tooltip.
48+
* @default 'top'
49+
*/
50+
position?: 'bottom' | 'left' | 'right' | 'top' | string;
51+
52+
/**
53+
* Control the visibility of the tooltip.
4954
* @default false
5055
*/
51-
autoPosition?: boolean;
56+
show?: boolean;
57+
58+
/**
59+
* The style of the tooltip.
60+
* @default null
61+
*/
62+
style?: undefined;
63+
64+
/**
65+
* The theme of the tooltip.
66+
* @default ''
67+
*/
68+
theme?: string;
5269
}
5370

5471
export default class Component extends SvelteComponentTyped<

0 commit comments

Comments
 (0)
Please sign in to comment.