@vingy/vueltip 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,9 +19,11 @@ pnpm add vueltip
19
19
  Register the tooltip in your app:
20
20
 
21
21
  ```ts
22
- import Vueltip from 'vueltip'
22
+ import { vueltipPlugin, vueltipDirective } from 'vueltip'
23
23
 
24
- createApp(App).use(Vueltip)
24
+ createApp(App)
25
+ .use(vueltipPlugin, { component: Tooltip })
26
+ .directive('tooltip', vueltipDirective)
25
27
  ```
26
28
 
27
29
  ## Usage
@@ -49,19 +51,18 @@ First, create a tooltip component using the \`useTooltip\` composable:
49
51
  </template>
50
52
 
51
53
  <script setup>
52
- import { ref } from 'vue'
53
- import { useTooltip } from 'vueltip'
54
+ import { useTemplateRef } from 'vue'
55
+ import { useVueltip } from 'vueltip'
54
56
 
55
- const tooltipElement = ref()
56
- const arrowElement = ref()
57
+ const tooltipElement = useTemplateRef('tooltipElement')
58
+ const arrowElement = useTemplateRef('arrowElement')
57
59
 
58
60
  const { tooltipStyles, arrowStyles, show, content } =
59
- useTooltip({
61
+ useVueltip({
60
62
  tooltipElement,
61
63
  arrowElement,
62
64
  offset: 8,
63
65
  padding: 8,
64
- arrowSize: 10,
65
66
  })
66
67
  </script>
67
68
 
@@ -89,14 +90,16 @@ Import and register the tooltip component and plugin in your app's entry point:
89
90
  // main.ts
90
91
  import { createApp } from 'vue'
91
92
  import App from './App.vue'
92
- import Vueltip from 'vueltip'
93
+ import { vueltipPlugin, vueltipDirective } from 'vueltip'
93
94
  import Tooltip from './components/Tooltip.vue'
94
95
 
95
96
  const app = createApp(App)
96
97
 
97
- app.use(Vueltip, {
98
- component: Tooltip,
99
- })
98
+ app
99
+ .use(vueltipPlugin, {
100
+ component: Tooltip,
101
+ })
102
+ .directive('tooltip', vueltipDirective)
100
103
 
101
104
  app.mount('#app')
102
105
  ```
@@ -105,20 +108,23 @@ app.mount('#app')
105
108
 
106
109
  Now you can use the `v-tooltip` directive on any element:
107
110
 
108
- ```ts
111
+ ```vue
109
112
  <template>
110
113
  <div>
111
- <button v-tooltip="{ text: 'Click me to submit' }">
112
- Submit
113
- </button>
114
+ <button v-tooltip="'Click me to submit'">Submit</button>
114
115
 
115
116
  <input
116
- v-tooltip="{ text: 'Enter your email address' }"
117
+ v-tooltip="'Enter your email address'"
117
118
  type="email"
118
119
  placeholder="Email"
119
120
  />
120
121
 
121
- <span v-tooltip="{ text: 'This is a helpful tooltip' }">
122
+ <span
123
+ v-tooltip="{
124
+ content: 'This is a helpful tooltip',
125
+ placement: 'right',
126
+ }"
127
+ >
122
128
  Hover over me
123
129
  </span>
124
130
  </div>
