@data-slot/hover-card 0.2.80
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 +232 -0
- package/dist/index-CjJcY3PD.d.cts +69 -0
- package/dist/index-CmLyPAKk.d.ts +69 -0
- package/dist/index.cjs +1 -0
- package/dist/index.js +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# @data-slot/hover-card
|
|
2
|
+
|
|
3
|
+
Headless hover-card (preview-card style) for vanilla JavaScript. Accessible, unstyled, tiny.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @data-slot/hover-card
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<div data-slot="hover-card">
|
|
15
|
+
<button data-slot="hover-card-trigger">Hover me</button>
|
|
16
|
+
<div data-slot="hover-card-content" hidden>
|
|
17
|
+
Preview content
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<script type="module">
|
|
22
|
+
import { create } from "@data-slot/hover-card";
|
|
23
|
+
|
|
24
|
+
const controllers = create();
|
|
25
|
+
</script>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## API
|
|
29
|
+
|
|
30
|
+
### `create(scope?)`
|
|
31
|
+
|
|
32
|
+
Auto-discover and bind all hover-card instances in a scope (defaults to `document`).
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { create } from "@data-slot/hover-card";
|
|
36
|
+
|
|
37
|
+
const controllers = create(); // Returns HoverCardController[]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### `createHoverCard(root, options?)`
|
|
41
|
+
|
|
42
|
+
Create a controller for a specific element.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { createHoverCard } from "@data-slot/hover-card";
|
|
46
|
+
|
|
47
|
+
const hoverCard = createHoverCard(element, {
|
|
48
|
+
delay: 700,
|
|
49
|
+
closeDelay: 300,
|
|
50
|
+
side: "bottom",
|
|
51
|
+
align: "center",
|
|
52
|
+
portal: true,
|
|
53
|
+
onOpenChange: (open) => console.log(open),
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Options
|
|
58
|
+
|
|
59
|
+
| Option | Type | Default | Description |
|
|
60
|
+
|--------|------|---------|-------------|
|
|
61
|
+
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled only) |
|
|
62
|
+
| `open` | `boolean` | - | Controlled open state |
|
|
63
|
+
| `delay` | `number` | `700` | Delay before opening on hover/focus (ms) |
|
|
64
|
+
| `skipDelayDuration` | `number` | `300` | Duration to skip delay after closing (ms). Set `0` to disable warm-up. |
|
|
65
|
+
| `closeDelay` | `number` | `300` | Delay before closing after leave/blur (ms) |
|
|
66
|
+
| `side` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Preferred side relative to trigger |
|
|
67
|
+
| `align` | `"start" \| "center" \| "end"` | `"center"` | Preferred alignment on the side axis |
|
|
68
|
+
| `sideOffset` | `number` | `4` | Distance from trigger in pixels |
|
|
69
|
+
| `alignOffset` | `number` | `0` | Offset from alignment edge in pixels |
|
|
70
|
+
| `avoidCollisions` | `boolean` | `true` | Flip/shift to stay in viewport |
|
|
71
|
+
| `collisionPadding` | `number` | `8` | Viewport edge padding in pixels |
|
|
72
|
+
| `portal` | `boolean` | `true` | Portal content to `document.body` while open |
|
|
73
|
+
| `closeOnClickOutside` | `boolean` | `true` | Close when clicking outside |
|
|
74
|
+
| `closeOnEscape` | `boolean` | `true` | Close when pressing Escape |
|
|
75
|
+
| `onOpenChange` | `(open: boolean) => void` | `undefined` | Callback when open state changes |
|
|
76
|
+
|
|
77
|
+
### Controlled Mode
|
|
78
|
+
|
|
79
|
+
When `open` is provided, hover/focus/outside interactions emit `onOpenChange` but do not mutate internal state.
|
|
80
|
+
Use controller `setOpen(open)` or the `hover-card:set` event to apply state.
|
|
81
|
+
|
|
82
|
+
### Controller
|
|
83
|
+
|
|
84
|
+
| Method/Property | Description |
|
|
85
|
+
|-----------------|-------------|
|
|
86
|
+
| `open()` | Request open state (`setOpen(true)` for forced update) |
|
|
87
|
+
| `close()` | Request closed state (`setOpen(false)` for forced update) |
|
|
88
|
+
| `toggle()` | Request toggle |
|
|
89
|
+
| `setOpen(open)` | Force open/closed update (works in controlled mode) |
|
|
90
|
+
| `isOpen` | Current open state (readonly `boolean`) |
|
|
91
|
+
| `destroy()` | Cleanup all event listeners and timers |
|
|
92
|
+
|
|
93
|
+
## Markup Structure
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<div data-slot="hover-card">
|
|
97
|
+
<button data-slot="hover-card-trigger">Trigger</button>
|
|
98
|
+
<div data-slot="hover-card-content">Content</div>
|
|
99
|
+
</div>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Required Slots
|
|
103
|
+
|
|
104
|
+
- `hover-card-trigger`
|
|
105
|
+
- `hover-card-content`
|
|
106
|
+
|
|
107
|
+
### Optional Slots
|
|
108
|
+
|
|
109
|
+
- `hover-card-positioner` - Optional authored positioning wrapper
|
|
110
|
+
- `hover-card-portal` - Optional authored portal wrapper that can contain `hover-card-positioner`
|
|
111
|
+
|
|
112
|
+
### Composed Portal Markup (Optional)
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<div data-slot="hover-card">
|
|
116
|
+
<button data-slot="hover-card-trigger">Trigger</button>
|
|
117
|
+
<div data-slot="hover-card-portal">
|
|
118
|
+
<div data-slot="hover-card-positioner">
|
|
119
|
+
<div data-slot="hover-card-content">Content</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Data Attributes
|
|
126
|
+
|
|
127
|
+
Options can be set via data attributes on root/content. JS options take precedence.
|
|
128
|
+
|
|
129
|
+
| Attribute | Type | Default | Description |
|
|
130
|
+
|-----------|------|---------|-------------|
|
|
131
|
+
| `data-default-open` | boolean | `false` | Initial open state |
|
|
132
|
+
| `data-delay` | number | `700` | Open delay (ms) |
|
|
133
|
+
| `data-skip-delay-duration` | number | `300` | Warm-up window to skip open delay (ms) |
|
|
134
|
+
| `data-close-delay` | number | `300` | Close delay (ms) |
|
|
135
|
+
| `data-side` | string | `"bottom"` | Preferred side (content first, then root) |
|
|
136
|
+
| `data-align` | string | `"center"` | Preferred align (content first, then root) |
|
|
137
|
+
| `data-side-offset` | number | `4` | Distance from trigger (px) |
|
|
138
|
+
| `data-align-offset` | number | `0` | Align offset (px) |
|
|
139
|
+
| `data-avoid-collisions` | boolean | `true` | Collision handling |
|
|
140
|
+
| `data-collision-padding` | number | `8` | Viewport edge padding (px) |
|
|
141
|
+
| `data-portal` | boolean | `true` | Portal while open |
|
|
142
|
+
| `data-close-on-click-outside` | boolean | `true` | Outside click close |
|
|
143
|
+
| `data-close-on-escape` | boolean | `true` | Escape close |
|
|
144
|
+
|
|
145
|
+
Boolean attributes: present/`"true"` = true, `"false"` = false, absent = default.
|
|
146
|
+
|
|
147
|
+
## Events
|
|
148
|
+
|
|
149
|
+
### Outbound
|
|
150
|
+
|
|
151
|
+
- `hover-card:change` - emitted when open state changes or is requested in controlled mode.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
root.addEventListener("hover-card:change", (e) => {
|
|
155
|
+
const { open, reason, trigger, content } = (e as CustomEvent).detail;
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`reason` is one of: `"pointer" | "focus" | "blur" | "dismiss" | "api"`.
|
|
160
|
+
|
|
161
|
+
### Inbound
|
|
162
|
+
|
|
163
|
+
- `hover-card:set` - force open state
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
root.dispatchEvent(new CustomEvent("hover-card:set", { detail: { open: true } }));
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Deprecated shape is still supported:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
root.dispatchEvent(new CustomEvent("hover-card:set", { detail: { value: true } }));
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Styling
|
|
176
|
+
|
|
177
|
+
Position is computed in JavaScript and applied as `position: absolute` + `transform: translate3d(...)`.
|
|
178
|
+
By default, content is portaled to `document.body` while open.
|
|
179
|
+
Use `data-open` / `data-closed`, `data-side`, and `data-align` for animation/styling.
|
|
180
|
+
|
|
181
|
+
```css
|
|
182
|
+
[data-slot="hover-card-content"] {
|
|
183
|
+
transform-origin: var(--transform-origin, center);
|
|
184
|
+
--hover-card-slide-x: 0px;
|
|
185
|
+
--hover-card-slide-y: -4px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
[data-slot="hover-card-content"][data-side="top"] {
|
|
189
|
+
--hover-card-slide-y: 4px;
|
|
190
|
+
}
|
|
191
|
+
[data-slot="hover-card-content"][data-side="bottom"] {
|
|
192
|
+
--hover-card-slide-y: -4px;
|
|
193
|
+
}
|
|
194
|
+
[data-slot="hover-card-content"][data-side="left"] {
|
|
195
|
+
--hover-card-slide-x: 4px;
|
|
196
|
+
--hover-card-slide-y: 0px;
|
|
197
|
+
}
|
|
198
|
+
[data-slot="hover-card-content"][data-side="right"] {
|
|
199
|
+
--hover-card-slide-x: -4px;
|
|
200
|
+
--hover-card-slide-y: 0px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
[data-slot="hover-card-content"][data-open] {
|
|
204
|
+
animation: hover-card-in 160ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
[data-slot="hover-card-content"][data-closed] {
|
|
208
|
+
pointer-events: none;
|
|
209
|
+
animation: hover-card-out 120ms ease-in forwards;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Warm-up Behavior
|
|
214
|
+
|
|
215
|
+
When one hover-card closes, another hovered shortly after can open immediately (delay skipped).
|
|
216
|
+
|
|
217
|
+
- Controlled by `skipDelayDuration` / `data-skip-delay-duration`
|
|
218
|
+
- Set to `0` to disable warm-up behavior
|
|
219
|
+
- Warm-up applies across hover-card instances
|
|
220
|
+
|
|
221
|
+
## Accessibility
|
|
222
|
+
|
|
223
|
+
The component automatically handles:
|
|
224
|
+
|
|
225
|
+
- `aria-haspopup="dialog"` on trigger
|
|
226
|
+
- `aria-controls` linking trigger to content
|
|
227
|
+
- `aria-expanded` state on trigger
|
|
228
|
+
- Unique content IDs via `ensureId`
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
type HoverCardSide = "top" | "right" | "bottom" | "left";
|
|
3
|
+
type HoverCardAlign = "start" | "center" | "end";
|
|
4
|
+
type HoverCardReason = "pointer" | "focus" | "blur" | "dismiss" | "api";
|
|
5
|
+
interface HoverCardOptions {
|
|
6
|
+
/** Initial open state (uncontrolled mode only) */
|
|
7
|
+
defaultOpen?: boolean;
|
|
8
|
+
/** Controlled open state. Internal interactions do not mutate when set. */
|
|
9
|
+
open?: boolean;
|
|
10
|
+
/** Delay before opening on hover/focus (ms). @default 700 */
|
|
11
|
+
delay?: number;
|
|
12
|
+
/** Duration to skip delay after closing (ms). Set to 0 to disable warm-up. @default 300 */
|
|
13
|
+
skipDelayDuration?: number;
|
|
14
|
+
/** Delay before closing after leave/blur (ms). @default 300 */
|
|
15
|
+
closeDelay?: number;
|
|
16
|
+
/** The preferred side of the trigger to render against. @default "bottom" */
|
|
17
|
+
side?: HoverCardSide;
|
|
18
|
+
/** The preferred alignment against the trigger. @default "center" */
|
|
19
|
+
align?: HoverCardAlign;
|
|
20
|
+
/** The distance in pixels from the trigger. @default 4 */
|
|
21
|
+
sideOffset?: number;
|
|
22
|
+
/** Offset in pixels from the alignment edge. @default 0 */
|
|
23
|
+
alignOffset?: number;
|
|
24
|
+
/** When true, flips/shifts content to avoid viewport collisions. @default true */
|
|
25
|
+
avoidCollisions?: boolean;
|
|
26
|
+
/** Viewport padding used when avoiding collisions. @default 8 */
|
|
27
|
+
collisionPadding?: number;
|
|
28
|
+
/** Portal content to body while open. @default true */
|
|
29
|
+
portal?: boolean;
|
|
30
|
+
/** Close when clicking outside. @default true */
|
|
31
|
+
closeOnClickOutside?: boolean;
|
|
32
|
+
/** Close when pressing Escape. @default true */
|
|
33
|
+
closeOnEscape?: boolean;
|
|
34
|
+
/** Callback when open state changes */
|
|
35
|
+
onOpenChange?: (open: boolean) => void;
|
|
36
|
+
}
|
|
37
|
+
interface HoverCardController {
|
|
38
|
+
/** Open the hover-card (request in controlled mode) */
|
|
39
|
+
open(): void;
|
|
40
|
+
/** Close the hover-card (request in controlled mode) */
|
|
41
|
+
close(): void;
|
|
42
|
+
/** Toggle the hover-card (request in controlled mode) */
|
|
43
|
+
toggle(): void;
|
|
44
|
+
/** Force open state update (works in both controlled/uncontrolled modes) */
|
|
45
|
+
setOpen(open: boolean): void;
|
|
46
|
+
/** Current open state */
|
|
47
|
+
readonly isOpen: boolean;
|
|
48
|
+
/** Cleanup all event listeners */
|
|
49
|
+
destroy(): void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create a hover-card controller for a root element
|
|
53
|
+
*
|
|
54
|
+
* Expected markup:
|
|
55
|
+
* ```html
|
|
56
|
+
* <div data-slot="hover-card">
|
|
57
|
+
* <button data-slot="hover-card-trigger">Hover me</button>
|
|
58
|
+
* <div data-slot="hover-card-content">Preview content</div>
|
|
59
|
+
* </div>
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function createHoverCard(root: Element, options?: HoverCardOptions): HoverCardController;
|
|
63
|
+
/**
|
|
64
|
+
* Find and bind all hover-card components in a scope
|
|
65
|
+
* Returns array of controllers for programmatic access
|
|
66
|
+
*/
|
|
67
|
+
declare function create(scope?: ParentNode): HoverCardController[];
|
|
68
|
+
//#endregion
|
|
69
|
+
export { HoverCardAlign, HoverCardController, HoverCardOptions, HoverCardReason, HoverCardSide, create, createHoverCard };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
type HoverCardSide = "top" | "right" | "bottom" | "left";
|
|
3
|
+
type HoverCardAlign = "start" | "center" | "end";
|
|
4
|
+
type HoverCardReason = "pointer" | "focus" | "blur" | "dismiss" | "api";
|
|
5
|
+
interface HoverCardOptions {
|
|
6
|
+
/** Initial open state (uncontrolled mode only) */
|
|
7
|
+
defaultOpen?: boolean;
|
|
8
|
+
/** Controlled open state. Internal interactions do not mutate when set. */
|
|
9
|
+
open?: boolean;
|
|
10
|
+
/** Delay before opening on hover/focus (ms). @default 700 */
|
|
11
|
+
delay?: number;
|
|
12
|
+
/** Duration to skip delay after closing (ms). Set to 0 to disable warm-up. @default 300 */
|
|
13
|
+
skipDelayDuration?: number;
|
|
14
|
+
/** Delay before closing after leave/blur (ms). @default 300 */
|
|
15
|
+
closeDelay?: number;
|
|
16
|
+
/** The preferred side of the trigger to render against. @default "bottom" */
|
|
17
|
+
side?: HoverCardSide;
|
|
18
|
+
/** The preferred alignment against the trigger. @default "center" */
|
|
19
|
+
align?: HoverCardAlign;
|
|
20
|
+
/** The distance in pixels from the trigger. @default 4 */
|
|
21
|
+
sideOffset?: number;
|
|
22
|
+
/** Offset in pixels from the alignment edge. @default 0 */
|
|
23
|
+
alignOffset?: number;
|
|
24
|
+
/** When true, flips/shifts content to avoid viewport collisions. @default true */
|
|
25
|
+
avoidCollisions?: boolean;
|
|
26
|
+
/** Viewport padding used when avoiding collisions. @default 8 */
|
|
27
|
+
collisionPadding?: number;
|
|
28
|
+
/** Portal content to body while open. @default true */
|
|
29
|
+
portal?: boolean;
|
|
30
|
+
/** Close when clicking outside. @default true */
|
|
31
|
+
closeOnClickOutside?: boolean;
|
|
32
|
+
/** Close when pressing Escape. @default true */
|
|
33
|
+
closeOnEscape?: boolean;
|
|
34
|
+
/** Callback when open state changes */
|
|
35
|
+
onOpenChange?: (open: boolean) => void;
|
|
36
|
+
}
|
|
37
|
+
interface HoverCardController {
|
|
38
|
+
/** Open the hover-card (request in controlled mode) */
|
|
39
|
+
open(): void;
|
|
40
|
+
/** Close the hover-card (request in controlled mode) */
|
|
41
|
+
close(): void;
|
|
42
|
+
/** Toggle the hover-card (request in controlled mode) */
|
|
43
|
+
toggle(): void;
|
|
44
|
+
/** Force open state update (works in both controlled/uncontrolled modes) */
|
|
45
|
+
setOpen(open: boolean): void;
|
|
46
|
+
/** Current open state */
|
|
47
|
+
readonly isOpen: boolean;
|
|
48
|
+
/** Cleanup all event listeners */
|
|
49
|
+
destroy(): void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create a hover-card controller for a root element
|
|
53
|
+
*
|
|
54
|
+
* Expected markup:
|
|
55
|
+
* ```html
|
|
56
|
+
* <div data-slot="hover-card">
|
|
57
|
+
* <button data-slot="hover-card-trigger">Hover me</button>
|
|
58
|
+
* <div data-slot="hover-card-content">Preview content</div>
|
|
59
|
+
* </div>
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function createHoverCard(root: Element, options?: HoverCardOptions): HoverCardController;
|
|
63
|
+
/**
|
|
64
|
+
* Find and bind all hover-card components in a scope
|
|
65
|
+
* Returns array of controllers for programmatic access
|
|
66
|
+
*/
|
|
67
|
+
declare function create(scope?: ParentNode): HoverCardController[];
|
|
68
|
+
//#endregion
|
|
69
|
+
export { HoverCardAlign, HoverCardController, HoverCardOptions, HoverCardReason, HoverCardSide, create, createHoverCard };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@data-slot/core`);const t=[`top`,`right`,`bottom`,`left`],n=[`start`,`center`,`end`];let r=0;function i(i,a={}){let o=(0,e.getPart)(i,`hover-card-trigger`),s=(0,e.getPart)(i,`hover-card-content`),c=(0,e.getPart)(i,`hover-card-positioner`),l=c&&s&&c.contains(s)?c:null,u=(0,e.getPart)(i,`hover-card-portal`),d=u&&l&&u.contains(l)?u:null;if(!o||!s)throw Error(`Hover-card requires trigger and content slots`);let f=a.open!==void 0,p=a.defaultOpen??(0,e.getDataBool)(i,`defaultOpen`)??!1,m=a.delay??(0,e.getDataNumber)(i,`delay`)??700,h=a.skipDelayDuration??(0,e.getDataNumber)(i,`skipDelayDuration`)??300,g=a.closeDelay??(0,e.getDataNumber)(i,`closeDelay`)??300,_=a.onOpenChange,v=a.closeOnClickOutside??(0,e.getDataBool)(i,`closeOnClickOutside`)??!0,y=a.closeOnEscape??(0,e.getDataBool)(i,`closeOnEscape`)??!0,b=a.portal??(0,e.getDataBool)(s,`portal`)??(0,e.getDataBool)(i,`portal`)??!0,x=a.side??(0,e.getDataEnum)(s,`side`,t)??(0,e.getDataEnum)(i,`side`,t)??`bottom`,S=a.align??(0,e.getDataEnum)(s,`align`,n)??(0,e.getDataEnum)(i,`align`,n)??`center`,C=a.sideOffset??(0,e.getDataNumber)(s,`sideOffset`)??(0,e.getDataNumber)(i,`sideOffset`)??4,w=a.alignOffset??(0,e.getDataNumber)(s,`alignOffset`)??(0,e.getDataNumber)(i,`alignOffset`)??0,T=a.avoidCollisions??(0,e.getDataBool)(s,`avoidCollisions`)??(0,e.getDataBool)(i,`avoidCollisions`)??!0,E=a.collisionPadding??(0,e.getDataNumber)(s,`collisionPadding`)??(0,e.getDataNumber)(i,`collisionPadding`)??8,D=a.open??p,O=!1,k=!1,A=!1,j=!1,M=null,N=null,P=[],F=(0,e.createPortalLifecycle)({content:s,root:i,enabled:b,wrapperSlot:l?void 0:`hover-card-positioner`,container:l??void 0,mountTarget:l?d??l:void 0}),I=(0,e.ensureId)(s,`hover-card-content`);o.setAttribute(`aria-haspopup`,`dialog`),o.setAttribute(`aria-controls`,I),s.setAttribute(`data-side`,x),s.setAttribute(`data-align`,S);let L=()=>o.hasAttribute(`disabled`)||o.getAttribute(`aria-disabled`)===`true`,R=()=>{M&&=(clearTimeout(M),null)},z=()=>{N&&=(clearTimeout(N),null)},B=()=>{R(),z()},V=(t,n)=>{(0,e.emit)(i,`hover-card:change`,{open:t,reason:n,trigger:o,content:s}),_?.(t)},H=()=>{let t=F.container,n=i.ownerDocument.defaultView??window,r=(0,e.computeFloatingPosition)({anchorRect:o.getBoundingClientRect(),contentRect:s.getBoundingClientRect(),side:x,align:S,sideOffset:C,alignOffset:w,avoidCollisions:T,collisionPadding:E});t.style.position=`absolute`,t.style.top=`0px`,t.style.left=`0px`,t.style.transform=`translate3d(${r.x+n.scrollX}px, ${r.y+n.scrollY}px, 0)`,t.style.willChange=`transform`,t.style.margin=`0`,s.setAttribute(`data-side`,r.side),s.setAttribute(`data-align`,r.align),t!==s&&(t.setAttribute(`data-side`,r.side),t.setAttribute(`data-align`,r.align))},U=e=>{let t=F.container;if(i.setAttribute(`data-state`,e),s.setAttribute(`data-state`,e),t!==s&&t.setAttribute(`data-state`,e),e===`open`){i.setAttribute(`data-open`,``),s.setAttribute(`data-open`,``),t!==s&&t.setAttribute(`data-open`,``),i.removeAttribute(`data-closed`),s.removeAttribute(`data-closed`),t!==s&&t.removeAttribute(`data-closed`);return}i.setAttribute(`data-closed`,``),s.setAttribute(`data-closed`,``),t!==s&&t.setAttribute(`data-closed`,``),i.removeAttribute(`data-open`),s.removeAttribute(`data-open`),t!==s&&t.removeAttribute(`data-open`)},W=(0,e.createPresenceLifecycle)({element:s,onExitComplete:()=>{O||(F.restore(),s.hidden=!0)}}),G=(0,e.createPositionSync)({observedElements:[o,s],isActive:()=>D,ancestorScroll:!1,onUpdate:H}),K=(t,n)=>{D!==t&&(!t&&D&&h>0&&(r=Date.now()+h),D=t,(0,e.setAria)(o,`expanded`,D),t?(F.mount(),s.hidden=!1,U(`open`),W.enter(),H(),G.start(),G.update()):(U(`closed`),W.exit(),G.stop()),V(D,n))},q=(e,t)=>{if(D!==e){if(f){V(e,t);return}K(e,t)}},J=(e,t)=>{K(e,t)},Y=e=>{if(z(),R(),h>0&&Date.now()<r){q(!0,e);return}if(m<=0){q(!0,e);return}M=setTimeout(()=>{M=null,q(!0,e)},m)},X=e=>{if(R(),z(),g<=0){q(!1,e);return}N=setTimeout(()=>{N=null,q(!1,e)},g)},Z=e=>{k||A||j||X(e)};return(0,e.setAria)(o,`expanded`,D),U(D?`open`:`closed`),s.hidden=!D,D&&(F.mount(),W.enter(),s.hidden=!1,H(),G.start(),G.update()),P.push((0,e.on)(o,`pointerenter`,e=>{e.pointerType!==`touch`&&(k=!0,!L()&&Y(`pointer`))}),(0,e.on)(o,`pointerleave`,e=>{if(e.pointerType===`touch`)return;k=!1;let t=e.relatedTarget;t&&s.contains(t)||Z(`pointer`)})),P.push((0,e.on)(s,`pointerenter`,e=>{e.pointerType!==`touch`&&(A=!0,z())}),(0,e.on)(s,`pointerleave`,e=>{if(e.pointerType===`touch`)return;A=!1;let t=e.relatedTarget;t&&o.contains(t)||Z(`pointer`)})),P.push((0,e.on)(o,`focusin`,()=>{j=!0,!L()&&Y(`focus`)}),(0,e.on)(o,`focusout`,e=>{let t=e.relatedTarget;t&&(o.contains(t)||s.contains(t))||(j=!1,Z(`blur`))}),(0,e.on)(s,`focusin`,()=>{j=!0,z()}),(0,e.on)(s,`focusout`,e=>{let t=e.relatedTarget;t&&(o.contains(t)||s.contains(t))||(j=!1,Z(`blur`))})),P.push((0,e.createDismissLayer)({root:i,isOpen:()=>D,onDismiss:()=>q(!1,`dismiss`),closeOnClickOutside:v,closeOnEscape:y})),P.push((0,e.on)(i,`hover-card:set`,e=>{let t=e.detail,n;t?.open===void 0?t?.value!==void 0&&(n=t.value):n=t.open,typeof n==`boolean`&&J(n,`api`)})),{open:()=>{L()||(B(),q(!0,`api`))},close:()=>{B(),q(!1,`api`)},toggle:()=>{B(),!(!D&&L())&&q(!D,`api`)},setOpen:e=>{B(),J(e,`api`)},get isOpen(){return D},destroy:()=>{O=!0,B(),G.stop(),W.cleanup(),F.cleanup(),P.forEach(e=>e()),P.length=0}}}const a=new WeakSet;function o(t=document){let n=[];for(let r of(0,e.getRoots)(t,`hover-card`))a.has(r)||(a.add(r),n.push(i(r)));return n}exports.create=o,exports.createHoverCard=i;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{computeFloatingPosition as e,createDismissLayer as t,createPortalLifecycle as n,createPositionSync as r,createPresenceLifecycle as i,emit as a,ensureId as ee,getDataBool as o,getDataEnum as s,getDataNumber as c,getPart as l,getRoots as u,on as d,setAria as f}from"@data-slot/core";const p=[`top`,`right`,`bottom`,`left`],m=[`start`,`center`,`end`];let h=0;function g(u,g={}){let _=l(u,`hover-card-trigger`),v=l(u,`hover-card-content`),y=l(u,`hover-card-positioner`),b=y&&v&&y.contains(v)?y:null,x=l(u,`hover-card-portal`),te=x&&b&&x.contains(b)?x:null;if(!_||!v)throw Error(`Hover-card requires trigger and content slots`);let ne=g.open!==void 0,S=g.defaultOpen??o(u,`defaultOpen`)??!1,C=g.delay??c(u,`delay`)??700,w=g.skipDelayDuration??c(u,`skipDelayDuration`)??300,T=g.closeDelay??c(u,`closeDelay`)??300,re=g.onOpenChange,ie=g.closeOnClickOutside??o(u,`closeOnClickOutside`)??!0,ae=g.closeOnEscape??o(u,`closeOnEscape`)??!0,oe=g.portal??o(v,`portal`)??o(u,`portal`)??!0,E=g.side??s(v,`side`,p)??s(u,`side`,p)??`bottom`,D=g.align??s(v,`align`,m)??s(u,`align`,m)??`center`,se=g.sideOffset??c(v,`sideOffset`)??c(u,`sideOffset`)??4,O=g.alignOffset??c(v,`alignOffset`)??c(u,`alignOffset`)??0,k=g.avoidCollisions??o(v,`avoidCollisions`)??o(u,`avoidCollisions`)??!0,A=g.collisionPadding??c(v,`collisionPadding`)??c(u,`collisionPadding`)??8,j=g.open??S,M=!1,N=!1,P=!1,F=!1,I=null,L=null,R=[],z=n({content:v,root:u,enabled:oe,wrapperSlot:b?void 0:`hover-card-positioner`,container:b??void 0,mountTarget:b?te??b:void 0}),ce=ee(v,`hover-card-content`);_.setAttribute(`aria-haspopup`,`dialog`),_.setAttribute(`aria-controls`,ce),v.setAttribute(`data-side`,E),v.setAttribute(`data-align`,D);let B=()=>_.hasAttribute(`disabled`)||_.getAttribute(`aria-disabled`)===`true`,V=()=>{I&&=(clearTimeout(I),null)},H=()=>{L&&=(clearTimeout(L),null)},U=()=>{V(),H()},W=(e,t)=>{a(u,`hover-card:change`,{open:e,reason:t,trigger:_,content:v}),re?.(e)},G=()=>{let t=z.container,n=u.ownerDocument.defaultView??window,r=e({anchorRect:_.getBoundingClientRect(),contentRect:v.getBoundingClientRect(),side:E,align:D,sideOffset:se,alignOffset:O,avoidCollisions:k,collisionPadding:A});t.style.position=`absolute`,t.style.top=`0px`,t.style.left=`0px`,t.style.transform=`translate3d(${r.x+n.scrollX}px, ${r.y+n.scrollY}px, 0)`,t.style.willChange=`transform`,t.style.margin=`0`,v.setAttribute(`data-side`,r.side),v.setAttribute(`data-align`,r.align),t!==v&&(t.setAttribute(`data-side`,r.side),t.setAttribute(`data-align`,r.align))},K=e=>{let t=z.container;if(u.setAttribute(`data-state`,e),v.setAttribute(`data-state`,e),t!==v&&t.setAttribute(`data-state`,e),e===`open`){u.setAttribute(`data-open`,``),v.setAttribute(`data-open`,``),t!==v&&t.setAttribute(`data-open`,``),u.removeAttribute(`data-closed`),v.removeAttribute(`data-closed`),t!==v&&t.removeAttribute(`data-closed`);return}u.setAttribute(`data-closed`,``),v.setAttribute(`data-closed`,``),t!==v&&t.setAttribute(`data-closed`,``),u.removeAttribute(`data-open`),v.removeAttribute(`data-open`),t!==v&&t.removeAttribute(`data-open`)},q=i({element:v,onExitComplete:()=>{M||(z.restore(),v.hidden=!0)}}),J=r({observedElements:[_,v],isActive:()=>j,ancestorScroll:!1,onUpdate:G}),Y=(e,t)=>{j!==e&&(!e&&j&&w>0&&(h=Date.now()+w),j=e,f(_,`expanded`,j),e?(z.mount(),v.hidden=!1,K(`open`),q.enter(),G(),J.start(),J.update()):(K(`closed`),q.exit(),J.stop()),W(j,t))},X=(e,t)=>{if(j!==e){if(ne){W(e,t);return}Y(e,t)}},Z=(e,t)=>{Y(e,t)},Q=e=>{if(H(),V(),w>0&&Date.now()<h){X(!0,e);return}if(C<=0){X(!0,e);return}I=setTimeout(()=>{I=null,X(!0,e)},C)},le=e=>{if(V(),H(),T<=0){X(!1,e);return}L=setTimeout(()=>{L=null,X(!1,e)},T)},$=e=>{N||P||F||le(e)};return f(_,`expanded`,j),K(j?`open`:`closed`),v.hidden=!j,j&&(z.mount(),q.enter(),v.hidden=!1,G(),J.start(),J.update()),R.push(d(_,`pointerenter`,e=>{e.pointerType!==`touch`&&(N=!0,!B()&&Q(`pointer`))}),d(_,`pointerleave`,e=>{if(e.pointerType===`touch`)return;N=!1;let t=e.relatedTarget;t&&v.contains(t)||$(`pointer`)})),R.push(d(v,`pointerenter`,e=>{e.pointerType!==`touch`&&(P=!0,H())}),d(v,`pointerleave`,e=>{if(e.pointerType===`touch`)return;P=!1;let t=e.relatedTarget;t&&_.contains(t)||$(`pointer`)})),R.push(d(_,`focusin`,()=>{F=!0,!B()&&Q(`focus`)}),d(_,`focusout`,e=>{let t=e.relatedTarget;t&&(_.contains(t)||v.contains(t))||(F=!1,$(`blur`))}),d(v,`focusin`,()=>{F=!0,H()}),d(v,`focusout`,e=>{let t=e.relatedTarget;t&&(_.contains(t)||v.contains(t))||(F=!1,$(`blur`))})),R.push(t({root:u,isOpen:()=>j,onDismiss:()=>X(!1,`dismiss`),closeOnClickOutside:ie,closeOnEscape:ae})),R.push(d(u,`hover-card:set`,e=>{let t=e.detail,n;t?.open===void 0?t?.value!==void 0&&(n=t.value):n=t.open,typeof n==`boolean`&&Z(n,`api`)})),{open:()=>{B()||(U(),X(!0,`api`))},close:()=>{U(),X(!1,`api`)},toggle:()=>{U(),!(!j&&B())&&X(!j,`api`)},setOpen:e=>{U(),Z(e,`api`)},get isOpen(){return j},destroy:()=>{M=!0,U(),J.stop(),q.cleanup(),z.cleanup(),R.forEach(e=>e()),R.length=0}}}const _=new WeakSet;function v(e=document){let t=[];for(let n of u(e,`hover-card`))_.has(n)||(_.add(n),t.push(g(n)));return t}export{v as create,g as createHoverCard};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@data-slot/hover-card",
|
|
3
|
+
"version": "0.2.80",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsdown"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/bejamas/data-slot.git",
|
|
30
|
+
"directory": "packages/hover-card"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"headless",
|
|
34
|
+
"ui",
|
|
35
|
+
"hover-card",
|
|
36
|
+
"preview-card",
|
|
37
|
+
"vanilla",
|
|
38
|
+
"data-slot"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@data-slot/core": "workspace:*"
|
|
43
|
+
}
|
|
44
|
+
}
|