vue-pointing-bubble 0.1.0 → 0.2.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
@@ -1,418 +1,69 @@
1
1
  # vue-pointing-bubble
2
2
 
3
- A Vue 3 callout for a speech bubble, speech balloon, popover with arrow,
4
- annotated popover, or coachmark. The tail tip points **exactly** at an absolute
5
- `(x, y)` coordinate or at the center of a DOM element.
3
+ Vue 3 callout / speech bubble whose SVG tail points exactly at coordinates or a DOM element.
6
4
 
7
- The box is a regular element that you style with CSS. The tail is a small SVG
8
- triangle in the same colors that covers the box border where it attaches, so there
9
- is no seam, and its tip lands on the target pixel with zero offset.
5
+ Repository: [github.com/MurzNN/js-pointing-bubble](https://github.com/MurzNN/js-pointing-bubble) · Demo: [https://murznn.github.io/js-pointing-bubble/vue/](https://murznn.github.io/js-pointing-bubble/vue/)
10
6
 
11
- ![Screenshot of the pointing bubble simple demo layout.](docs/screenshot.png)
7
+ - Point at `{ x, y }` or a DOM element / CSS selector
8
+ - Auto / fixed / manual placement with a seamless SVG tail
9
+ - Edge or center target anchors, resize + scroll tracking, optional glide
10
+ - Style with `class` / `style` (Tailwind / UnoCSS welcome); no CSS import required
12
11
 
13
- **[Live demo](https://murznn.github.io/vue-pointing-bubble/)**
14
-
15
- ## Features
16
-
17
- - Point at absolute coordinates (`{ x, y }`) or at a DOM element / component instance.
18
- - Automatic quadrant placement that keeps the bubble inside the container and the visible viewport, a fixed quadrant, or a box positioned by your own CSS.
19
- - Seamless tail with rounded box corners and an optional drop shadow that covers both.
20
- - Any content through the default slot. The bubble resizes to fit it.
21
- - Style the bubble with regular `class` and `style`, including Tailwind CSS or UnoCSS utility classes: size, padding, position, background, border, corner radius, and `z-index`.
22
- - Glides smoothly to a new target, and to a new size when its content changes.
23
- - Follows window resize, scrolling, and container resize.
24
- - No CSS file to import and no dependency on Tailwind or any other CSS framework.
25
- - Ships as an ES module, a UMD bundle for `<script>` tags, the raw `.vue` SFC, and a [shadcn-vue](https://www.shadcn-vue.com/docs/registry) registry item. Includes TypeScript declarations.
26
-
27
- ## Installation
28
-
29
- ```bash
30
- npm install vue-pointing-bubble
31
- ```
32
-
33
- You can also install it straight from GitHub:
12
+ ## Install
34
13
 
35
14
  ```bash
36
- npm install github:MurzNN/vue-pointing-bubble
15
+ pnpm add vue-pointing-bubble
37
16
  ```
38
17
 
39
- Vue `^3.3` is a peer dependency.
40
-
41
- ### shadcn-vue
42
-
43
- This repository is a shadcn-vue registry. In a project that already has `components.json`, install the component with:
18
+ ## Install via shadcn-vue
44
19
 
45
20
  ```bash
46
- pnpm dlx shadcn-vue@latest add MurzNN/vue-pointing-bubble/pointing-bubble
21
+ pnpm dlx shadcn-vue@latest add MurzNN/js-pointing-bubble/vue-pointing-bubble
47
22
  ```
48
23
 
49
- The CLI reads [`registry.json`](registry.json) from the default branch and writes the component into the project's UI directory. With the default alias, that is `src/components/ui/pointing-bubble`:
50
-
51
- ```vue
52
- <script setup>
24
+ ```ts
53
25
  import { PointingBubble } from '@/components/ui/pointing-bubble'
54
- </script>
55
-
56
- <template>
57
- <PointingBubble :target="{ x: 320, y: 180 }">
58
- Look here.
59
- </PointingBubble>
60
- </template>
61
- ```
62
-
63
- Append `#ref` to install from a branch, tag, or commit, for example `MurzNN/vue-pointing-bubble/pointing-bubble#v0.1.0`.
64
-
65
- The same item is also published with the demo, so you can register a short name in `components.json`:
66
-
67
- ```json
68
- {
69
- "registries": {
70
- "@vue-pointing-bubble": "https://murznn.github.io/vue-pointing-bubble/r/{name}.json"
71
- }
72
- }
73
- ```
74
-
75
- ```bash
76
- pnpm dlx shadcn-vue@latest add @vue-pointing-bubble/pointing-bubble
77
- ```
78
-
79
- List what the repository publishes with `pnpm dlx shadcn-vue@latest search MurzNN/vue-pointing-bubble`.
80
-
81
- ## Usage
82
-
83
- The component fills its nearest **positioned** ancestor (`position: relative`,
84
- `absolute`, or `fixed`) with an overlay. Coordinates are measured relative to
85
- that ancestor.
86
-
87
- ### Pointing at page coordinates
88
-
89
- To point at absolute coordinates on the page, wrap the bubble in a layer that
90
- covers the page. Because the layer is `position: absolute`, it takes no space in
91
- the layout, so the page content stays where it is. Add `pointer-events: none` so the
92
- layer doesn't block clicks on the elements under it.
93
-
94
- ```vue
95
- <script setup>
96
- import { PointingBubble } from 'vue-pointing-bubble'
97
- </script>
98
-
99
- <template>
100
- <div style="position: absolute; inset: 0; pointer-events: none">
101
- <PointingBubble :target="{ x: 320, y: 180 }">
102
- <strong>Look here!</strong>
103
- <p>The tail tip is exactly at (320, 180) on the page.</p>
104
- </PointingBubble>
105
- </div>
106
- </template>
107
26
  ```
108
27
 
109
- The coordinates are measured from the top-left corner of the document, the same way
110
- as `event.pageX` and `event.pageY`, and the bubble scrolls together with the page.
111
- This works only if none of the layer's ancestors is positioned. Otherwise, the
112
- coordinates are measured from that ancestor. To be safe, render the layer with
113
- `<Teleport to="body">`.
114
-
115
- If you want viewport coordinates instead (`event.clientX` and `event.clientY`), with
116
- the bubble staying in place while the page scrolls, use `position: fixed` for the
117
- layer.
118
-
119
- ### Pointing at coordinates inside a container
120
-
121
- To point at a spot inside a specific element, such as an image or a chart, put the
122
- bubble into a positioned container. The coordinates are then measured from the
123
- container's top-left corner, so they stay correct wherever the container is on the
124
- page.
28
+ ## Quick start
125
29
 
126
30
  ```vue
127
- <template>
128
- <div style="position: relative; width: 600px">
129
- <img src="/floor-plan.png" width="600" height="400" alt="Floor plan">
130
-
131
- <PointingBubble :target="{ x: 420, y: 150 }">
132
- The kitchen is here.
133
- </PointingBubble>
134
- </div>
135
- </template>
136
- ```
137
-
138
- Here, `(420, 150)` is a point on the image. If the image moves, for example because
139
- of text added above it, the bubble moves with it.
140
-
141
- ### Pointing at an element
142
-
143
- Pass a template ref, either a DOM element or a component instance. The tip
144
- attaches to the center of the element. This is the usual setup for a coachmark
145
- or a popover with arrow next to a control.
146
-
147
- ```vue
148
- <script setup>
31
+ <script setup lang="ts">
149
32
  import { ref } from 'vue'
150
33
  import { PointingBubble } from 'vue-pointing-bubble'
151
34
 
152
- const button = ref(null)
35
+ const btn = ref<HTMLElement | null>(null)
153
36
  </script>
154
37
 
155
38
  <template>
156
- <div style="position: relative; height: 400px">
157
- <button ref="button">Save</button>
158
-
159
- <PointingBubble :target="button" placement="bottom-right">
160
- Click here to save your changes.
161
- </PointingBubble>
162
- </div>
163
- </template>
164
- ```
165
-
166
- By default the tip lands on the element's center. To stop it at the element's
167
- border instead, so it doesn't cover the button's label, add `target-anchor="edge"`:
168
-
169
- ```vue
170
- <PointingBubble :target="button" target-anchor="edge">
171
- Click here to save your changes.
172
- </PointingBubble>
173
- ```
174
-
175
- With automatic placement, the tip then touches the middle of the element's top or
176
- bottom edge, whichever faces the box. With `placement="manual"`, it touches the
177
- point of the element's border nearest to the center of the box.
178
-
179
- You can also pass a CSS selector instead of a ref, which is handy when the element
180
- belongs to another component or isn't rendered by Vue at all:
181
-
182
- ```vue
183
- <PointingBubble target="#save-button" target-anchor="edge">
184
- Click here to save your changes.
185
- </PointingBubble>
186
- ```
187
-
188
- The selector is looked up in the whole document with `document.querySelector`, and
189
- the first match is used. It's looked up again on every update, so the bubble finds
190
- an element that appears later, after a re-render, resize, or scroll, or when you
191
- call `update()`. While nothing matches, the bubble is hidden.
192
-
193
- ### Sizing and styling the box
194
-
195
- `class`, `style`, and event listeners that you put on `<PointingBubble>` go to the
196
- box with your content, the same way as on a regular `<div>`. The tail is attached
197
- to whatever size the box ends up with.
198
-
199
- ```vue
200
- <PointingBubble :target="button" class="w-64 p-3 text-sm">
201
- Click here to save your changes.
202
- </PointingBubble>
203
- ```
204
-
205
- By default, the box fits its content, wraps the text at `maxWidth` (280px), and has
206
- `padding` of 8px, a white background, a gray border, and 8px rounded corners.
207
- These defaults come from a tiny stylesheet that the component adds to the page,
208
- with zero specificity, so anything your classes or `style` set wins, including
209
- zero values like `p-0` or `rounded-none`.
210
-
211
- If you give the box a fixed height, content that doesn't fit is clipped. Add
212
- `overflow-auto` (or `overflow: auto`) to make it scrollable instead.
213
-
214
- The box paints itself, so any CSS works on it: gradients, per-corner radii,
215
- `hover:` variants, and so on. The tail takes these properties from the box:
216
-
217
- | CSS of the box | Effect on the tail |
218
- | --------------------------------------- | ----------------------------------------- |
219
- | `background-color` | Fill color. |
220
- | `border-top-width`, `border-top-color` | Outline width and color. |
221
- | `border-top-left-radius` | Keeps the tail away from rounded corners. |
222
- | `z-index` | Stacking order of the whole bubble (`30` if unset). |
223
-
224
- ```vue
225
- <PointingBubble :target="button" class="p-3 rounded-xl bg-yellow-100 border-2 border-yellow-600">
226
- Click here to save your changes.
227
- </PointingBubble>
228
-
229
- <!-- The same without a CSS framework -->
230
- <PointingBubble
231
- :target="button"
232
- style="padding: 12px; border-radius: 12px; background: #fef9c3; border: 2px solid #ca8a04"
233
- >
234
- Click here to save your changes.
235
- </PointingBubble>
236
- ```
237
-
238
- The tail is a solid color, so with a gradient or background image, pick a
239
- `background-color` that blends with it. The tail is updated on a re-render and on
240
- resize. If the box colors change without one, for example on hover or when you
241
- toggle dark mode, call `update()`.
242
-
243
- The `shadow` prop adds a drop shadow that follows the box and the tail together. A
244
- `box-shadow` of your own only applies to the box.
245
-
246
- ### Placing the box yourself
247
-
248
- By default, the box is placed next to the tip automatically. With
249
- `placement="manual"`, you position the box yourself with `left` and `top`, set by
250
- classes or by `style`, relative to the positioned parent. The tail still points
251
- exactly at `target`, from whichever side of the box faces it. This works well for
252
- an annotated popover on a diagram, or for labels the user can drag around.
253
-
254
- ```vue
255
- <template>
256
- <div style="position: relative; height: 300px">
257
- <img ref="photo" src="/laptop.png" style="margin: 80px auto; display: block">
258
-
259
- <PointingBubble :target="photo" placement="manual" class="left-4 top-4 w-44">
260
- This label stays in the top-left corner.
261
- </PointingBubble>
262
- </div>
39
+ <button ref="btn">Hint</button>
40
+ <PointingBubble :target="btn" :max-width="240">
41
+ Precise callout content
42
+ </PointingBubble>
263
43
  </template>
264
44
  ```
265
45
 
266
- Any CSS unit works, for example `class="left-[7em] top-[5em]"` in Tailwind CSS, or
267
- `class="left-7em top-5em"` in UnoCSS. Without a framework, use `style`:
268
-
269
- ```vue
270
- <PointingBubble :target="photo" placement="manual" :style="{ left: x + 'px', top: y + 'px' }">
271
- Drag me around.
272
- </PointingBubble>
273
- ```
46
+ ### Props
274
47
 
275
- You don't need a class like `absolute`, because the box is always absolutely
276
- positioned. Without `left` and `top`, it sits in the top-left corner of the parent.
277
- The width works as in automatic mode: the box fits its content and wraps at
278
- `max-width` unless you set a width. In manual mode, the box itself doesn't glide
279
- when you move it, only the tail does. If the box moves without a resize or a change
280
- of its classes or style, such as during a CSS transition, call `update()`.
48
+ | Prop | Default | Notes |
49
+ | --- | --- | --- |
50
+ | `target` | `null` | `{x,y}`, `Element`, CSS selector, or `null` (in-flow box only) |
51
+ | `placement` | `'auto'` | `'auto'` \| quadrant \| `'manual'` (CSS positions the box) |
52
+ | `targetAnchor` | `'center'` | `'center'` \| `'edge'` for element targets |
53
+ | `maxWidth` | — | Wrap width (px) unless CSS sets width/max-width |
54
+ | `tailLength` | `45` | Tip → box offset (px) |
55
+ | `tailBaseWidth` | `24` | Tail width at the box (px) |
56
+ | `padding` | `8` | Via `--pb-padding` unless CSS sets padding |
57
+ | `shadow` | `true` | Shared drop-shadow on overlay/box |
58
+ | `transitionDuration` | `200` | Glide ms (`0` off; respects reduced motion) |
281
59
 
282
- ### A box without a tail
60
+ Expose / ref: `update()` after non-reactive layout changes.
283
61
 
284
- Leave out `target`, or set it to `null`, to draw just the box, with no tail. The
285
- box is then a regular element in the normal document flow: it isn't absolutely
286
- positioned and doesn't need a positioned parent. It sits on its own line inside
287
- the component's root block and is as wide as its content, up to the parent's
288
- width. The 280px default `maxWidth` doesn't apply here, but an explicit
289
- `:max-width`, or a width or max-width from your CSS, does. Margins on the box work as usual, for example `mx-auto` to
290
- center it. Opacity and transforms set on the component's root element, such as
291
- by Slidev's `v-click`, apply to the box as well. In a flex or grid parent, the
292
- root is the flex or grid item, so put item classes like `flex-1` on a wrapper
293
- element. This is handy for a step that doesn't point at anything, such as the
294
- welcome step of a guided tour:
62
+ CSS hooks: `.pointing-bubble`, `.pointing-bubble__inner`, `.pointing-bubble__svg`, `.pointing-bubble__path`. Vars: `--pb-padding`, `--pb-max-width`, `--pb-width`.
295
63
 
296
- ```vue
297
- <PointingBubble :target="step.element" class="mt-4">
298
- {{ step.text }}
299
- </PointingBubble>
300
- ```
301
-
302
- When `target` changes from `null` to an element, the box leaves the flow and appears
303
- next to the element, with a tail, and comes back when it changes to `null`. To hide
304
- the bubble completely, use `v-if`. Margins only apply to this mode and to
305
- `placement="manual"`; a bubble placed automatically next to its target ignores them.
306
-
307
- ### Global registration
308
-
309
- ```js
310
- import { createApp } from 'vue'
311
- import VuePointingBubble from 'vue-pointing-bubble'
312
- import App from './App.vue'
313
-
314
- createApp(App)
315
- .use(VuePointingBubble) // registers <PointingBubble>
316
- // .use(VuePointingBubble, { name: 'Callout' }) // or under a custom name
317
- .mount('#app')
318
- ```
319
-
320
- The component is also exported under the alias `Callout`:
321
-
322
- ```js
323
- import { Callout } from 'vue-pointing-bubble'
324
- ```
325
-
326
- ### Using the raw SFC
327
-
328
- If you prefer to compile the component with your own build pipeline:
329
-
330
- ```js
331
- import PointingBubble from 'vue-pointing-bubble/PointingBubble.vue'
332
- ```
333
-
334
- ### Without a build step (CDN)
335
-
336
- ```html
337
- <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
338
- <script src="https://unpkg.com/vue-pointing-bubble"></script>
339
-
340
- <div id="app">
341
- <div style="position: absolute; inset: 0; pointer-events: none">
342
- <pointing-bubble :target="{ x: 200, y: 120 }">Hello!</pointing-bubble>
343
- </div>
344
- </div>
345
-
346
- <script>
347
- Vue.createApp({})
348
- .component('PointingBubble', VuePointingBubble.PointingBubble)
349
- .mount('#app')
350
- </script>
351
- ```
352
-
353
- ## Props
354
-
355
- | Prop | Type | Default | Description |
356
- | -------------------- | ------------------------------------- | ----------- | ----------- |
357
- | `target` | `{ x, y }` \| `Element` \| component \| `string` | `null` | What the tip points at. Coordinates are relative to the positioned parent. A string is a CSS selector, looked up with `document.querySelector`; while it matches nothing, the bubble is hidden. Elements are pointed at their center, or at their border with `targetAnchor="edge"`. When `null`, only the box is drawn, without a tail, as a regular element in the document flow. Use `v-if` to hide the bubble entirely. |
358
- | `placement` | `'auto'` \| `'top-left'` \| `'top-right'` \| `'bottom-left'` \| `'bottom-right'` \| `'manual'` | `'auto'` | Where the box sits relative to the tip. `auto` chooses the quadrant facing the center of the container. It flips to the other side if the bubble would go outside the viewport, such as after scrolling, and fits on the other side. `manual` leaves the position to the `left` and `top` that you set with `class` or `style`. The tail then grows from whichever side of the box faces the tip. |
359
- | `targetAnchor` | `'center'` \| `'edge'` | `'center'` | For element targets, where the tip lands. `center` is the element's center. `edge` is the element's border facing the box: the middle of the top or bottom edge with automatic placement, or the nearest border point with `manual` placement. Ignored for coordinate targets. |
360
- | `maxWidth` | `number` | `280` | Width in px at which content wraps, unless your `class` or `style` sets a width or max-width. Without a `target`, there's no default: the box is only limited by its parent's width. |
361
- | `tailLength` | `number` | `45` | Horizontal and vertical offset in px from the tip to the box corner. |
362
- | `tailBaseWidth` | `number` | `24` | Width in px of the tail where it joins the box. |
363
- | `padding` | `number` \| `string` | `8` | Padding of the box (a number means px), unless your `class` or `style` sets a padding. |
364
- | `shadow` | `boolean` | `true` | Whether to render a drop shadow under the box and the tail. |
365
- | `transitionDuration` | `number` | `200` | Duration in ms of the glide animation when the target, size, or placement changes. The box and the tail move together. `0` disables it, and it is skipped when the user prefers reduced motion. Consider `0` while the target follows the mouse, such as during dragging. |
366
-
367
- ## Slots
368
-
369
- | Slot | Props | Description |
370
- | --------- | ----------------------- | ----------- |
371
- | `default` | `{ placement, tip }` | Bubble content. `placement` is the resolved quadrant, `'manual'`, or `'none'` without a target, and `tip` is the tip position `{ x, y }` in container pixels, or `null` without a target. |
372
-
373
- ## Exposed methods
374
-
375
- | Name | Description |
376
- | ---------- | ----------- |
377
- | `update()` | Recomputes the tip position, remeasures the box, and rereads its colors for the tail. Call it if the target element or a manually placed box moved, or the box colors changed, without a window resize, scroll, or re-render, such as after an animation or a theme switch. |
378
-
379
- ## Styling
380
-
381
- The overlay elements have stable class names you can target from your own CSS:
382
-
383
- - `.vue-pointing-bubble`: the root element, with a `data-placement` attribute holding the resolved quadrant. With a `target`, it's the full-size overlay; without one, it's a plain block around the box, with `data-placement="none"`.
384
- - `.vue-pointing-bubble__inner`: the box with the slot content. It receives the `class`, `style`, and listeners from `<PointingBubble>`.
385
- - `.vue-pointing-bubble__svg`: the SVG layer with the tail. It isn't rendered without a `target`.
386
- - `.vue-pointing-bubble__path`: the tail path.
387
-
388
- ## Development
389
-
390
- ```bash
391
- npm install
392
- npm run dev:demo # start the demo dev server with hot reload (`npm run dev` is an alias)
393
- npm run build # build the library into dist/
394
- npm run registry:build # write the shadcn-vue registry JSON into demo/public/r/
395
- npm run build:demo # build the registry JSON and the demo into demo-dist/
396
- npm run preview:demo # serve the built demo
397
- npm test # build the demo and run the browser tests against it
398
- ```
399
-
400
- The tests in [`tests/demo.spec.js`](tests/demo.spec.js) use
401
- [Playwright](https://playwright.dev/) and drive the demo page itself: they hover
402
- the hint buttons, walk through the tour, click the chart, and drag the labels,
403
- checking that each tail tip lands exactly on its target and that the box without a
404
- target stays in the page flow. They run in the installed Google Chrome; if you
405
- don't have it, install it with `npx playwright install chrome`.
406
-
407
- The component source is [`registry/ui/pointing-bubble/PointingBubble.vue`](registry/ui/pointing-bubble/PointingBubble.vue). The library and the shadcn-vue registry install the same file. The demo (`demo/Demo.vue`) imports it through `src/index.js`, so editing the demo or the component hot-reloads in the browser. It shows hover hints,
408
- a guided tour with a coachmark, an annotated popover on a picture, and draggable
409
- labels with custom box positions.
410
-
411
- The workflow [`.github/workflows/deploy-demo.yml`](.github/workflows/deploy-demo.yml)
412
- runs the tests on every pull request and push to `main`, and on `main`, once they
413
- pass, deploys the demo to GitHub Pages. To enable it, open **Settings → Pages** in the repository and
414
- set **Source** to **GitHub Actions**.
64
+ **Breaking (v0.2):** class prefix renamed from `.vue-pointing-bubble*` / `--vpb-*` to `.pointing-bubble*` / `--pb-*`.
415
65
 
416
66
  ## License
417
67
 
418
- [MIT](LICENSE)
68
+ MIT
69
+
package/dist/index.d.ts CHANGED
@@ -1,44 +1,3 @@
1
- import type { DefineComponent, Plugin, ComponentPublicInstance } from 'vue'
2
-
3
- export type PointingBubblePlacement =
4
- | 'auto'
5
- | 'top-left'
6
- | 'top-right'
7
- | 'bottom-left'
8
- | 'bottom-right'
9
- | 'manual'
10
-
11
- export interface PointingBubblePoint {
12
- x: number
13
- y: number
14
- }
15
-
16
- export type PointingBubbleTarget =
17
- | PointingBubblePoint
18
- | Element
19
- | ComponentPublicInstance
20
- /** A CSS selector, looked up with `document.querySelector`. */
21
- | string
22
- | null
23
-
24
- export interface PointingBubbleProps {
25
- target?: PointingBubbleTarget
26
- placement?: PointingBubblePlacement
27
- targetAnchor?: 'center' | 'edge'
28
- maxWidth?: number
29
- tailLength?: number
30
- tailBaseWidth?: number
31
- padding?: number | string
32
- shadow?: boolean
33
- transitionDuration?: number
34
- }
35
-
36
- export declare const PointingBubble: DefineComponent<PointingBubbleProps>
37
- export declare const Callout: typeof PointingBubble
38
-
39
- export interface PointingBubblePluginOptions {
40
- name?: string
41
- }
42
-
43
- declare const plugin: Plugin<[PointingBubblePluginOptions?]>
44
- export default plugin
1
+ export { PointingBubble, PointingBubble as Callout, PointingBubble as default, PointingBubblePlugin, type PointingBubblePluginOptions, } from './pointing-bubble';
2
+ export type { Placement, ResolvedPlacement, TargetAnchor, Point, BubbleTarget, BubbleConfig, BubbleController, BubbleRenderState, } from './pointing-bubble';
3
+ export { DEFAULT_BUBBLE_CONFIG, createBubbleController, resolveElement, isPoint, computeGeometry, buildTailPaths, } from './pointing-bubble';
@@ -0,0 +1,45 @@
1
+ import { ComponentPublicInstance } from 'vue';
2
+ import { BubbleTarget, Placement, TargetAnchor } from './core';
3
+ type __VLS_Props = {
4
+ target?: BubbleTarget | ComponentPublicInstance | null;
5
+ placement?: Placement;
6
+ targetAnchor?: TargetAnchor;
7
+ maxWidth?: number;
8
+ tailLength?: number;
9
+ tailBaseWidth?: number;
10
+ padding?: number | string;
11
+ shadow?: boolean;
12
+ transitionDuration?: number;
13
+ };
14
+ declare function update(): void;
15
+ declare var __VLS_1: {
16
+ placement: "none";
17
+ tip: {
18
+ x: number;
19
+ y: number;
20
+ } | null;
21
+ };
22
+ type __VLS_Slots = {} & {
23
+ default?: (props: typeof __VLS_1) => any;
24
+ };
25
+ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
26
+ update: typeof update;
27
+ }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
28
+ placement: Placement;
29
+ tailLength: number;
30
+ tailBaseWidth: number;
31
+ target: BubbleTarget | ComponentPublicInstance;
32
+ targetAnchor: TargetAnchor;
33
+ maxWidth: number;
34
+ padding: string | number;
35
+ shadow: boolean;
36
+ transitionDuration: number;
37
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
38
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
39
+ declare const _default: typeof __VLS_export;
40
+ export default _default;
41
+ type __VLS_WithSlots<T, S> = T & {
42
+ new (): {
43
+ $slots: S;
44
+ };
45
+ };
@@ -0,0 +1,13 @@
1
+ export type { Placement, ResolvedPlacement, TargetAnchor, Point, BubbleTarget, FrameRect, LocalRect, BoxMeasure, Geometry, Room, TailPaths, BubbleConfig, StyleMap, } from './types';
2
+ export { DEFAULT_BUBBLE_CONFIG } from './types';
3
+ export { injectBubbleStyles, SHADOW } from './styles';
4
+ export { isPoint, resolveElement } from './resolveTarget';
5
+ export { sameKeys, measureFrame, measureBox } from './measure';
6
+ export { computeRoom, roomierSide, computeGeometry } from './geometry';
7
+ export type { ComputeGeometryOptions } from './geometry';
8
+ export { easeGeometry } from './ease';
9
+ export type { EaseHandle } from './ease';
10
+ export { buildTailPaths } from './tail';
11
+ export type { BuildTailOptions } from './tail';
12
+ export { createBubbleController } from './createBubbleController';
13
+ export type { BubbleRenderState, BubbleControllerOptions, BubbleController, } from './createBubbleController';
@@ -0,0 +1,31 @@
1
+ import { BubbleConfig, BubbleTarget, Point, ResolvedPlacement, StyleMap, TailPaths } from './types';
2
+ export interface BubbleRenderState {
3
+ hasTarget: boolean;
4
+ placement: ResolvedPlacement;
5
+ tip: Point | null;
6
+ /** Tip passed to slot/children (resolved geometry tip when available). */
7
+ slotTip: Point | null;
8
+ overlayStyle: StyleMap;
9
+ boxStyle: StyleMap;
10
+ /** When no target, root element is the box. */
11
+ rootIsBox: boolean;
12
+ tail: TailPaths | null;
13
+ boxFill: string;
14
+ boxStroke: string;
15
+ boxStrokeWidth: number;
16
+ boxZIndex: string;
17
+ }
18
+ export interface BubbleControllerOptions {
19
+ getRootEl: () => HTMLElement | null;
20
+ getBoxEl: () => HTMLElement | null;
21
+ onChange?: (state: BubbleRenderState) => void;
22
+ }
23
+ export interface BubbleController {
24
+ update: () => void;
25
+ setConfig: (partial: Partial<BubbleConfig>) => void;
26
+ getConfig: () => BubbleConfig;
27
+ getState: () => BubbleRenderState;
28
+ destroy: () => void;
29
+ }
30
+ export declare function createBubbleController(options: BubbleControllerOptions): BubbleController;
31
+ export type { BubbleTarget };
@@ -0,0 +1,9 @@
1
+ import { Geometry } from './types';
2
+ export interface EaseHandle {
3
+ cancel: () => void;
4
+ }
5
+ /**
6
+ * Ease `from` → `to` with ease-out cubic. Calls `onFrame` each tick.
7
+ * Manual placement only animates tip coords (CSS owns the box).
8
+ */
9
+ export declare function easeGeometry(from: Geometry | null, to: Geometry | null, durationMs: number, onFrame: (value: Geometry | null) => void): EaseHandle;
@@ -0,0 +1,14 @@
1
+ import { BoxMeasure, FrameRect, Geometry, LocalRect, Placement, Point, Room, TargetAnchor } from './types';
2
+ export declare function computeRoom(tip: Point, frame: FrameRect, tailLength: number): Room;
3
+ export declare function roomierSide(room: Room): 'left' | 'right';
4
+ export interface ComputeGeometryOptions {
5
+ tip: Point;
6
+ box: BoxMeasure;
7
+ frame: FrameRect;
8
+ placement: Placement;
9
+ targetAnchor: TargetAnchor;
10
+ targetRect: LocalRect | null;
11
+ room: Room;
12
+ tailLength: number;
13
+ }
14
+ export declare function computeGeometry(opts: ComputeGeometryOptions): Geometry | null;
@@ -0,0 +1,11 @@
1
+ import { Plugin } from 'vue';
2
+ import { default as PointingBubble } from './PointingBubble.vue';
3
+ export type { Placement, ResolvedPlacement, TargetAnchor, Point, BubbleTarget, BubbleConfig, BubbleController, BubbleRenderState, } from './core';
4
+ export { DEFAULT_BUBBLE_CONFIG, createBubbleController, resolveElement, isPoint, computeGeometry, buildTailPaths, } from './core';
5
+ export { PointingBubble };
6
+ export { PointingBubble as Callout };
7
+ export { PointingBubble as default };
8
+ export interface PointingBubblePluginOptions {
9
+ name?: string;
10
+ }
11
+ export declare const PointingBubblePlugin: Plugin;
@@ -0,0 +1,5 @@
1
+ import { BoxMeasure, FrameRect } from './types';
2
+ export declare function sameKeys(a: Record<string, unknown> | null | undefined, b: Record<string, unknown> | null | undefined): boolean;
3
+ /** Measure root frame including ancestor CSS scale (e.g. Slidev). */
4
+ export declare function measureFrame(root: HTMLElement): FrameRect;
5
+ export declare function measureBox(boxEl: HTMLElement, rootEl: HTMLElement, frame: FrameRect, manual: boolean): BoxMeasure;
@@ -0,0 +1,7 @@
1
+ import { BubbleTarget, Point } from './types';
2
+ export declare function isPoint(value: unknown): value is Point;
3
+ /**
4
+ * Resolve a CSS selector or Element. Adapters unwrap framework component
5
+ * instances / refs before calling this.
6
+ */
7
+ export declare function resolveElement(target: BubbleTarget): Element | null;
@@ -0,0 +1,3 @@
1
+ export declare const SHADOW = "drop-shadow(0 6px 8px rgba(0, 0, 0, 0.25))";
2
+ /** Inject default zero-specificity box styles once per document. */
3
+ export declare function injectBubbleStyles(doc?: Document): void;