@@ -126,3 +132,53 @@ Now you can use the `v-tooltip` directive on any element:
126
132
  ```
127
133
 
128
134
  See the demo app in [demo/](../../demo/).
135
+
136
+ ## Options
137
+
138
+ ### Plugin Options
139
+
140
+ The `vueltipPlugin` accepts the following options:
141
+
142
+ | Option | Type | Default | Description |
143
+ | -------------------------- | -------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------- |
144
+ | `component` | `Component` | Required | The Vue component to render as the tooltip |
145
+ | `showDelay` | `number` | `0` | Delay in milliseconds before the tooltip appears on hover |
146
+ | `hideDelay` | `number` | `200` | Delay in milliseconds before the tooltip disappears when the cursor leaves |
147
+ | `defaultPlacement` | `Placement` | `'top'` | Default tooltip placement: `'top'`, `'bottom'`, `'left'`, `'right'`, etc. |
148
+ | `defaultTruncateDetection` | `'x' \| 'y' \| 'both' \| 'none'` | `'both'` | Direction(s) to check for text truncation (`'x'` for horizontal, `'y'` for vertical, `'both'`, or `'none'` to disable) |
149
+ | `handleDialogModals` | `boolean` | `false` | Whether to handle tooltips within HTML `<dialog>` elements with the `open` attribute (modal dialogs) |
150
+ | `placementAttribute` | `string` | `'vueltip-placement'` | HTML attribute name for tooltip placement overrides |
151
+ | `keyAttribute` | `string` | `'vueltip-key'` | HTML attribute name for tooltip identification |
152
+ | `truncateAttribute` | `string` | `'vueltip-truncate'` | HTML attribute name for truncate detection overrides |
153
+
154
+ ### useVueltip Composable Options
155
+
156
+ The `useVueltip` composable accepts the following options:
157
+
158
+ | Option | Type | Default | Description |
159
+ | ----------------- | -------------------------- | -------- | ---------------------------------------------------------- |
160
+ | `tooltipElement` | `Ref<HTMLElement \| null>` | Required | Reference to the tooltip container element |
161
+ | `arrowElement` | `Ref<HTMLElement \| null>` | Optional | Reference to the arrow element for positioning |
162
+ | `offset` | `number` | `0` | Offset distance between the tooltip and the target element |
163
+ | `padding` | `number` | `0` | Padding between the tooltip and the viewport edges |
164
+ | `arrowSize` | `number` | `0` | Size of the arrow element (used for proper positioning) |
165
+ | `floatingOptions` | `UseFloatingOptions` | `{}` | Advanced options for the underlying Floating UI library |
166
+
167
+ ### Directive Options
168
+
169
+ The `v-tooltip` directive accepts bindings in two formats:
170
+
171
+ **Simple string (text only):**
172
+
173
+ ```ts
174
+ v-tooltip="'Tooltip text'"
175
+ ```
176
+
177
+ **Object with options:**
178
+
179
+ ```ts
180
+ v-tooltip="{
181
+ content: 'Tooltip text',
182
+ placement: 'right' // Placement: 'top', 'bottom', 'left', 'right', etc.
183
+ }"
184
+ ```
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as __reExport, t as __exportAll } from "./chunk-CtajNgzt.mjs";
2
2
  import * as Vue from "vue";
3
- import { App, Component, Directive, ShallowRef, StyleValue } from "vue";
3
+ import { App, Component, Directive, DirectiveBinding, ShallowRef, StyleValue } from "vue";
4
4
 
5
5
  //#region ../../node_modules/.pnpm/@floating-ui+utils@0.2.10/node_modules/@floating-ui/utils/dist/floating-ui.utils.d.mts
6
6
  declare type AlignedPlacement = `${Side}-${Alignment}`;
@@ -244,16 +244,20 @@ declare type UseFloatingOptions<T extends ReferenceElement = ReferenceElement> =
244
244
  whileElementsMounted?: (reference: T, floating: FloatingElement, update: () => void) => () => void;
245
245
  };
246
246
  //#endregion
247
+ //#region ../shared/dist/types.d.mts
248
+ //#region src/types.d.ts
249
+ type Maybe<T> = T | null | undefined; //#endregion
250
+ //#endregion
247
251
  //#region src/types.d.ts
248
252
  interface Content {
249
- text: string;
253
+ text: Maybe<string>;
250
254
  }
251
- type Binding = string | {
252
- content: string | Content;
255
+ type Binding = Maybe<string> | {
256
+ content: Maybe<string | Content>;
253
257
  placement: Placement;
254
258
  };
255
259
  type Modifier = 'x' | 'y' | 'none' | 'both';
256
- type TooltipDirective = Directive<HTMLElement, Binding, Modifier>;
260
+ type TooltipDirective = Directive<HTMLElement, Binding, Modifier, Placement>;
257
261
  type Options = {
258
262
  /** @default 'vueltip-placement' */placementAttribute: string; /** @default 'vueltip-key' */
259
263
  keyAttribute: string; /** @default 'vueltip-truncate' */
@@ -261,7 +265,8 @@ type Options = {
261
265
  showDelay: number; /** @default 200 */
262
266
  hideDelay: number; /** @default false */
263
267
  handleDialogModals: boolean; /** @default 'both' */
264
- defaultTruncateDetection: Modifier;
268
+ defaultTruncateDetection: Modifier; /** @default 'top' */
269
+ defaultPlacement: Placement;
265
270
  };
266
271
  type UseTooltipOptions = {
267
272
  tooltipElement: Readonly<ShallowRef<HTMLElement | null>>;
@@ -277,10 +282,6 @@ declare module 'vue' {
277
282
  }
278
283
  }
279
284
  //#endregion
280
- //#region ../shared/dist/types.d.mts
281
- //#region src/types.d.ts
282
- type Maybe<T> = T | null | undefined; //#endregion
283
- //#endregion
284
285
  //#region src/composables.d.ts
285
286
  declare const useVueltip: ({
286
287
  tooltipElement,
@@ -310,8 +311,8 @@ declare const useVueltip: ({
310
311
  //#endregion
311
312
  //#region src/directive.d.ts
312
313
  declare const vueltipDirective: {
313
- updated: (el: HTMLElement, binding: Vue.DirectiveBinding<Binding, Modifier, any>) => void;
314
- created: (el: HTMLElement, binding: Vue.DirectiveBinding<Binding, Modifier, any>) => void;
314
+ updated: (el: HTMLElement, binding: DirectiveBinding<Binding, Modifier, Placement>) => void;
315
+ created: (el: HTMLElement, binding: DirectiveBinding<Binding, Modifier, Placement>) => void;
315
316
  beforeUnmount: (el: HTMLElement) => void;
316
317
  };
317
318
  //#endregion
package/dist/index.mjs CHANGED
@@ -1396,7 +1396,8 @@ let options = {
1396
1396
  showDelay: 0,
1397
1397
  hideDelay: 200,
1398
1398
  handleDialogModals: false,
1399
- defaultTruncateDetection: "both"
1399
+ defaultTruncateDetection: "both",
1400
+ defaultPlacement: "top"
1400
1401
  };
1401
1402
  const setOptions = (opts) => {
1402
1403
  options = {
@@ -1404,6 +1405,7 @@ const setOptions = (opts) => {
1404
1405
  ...opts
1405
1406
  };
1406
1407
  };
1408
+ const getOption = (key) => options[key];
1407
1409
 
1408
1410
  //#endregion
1409
1411
  //#region src/state.ts
@@ -1411,10 +1413,10 @@ const tooltipPlacement = ref("top");
1411
1413
  const debouncedTooltipPlacement = ref("top");
1412
1414
  const hoveredElement = ref();
1413
1415
  const debouncedHoveredElement = ref();
1414
- const contentMap = /* @__PURE__ */ new Map();
1415
- const getContent = (key) => contentMap.get(key);
1416
- const setContent = (key, value) => contentMap.set(key, value);
1417
- const deleteContent = (key) => contentMap.delete(key);
1416
+ const contentMap = ref(/* @__PURE__ */ new Map());
1417
+ const getContent = (key) => contentMap.value.get(key);
1418
+ const setContent = (key, value) => contentMap.value.set(key, value);
1419
+ const deleteContent = (key) => contentMap.value.delete(key);
1418
1420
  const generateKey = () => crypto.randomUUID();
1419
1421
  const tooltipKey = ref();
1420
1422
  const tooltipContent = ref();
@@ -1422,11 +1424,12 @@ let timerId;
1422
1424
  watch([
1423
1425
  tooltipKey,
1424
1426
  hoveredElement,
1425
- tooltipPlacement
1427
+ tooltipPlacement,
1428
+ () => getContent(tooltipKey.value ?? "")
1426
1429
  ], ([key, el, placement]) => {
1427
1430
  if (!key) return;
1428
1431
  if (timerId) clearTimeout(timerId);
1429
- const timeout = el ? options.showDelay : options.hideDelay;
1432
+ const timeout = el ? getOption("showDelay") : getOption("hideDelay");
1430
1433
  timerId = setTimeout(() => {
1431
1434
  tooltipContent.value = getContent(key);
1432
1435
  debouncedHoveredElement.value = el;
@@ -1479,7 +1482,7 @@ const useVueltip = ({ tooltipElement, arrowElement, offset: _offset, padding, ar
1479
1482
  };
1480
1483
  });
1481
1484
  const show = computed(() => !!debouncedHoveredElement.value);
1482
- if (options.handleDialogModals) watch(show, (value) => {
1485
+ if (getOption("handleDialogModals")) watch(show, (value) => {
1483
1486
  if (!value || !tooltipElement.value || !debouncedHoveredElement.value || !initialParent) return;
1484
1487
  const dialogEl = debouncedHoveredElement.value.closest("dialog");
1485
1488
  if (!dialogEl) {
@@ -1510,7 +1513,7 @@ function isTruncated(el) {
1510
1513
  }
1511
1514
  }
1512
1515
  function getTruncationDirection(el) {
1513
- return el.getAttribute(options.truncateAttribute) ?? options.defaultTruncateDetection;
1516
+ return el.getAttribute(getOption("truncateAttribute")) ?? getOption("defaultTruncateDetection");
1514
1517
  }
1515
1518
  function elementContainsText(el, text) {
1516
1519
  if (isInputElement(el) || isTextAreaElement(el)) return getInputValue(el).includes(text);
@@ -1529,7 +1532,7 @@ function getInputValue(el) {
1529
1532
  return el.value;
1530
1533
  }
1531
1534
  const ensureKey = (el, fn) => {
1532
- const key = el.getAttribute(options.keyAttribute);
1535
+ const key = el.getAttribute(getOption("keyAttribute"));
1533
1536
  if (!key) return;
1534
1537
  return fn(key);
1535
1538
  };
@@ -1545,8 +1548,8 @@ const onMouseover = ensureEventTarget((target) => ensureKey(target, (key) => {
1545
1548
  const content = getContent(key);
1546
1549
  if (!content) return;
1547
1550
  const { text } = content;
1548
- if (elementContainsText(target, text) && !isTruncated(target)) return;
1549
- const placement = target.getAttribute(options.placementAttribute);
1551
+ if (!text || elementContainsText(target, text) && !isTruncated(target)) return;
1552
+ const placement = target.getAttribute(getOption("placementAttribute"));
1550
1553
  tooltipKey.value = key;
1551
1554
  hoveredElement.value = target;
1552
1555
  tooltipPlacement.value = placement;
@@ -1558,26 +1561,41 @@ const onMouseout = ensureEventTarget((target) => {
1558
1561
 
1559
1562
  //#endregion
1560
1563
  //#region src/directive.ts
1561
- const toContent = (value) => typeof value === "string" ? { text: value } : typeof value.content === "string" ? { text: value.content } : value.content;
1562
- const extractPlacement = (value) => typeof value === "string" ? "top" : value.placement;
1564
+ const toContent = (value) => {
1565
+ if (value == null) return { text: value };
1566
+ if (typeof value === "string") return { text: value };
1567
+ if (value.content == null) return { text: value.content };
1568
+ if (typeof value.content === "string") return { text: value.content };
1569
+ return value.content;
1570
+ };
1571
+ const extractPlacement = (binding) => {
1572
+ const { value, arg } = binding;
1573
+ if (value && typeof value !== "string" && "placement" in value) return value.placement;
1574
+ if (!arg) return getOption("defaultPlacement");
1575
+ return arg;
1576
+ };
1563
1577
  const truncationDirection = (modifiers) => {
1564
1578
  if (modifiers.none) return "none";
1565
1579
  if (modifiers.both) return "both";
1566
1580
  if (modifiers.x && modifiers.y) return "both";
1567
1581
  if (modifiers.x) return "x";
1568
1582
  if (modifiers.y) return "y";
1569
- return options.defaultTruncateDetection;
1583
+ return getOption("defaultTruncateDetection");
1570
1584
  };
1571
1585
  const vueltipDirective = {
1572
1586
  updated: (el, binding) => {
1573
- ensureKey(el, (key) => setContent(key, toContent(binding.value)));
1587
+ ensureKey(el, (key) => {
1588
+ el.setAttribute(getOption("placementAttribute"), extractPlacement(binding));
1589
+ el.setAttribute(getOption("truncateAttribute"), truncationDirection(binding.modifiers ?? {}));
1590
+ setContent(key, toContent(binding.value));
1591
+ });
1574
1592
  },
1575
1593
  created: (el, binding) => {
1576
1594
  const key = generateKey();
1577
1595
  setContent(key, toContent(binding.value));
1578
- el.setAttribute(options.keyAttribute, key);
1579
- el.setAttribute(options.placementAttribute, extractPlacement(binding.value));
1580
- el.setAttribute(options.truncateAttribute, truncationDirection(binding.modifiers ?? {}));
1596
+ el.setAttribute(getOption("keyAttribute"), key);
1597
+ el.setAttribute(getOption("placementAttribute"), extractPlacement(binding));
1598
+ el.setAttribute(getOption("truncateAttribute"), truncationDirection(binding.modifiers ?? {}));
1581
1599
  el.addEventListener("mouseenter", onMouseover);
1582
1600
  el.addEventListener("focus", onMouseover);
1583
1601
  el.addEventListener("mouseleave", onMouseout);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vingy/vueltip",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Headless tooltip which only shows when necessary.",
5
5
  "keywords": [
6
6
  "composition-api",