ajo-ui 0.1.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/LICENSE +15 -0
- package/README.md +126 -0
- package/dist/accordion.js +130 -0
- package/dist/avatar.js +54 -0
- package/dist/calendar.js +2 -0
- package/dist/carousel.js +239 -0
- package/dist/chart.js +790 -0
- package/dist/checkbox-group.js +70 -0
- package/dist/checkbox.js +77 -0
- package/dist/chunks/bar-CVafh6C1.js +99 -0
- package/dist/chunks/calendar-q8jZ8Cxr.js +1923 -0
- package/dist/chunks/collection-DtRB63U4.js +111 -0
- package/dist/chunks/data-table-DpkWv4y4.js +1039 -0
- package/dist/chunks/menu-B45IyHHC.js +704 -0
- package/dist/chunks/native-BJdhd9XJ.js +20 -0
- package/dist/chunks/popup-C8Bb3l_g.js +459 -0
- package/dist/chunks/popup-surface-BDCgtVU0.js +18 -0
- package/dist/chunks/position-D6_i_SRn.js +434 -0
- package/dist/chunks/virtual-list-B7hjGkjk.js +413 -0
- package/dist/collapsible.js +106 -0
- package/dist/command.js +263 -0
- package/dist/context-menu.js +112 -0
- package/dist/data-table.js +5 -0
- package/dist/dialog.js +207 -0
- package/dist/direction.js +22 -0
- package/dist/drawer.js +139 -0
- package/dist/field.js +26 -0
- package/dist/index.js +38 -0
- package/dist/input-date.js +994 -0
- package/dist/input-group.js +52 -0
- package/dist/input-otp.js +179 -0
- package/dist/menu.js +2 -0
- package/dist/menubar.js +236 -0
- package/dist/message-scroller.js +446 -0
- package/dist/navigation-menu.js +330 -0
- package/dist/popover.js +307 -0
- package/dist/progress.js +39 -0
- package/dist/radio-group.js +107 -0
- package/dist/resizable.js +172 -0
- package/dist/select.js +961 -0
- package/dist/sidebar.js +343 -0
- package/dist/slider.js +259 -0
- package/dist/switch.js +53 -0
- package/dist/tabs.js +182 -0
- package/dist/toast.js +492 -0
- package/dist/toggle-group.js +111 -0
- package/dist/toggle.js +52 -0
- package/dist/toolbar.js +127 -0
- package/dist/tooltip.js +196 -0
- package/dist/utils.js +104 -0
- package/dist/virtual-list.js +2 -0
- package/package.json +250 -0
- package/src/accordion.tsx +261 -0
- package/src/availability.ts +261 -0
- package/src/avatar.tsx +99 -0
- package/src/bar.ts +156 -0
- package/src/calendar.tsx +1441 -0
- package/src/carousel.tsx +424 -0
- package/src/chart.tsx +1194 -0
- package/src/checkbox-group.tsx +132 -0
- package/src/checkbox.tsx +130 -0
- package/src/collapsible.tsx +188 -0
- package/src/collection.ts +154 -0
- package/src/command.tsx +511 -0
- package/src/context-menu.tsx +233 -0
- package/src/data-table-contract.ts +143 -0
- package/src/data-table-model.ts +760 -0
- package/src/data-table.tsx +475 -0
- package/src/dialog.tsx +393 -0
- package/src/direction.tsx +45 -0
- package/src/drawer.tsx +251 -0
- package/src/field.tsx +61 -0
- package/src/index.ts +37 -0
- package/src/input-date.tsx +1539 -0
- package/src/input-group.tsx +142 -0
- package/src/input-otp.tsx +324 -0
- package/src/menu-cluster.ts +124 -0
- package/src/menu.tsx +1095 -0
- package/src/menubar.tsx +459 -0
- package/src/message-scroller.tsx +732 -0
- package/src/native.ts +26 -0
- package/src/navigation-menu.tsx +578 -0
- package/src/popover.tsx +519 -0
- package/src/popup-surface.tsx +31 -0
- package/src/popup.ts +569 -0
- package/src/position.ts +523 -0
- package/src/progress.tsx +70 -0
- package/src/radio-group.tsx +186 -0
- package/src/resizable.tsx +310 -0
- package/src/segments.ts +922 -0
- package/src/select.tsx +1501 -0
- package/src/sidebar.tsx +683 -0
- package/src/slider.tsx +424 -0
- package/src/switch.tsx +104 -0
- package/src/tabs.tsx +314 -0
- package/src/toast.tsx +923 -0
- package/src/toggle-group.tsx +249 -0
- package/src/toggle.tsx +91 -0
- package/src/toolbar.tsx +212 -0
- package/src/tooltip.tsx +359 -0
- package/src/utils.ts +204 -0
- package/src/virtual-list.tsx +205 -0
- package/src/virtual.ts +385 -0
package/src/popover.tsx
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import type { IntrinsicElements, Stateful, Stateless, WithChildren } from 'ajo'
|
|
2
|
+
import type { Host } from 'ajo-cloves'
|
|
3
|
+
import { callHandler, callRef, statefulRootAttrs as rootAttrs } from 'ajo-cloves'
|
|
4
|
+
import { context } from 'ajo/context'
|
|
5
|
+
import { contentAttrs, popup, type PopupView } from './popup'
|
|
6
|
+
import { PopupSurface } from './popup-surface'
|
|
7
|
+
import type { ReservedPositionArg } from './position'
|
|
8
|
+
import { popupStyle, triggerAttrs, type FixedArgs, type OmitArg, type PopupPosition } from './utils'
|
|
9
|
+
export type { PopupPlacement, PopupPosition } from './utils'
|
|
10
|
+
|
|
11
|
+
/** Interaction that opens a popover. */
|
|
12
|
+
export type PopoverOpenOn = 'click' | 'hover'
|
|
13
|
+
|
|
14
|
+
/** Props for the popover root and its controlled open state. */
|
|
15
|
+
export type PopoverArgs = WithChildren<OmitArg<IntrinsicElements['div'], 'onchange' | ReservedPositionArg> & PopupPosition & {
|
|
16
|
+
/** Accessible and visible title owned by the popover surface. */
|
|
17
|
+
label: string
|
|
18
|
+
/** Optional visible description associated with the popover surface. */
|
|
19
|
+
description?: string
|
|
20
|
+
/** Controlled open state. */
|
|
21
|
+
open?: boolean
|
|
22
|
+
/** Initial open state for uncontrolled usage. */
|
|
23
|
+
defaultOpen?: boolean
|
|
24
|
+
/** Interaction mode that opens the popover. Fixed at mount. Defaults to click. */
|
|
25
|
+
openOn?: PopoverOpenOn
|
|
26
|
+
/** Hover-mode-only delay before opening, in milliseconds. */
|
|
27
|
+
openDelay?: number
|
|
28
|
+
/** Hover-mode-only delay before closing, in milliseconds. */
|
|
29
|
+
closeDelay?: number
|
|
30
|
+
/** Disable trigger activation. */
|
|
31
|
+
disabled?: boolean
|
|
32
|
+
/** Called whenever the popover opens or closes. */
|
|
33
|
+
onOpenChange?: (open: boolean, event?: Event) => void
|
|
34
|
+
/** Additional CSS classes. */
|
|
35
|
+
class?: string
|
|
36
|
+
}> & FixedArgs<'onchange' | ReservedPositionArg>
|
|
37
|
+
|
|
38
|
+
type PopoverTriggerSharedArgs = {
|
|
39
|
+
/** Render the trigger wrapper as an anchor, button, or span. */
|
|
40
|
+
as?: 'a' | 'button' | 'span'
|
|
41
|
+
/** Additional CSS classes. */
|
|
42
|
+
class?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Props for the element that opens a popover. */
|
|
46
|
+
export type PopoverTriggerArgs = WithChildren<
|
|
47
|
+
| (IntrinsicElements['button'] & PopoverTriggerSharedArgs & { as?: 'button' })
|
|
48
|
+
| (IntrinsicElements['a'] & PopoverTriggerSharedArgs & { as: 'a' })
|
|
49
|
+
| (IntrinsicElements['span'] & PopoverTriggerSharedArgs & { as: 'span' })
|
|
50
|
+
>
|
|
51
|
+
|
|
52
|
+
type PopoverTriggerAllArgs = WithChildren<(IntrinsicElements['a'] & IntrinsicElements['button'] & IntrinsicElements['span']) & PopoverTriggerSharedArgs>
|
|
53
|
+
|
|
54
|
+
/** Props for the positioned popover panel. Semantics come from the root label. */
|
|
55
|
+
export type PopoverContentArgs = WithChildren<OmitArg<IntrinsicElements['div'], 'align' | 'aria-describedby' | 'aria-label' | 'aria-labelledby' | 'aria-modal' | 'id' | 'popover' | 'role' | 'tabindex' | 'tabIndex' | ReservedPositionArg> & {
|
|
56
|
+
/** Extends the visual surface toward its positioning reference. */
|
|
57
|
+
arrow?: boolean
|
|
58
|
+
/** Additional CSS classes. */
|
|
59
|
+
class?: string
|
|
60
|
+
/** Inline CSS string. */
|
|
61
|
+
style?: string
|
|
62
|
+
}> & FixedArgs<'aria-describedby' | 'aria-label' | 'aria-labelledby' | 'aria-modal' | 'gap' | 'id' | 'placement' | 'popover' | 'role' | 'tabindex' | 'tabIndex' | ReservedPositionArg>
|
|
63
|
+
|
|
64
|
+
/** Props for an explicit positioning anchor. */
|
|
65
|
+
export type PopoverAnchorArgs = WithChildren<IntrinsicElements['div'] & {
|
|
66
|
+
/** Additional CSS classes. */
|
|
67
|
+
class?: string
|
|
68
|
+
}>
|
|
69
|
+
|
|
70
|
+
type PopoverContextValue = {
|
|
71
|
+
arrowAttrs: PopupView['arrowAttrs']
|
|
72
|
+
close: (event?: Event) => void
|
|
73
|
+
content: HTMLDivElement | null
|
|
74
|
+
contentId: string
|
|
75
|
+
contentStyle: PopupView['contentStyle']
|
|
76
|
+
description?: string
|
|
77
|
+
disabled: boolean
|
|
78
|
+
label: string
|
|
79
|
+
open: boolean
|
|
80
|
+
openOn: PopoverOpenOn
|
|
81
|
+
openWithDelay?: (event?: Event) => void
|
|
82
|
+
registerContentFocus?: (focused: boolean, event?: Event) => void
|
|
83
|
+
registerContentHover?: (hovering: boolean, event?: Event) => void
|
|
84
|
+
registerTriggerFocus?: (focused: boolean, event?: Event) => void
|
|
85
|
+
registerTriggerHover?: (hovering: boolean, event?: Event) => void
|
|
86
|
+
setReference: (element: HTMLElement | null) => void
|
|
87
|
+
setContent: (element: HTMLDivElement | null) => void
|
|
88
|
+
setOpen: (open: boolean, event?: Event) => void
|
|
89
|
+
setTrigger: (element: HTMLElement | null) => void
|
|
90
|
+
trigger: HTMLElement | null
|
|
91
|
+
triggerId: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const PopoverContext = context<PopoverContextValue | null>(null)
|
|
95
|
+
|
|
96
|
+
/** Shared root body for both interaction modes; the mode is fixed per host. */
|
|
97
|
+
function* popoverEngine(
|
|
98
|
+
this: Host<HTMLDivElement, PopoverArgs>,
|
|
99
|
+
initial: PopoverArgs,
|
|
100
|
+
mode: PopoverOpenOn,
|
|
101
|
+
) {
|
|
102
|
+
let closeDelay = 300
|
|
103
|
+
let disabled = false
|
|
104
|
+
let onOpenChange: PopoverArgs['onOpenChange']
|
|
105
|
+
let openDelay = 700
|
|
106
|
+
let popover: PopupView<HTMLElement, HTMLDivElement>
|
|
107
|
+
|
|
108
|
+
popover = popup<HTMLElement, HTMLDivElement>(this, {
|
|
109
|
+
prefix: 'popover',
|
|
110
|
+
profile: 'popover',
|
|
111
|
+
initialOpen: Boolean(initial.open ?? initial.defaultOpen),
|
|
112
|
+
disabled: () => disabled,
|
|
113
|
+
hover: mode === 'hover' ? {
|
|
114
|
+
openDelay: () => openDelay,
|
|
115
|
+
closeDelay: () => closeDelay,
|
|
116
|
+
} : undefined,
|
|
117
|
+
onOpenChange: (next, event) => onOpenChange?.(next, event),
|
|
118
|
+
reference: view => view.reference ?? view.trigger,
|
|
119
|
+
referenceHidden: 'close',
|
|
120
|
+
dismiss: {
|
|
121
|
+
prevent: mode === 'hover',
|
|
122
|
+
outside: true,
|
|
123
|
+
onDismiss: (event, view) => {
|
|
124
|
+
view.cancelHover()
|
|
125
|
+
view.setOpen(false, event)
|
|
126
|
+
if (mode === 'click') queueMicrotask(() => view.trigger?.focus())
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const close = (event?: Event) => {
|
|
132
|
+
popover.cancelHover()
|
|
133
|
+
popover.close(event)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const openWithDelay = (event?: Event) => popover.hold('trigger', event as Event)
|
|
137
|
+
|
|
138
|
+
const registerTriggerHover = (hovering: boolean, event?: Event) =>
|
|
139
|
+
hovering ? popover.hold('trigger', event as Event) : popover.release('trigger', event as Event)
|
|
140
|
+
|
|
141
|
+
const registerContentHover = (hovering: boolean, event?: Event) =>
|
|
142
|
+
hovering ? popover.hold('content', event as Event) : popover.release('content', event as Event)
|
|
143
|
+
|
|
144
|
+
const registerTriggerFocus = (focused: boolean, event?: Event) =>
|
|
145
|
+
focused ? popover.hold('focus-trigger', event as Event) : popover.release('focus-trigger', event as Event)
|
|
146
|
+
|
|
147
|
+
const registerContentFocus = (focused: boolean, event?: Event) =>
|
|
148
|
+
focused ? popover.hold('focus-content', event as Event) : popover.release('focus-content', event as Event)
|
|
149
|
+
|
|
150
|
+
for (const args of this) {
|
|
151
|
+
closeDelay = Math.max(0, Number(args.closeDelay ?? 300))
|
|
152
|
+
disabled = Boolean(args.disabled)
|
|
153
|
+
onOpenChange = args.onOpenChange
|
|
154
|
+
openDelay = Math.max(0, Number(args.openDelay ?? 700))
|
|
155
|
+
const opened = popover.sync(args.open == null ? null : Boolean(args.open), {
|
|
156
|
+
placement: args.placement,
|
|
157
|
+
gap: args.gap,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
PopoverContext({
|
|
161
|
+
...popover,
|
|
162
|
+
close: mode === 'hover' ? close : popover.close,
|
|
163
|
+
description: args.description,
|
|
164
|
+
disabled,
|
|
165
|
+
label: args.label,
|
|
166
|
+
open: opened,
|
|
167
|
+
openOn: mode,
|
|
168
|
+
...(mode === 'hover' ? {
|
|
169
|
+
openWithDelay,
|
|
170
|
+
registerContentFocus,
|
|
171
|
+
registerContentHover,
|
|
172
|
+
registerTriggerFocus,
|
|
173
|
+
registerTriggerHover,
|
|
174
|
+
} : {}),
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
yield <>{args.children}</>
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const PopoverClickRoot: Stateful<PopoverArgs> = function* (args) {
|
|
182
|
+
yield* popoverEngine.call(this, args, 'click')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
const PopoverHoverRoot: Stateful<PopoverArgs> = function* (args) {
|
|
187
|
+
yield* popoverEngine.call(this, args, 'hover')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
/** Unstyled root provider for a popover. */
|
|
192
|
+
const Popover: Stateless<PopoverArgs> = ({
|
|
193
|
+
children,
|
|
194
|
+
class: classes,
|
|
195
|
+
'data-slot': slot = 'popover',
|
|
196
|
+
closeDelay,
|
|
197
|
+
defaultOpen,
|
|
198
|
+
description,
|
|
199
|
+
disabled,
|
|
200
|
+
gap,
|
|
201
|
+
label,
|
|
202
|
+
onOpenChange,
|
|
203
|
+
open,
|
|
204
|
+
openDelay,
|
|
205
|
+
openOn = 'click',
|
|
206
|
+
placement,
|
|
207
|
+
...attrs
|
|
208
|
+
}) => openOn === 'hover' ? (
|
|
209
|
+
<PopoverHoverRoot
|
|
210
|
+
{...rootAttrs(attrs)}
|
|
211
|
+
closeDelay={closeDelay}
|
|
212
|
+
defaultOpen={defaultOpen}
|
|
213
|
+
description={description}
|
|
214
|
+
disabled={disabled}
|
|
215
|
+
gap={gap}
|
|
216
|
+
label={label}
|
|
217
|
+
onOpenChange={onOpenChange}
|
|
218
|
+
open={open}
|
|
219
|
+
openDelay={openDelay}
|
|
220
|
+
placement={placement}
|
|
221
|
+
attr:class={classes}
|
|
222
|
+
attr:data-slot={slot}
|
|
223
|
+
>
|
|
224
|
+
{children}
|
|
225
|
+
</PopoverHoverRoot>
|
|
226
|
+
) : (
|
|
227
|
+
<PopoverClickRoot
|
|
228
|
+
{...rootAttrs(attrs)}
|
|
229
|
+
defaultOpen={defaultOpen}
|
|
230
|
+
description={description}
|
|
231
|
+
disabled={disabled}
|
|
232
|
+
gap={gap}
|
|
233
|
+
label={label}
|
|
234
|
+
onOpenChange={onOpenChange}
|
|
235
|
+
open={open}
|
|
236
|
+
placement={placement}
|
|
237
|
+
attr:class={classes}
|
|
238
|
+
attr:data-slot={slot}
|
|
239
|
+
>
|
|
240
|
+
{children}
|
|
241
|
+
</PopoverClickRoot>
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
/** Unstyled button that opens a Popover. */
|
|
245
|
+
const PopoverTrigger: Stateless<PopoverTriggerArgs> = args => {
|
|
246
|
+
const popover = PopoverContext()
|
|
247
|
+
const all = args as PopoverTriggerAllArgs
|
|
248
|
+
|
|
249
|
+
if (popover?.openOn === 'hover') {
|
|
250
|
+
const {
|
|
251
|
+
as = 'button',
|
|
252
|
+
children,
|
|
253
|
+
class: classes,
|
|
254
|
+
'data-slot': slot = 'popover-trigger',
|
|
255
|
+
disabled,
|
|
256
|
+
id,
|
|
257
|
+
ref,
|
|
258
|
+
type = 'button',
|
|
259
|
+
'set:onblur': onBlur,
|
|
260
|
+
'set:onfocus': onFocus,
|
|
261
|
+
'set:onkeydown': onKeydown,
|
|
262
|
+
'set:onmouseleave': onMouseLeave,
|
|
263
|
+
'set:onmouseenter': onMouseEnter,
|
|
264
|
+
...attrs
|
|
265
|
+
} = all
|
|
266
|
+
const disabledFlag = Boolean(disabled ?? popover.disabled)
|
|
267
|
+
|
|
268
|
+
const common = {
|
|
269
|
+
...attrs,
|
|
270
|
+
...triggerAttrs({
|
|
271
|
+
controls: popover.contentId,
|
|
272
|
+
expanded: popover.open,
|
|
273
|
+
haspopup: 'dialog',
|
|
274
|
+
id,
|
|
275
|
+
open: popover.open,
|
|
276
|
+
ref,
|
|
277
|
+
setTrigger: popover.setTrigger,
|
|
278
|
+
triggerId: popover.triggerId,
|
|
279
|
+
}),
|
|
280
|
+
class: classes,
|
|
281
|
+
'data-slot': slot,
|
|
282
|
+
'set:onblur': (event: FocusEvent) => {
|
|
283
|
+
callHandler(onBlur, event)
|
|
284
|
+
popover.registerTriggerFocus?.(false, event)
|
|
285
|
+
},
|
|
286
|
+
'set:onfocus': (event: FocusEvent) => {
|
|
287
|
+
callHandler(onFocus, event)
|
|
288
|
+
if (event.defaultPrevented || disabledFlag) return
|
|
289
|
+
popover.registerTriggerFocus?.(true, event)
|
|
290
|
+
},
|
|
291
|
+
'set:onkeydown': (event: KeyboardEvent) => {
|
|
292
|
+
callHandler(onKeydown, event)
|
|
293
|
+
},
|
|
294
|
+
'set:onmouseleave': (event: MouseEvent) => {
|
|
295
|
+
callHandler(onMouseLeave, event)
|
|
296
|
+
popover.registerTriggerHover?.(false, event)
|
|
297
|
+
},
|
|
298
|
+
'set:onmouseenter': (event: MouseEvent) => {
|
|
299
|
+
callHandler(onMouseEnter, event)
|
|
300
|
+
if (event.defaultPrevented || disabledFlag) return
|
|
301
|
+
popover.registerTriggerHover?.(true, event)
|
|
302
|
+
},
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (as === 'a') {
|
|
306
|
+
const anchor = attrs as IntrinsicElements['a']
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<a
|
|
310
|
+
{...common}
|
|
311
|
+
aria-disabled={disabledFlag ? 'true' : undefined}
|
|
312
|
+
href={disabledFlag ? undefined : anchor.href}
|
|
313
|
+
tabIndex={disabledFlag ? -1 : anchor.tabIndex}
|
|
314
|
+
>
|
|
315
|
+
{children}
|
|
316
|
+
</a>
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (as === 'span') {
|
|
321
|
+
return (
|
|
322
|
+
<span
|
|
323
|
+
{...common}
|
|
324
|
+
aria-disabled={disabledFlag ? 'true' : undefined}
|
|
325
|
+
>
|
|
326
|
+
{children}
|
|
327
|
+
</span>
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return (
|
|
332
|
+
<button
|
|
333
|
+
{...common}
|
|
334
|
+
disabled={disabledFlag}
|
|
335
|
+
type={type}
|
|
336
|
+
>
|
|
337
|
+
{children}
|
|
338
|
+
</button>
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const {
|
|
343
|
+
children,
|
|
344
|
+
'data-slot': slot = 'popover-trigger',
|
|
345
|
+
disabled,
|
|
346
|
+
id,
|
|
347
|
+
ref,
|
|
348
|
+
type = 'button',
|
|
349
|
+
'set:onclick': onClick,
|
|
350
|
+
...attrs
|
|
351
|
+
} = all
|
|
352
|
+
const disabledFlag = Boolean(disabled ?? popover?.disabled)
|
|
353
|
+
|
|
354
|
+
return (
|
|
355
|
+
<button
|
|
356
|
+
{...attrs}
|
|
357
|
+
{...triggerAttrs({
|
|
358
|
+
controls: popover?.contentId,
|
|
359
|
+
expanded: Boolean(popover?.open),
|
|
360
|
+
haspopup: 'dialog',
|
|
361
|
+
id,
|
|
362
|
+
open: Boolean(popover?.open),
|
|
363
|
+
ref,
|
|
364
|
+
setTrigger: popover?.setTrigger,
|
|
365
|
+
triggerId: popover?.triggerId,
|
|
366
|
+
})}
|
|
367
|
+
data-slot={slot}
|
|
368
|
+
disabled={disabledFlag}
|
|
369
|
+
set:onclick={(event: Event) => {
|
|
370
|
+
callHandler(onClick, event)
|
|
371
|
+
if (event.defaultPrevented) return
|
|
372
|
+
popover?.setOpen(!popover.open, event)
|
|
373
|
+
}}
|
|
374
|
+
type={type}
|
|
375
|
+
>
|
|
376
|
+
{children}
|
|
377
|
+
</button>
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Optional explicit anchor used to position PopoverContent independently of the trigger. */
|
|
382
|
+
const PopoverAnchor: Stateless<PopoverAnchorArgs> = ({
|
|
383
|
+
children,
|
|
384
|
+
class: classes,
|
|
385
|
+
'data-slot': slot = 'popover-anchor',
|
|
386
|
+
ref,
|
|
387
|
+
...attrs
|
|
388
|
+
}) => {
|
|
389
|
+
const popover = PopoverContext()
|
|
390
|
+
const reference = (element: HTMLDivElement | null) => {
|
|
391
|
+
popover?.setReference(element)
|
|
392
|
+
callRef(ref, element)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<div
|
|
397
|
+
{...attrs}
|
|
398
|
+
class={classes}
|
|
399
|
+
data-slot={slot}
|
|
400
|
+
ref={reference}
|
|
401
|
+
>
|
|
402
|
+
{children}
|
|
403
|
+
</div>
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Unstyled floating panel for a Popover. */
|
|
408
|
+
const PopoverContent: Stateless<PopoverContentArgs> = args => {
|
|
409
|
+
const popover = PopoverContext()
|
|
410
|
+
const titleId = popover ? `${popover.contentId}-title` : undefined
|
|
411
|
+
const descriptionId = popover?.description ? `${popover.contentId}-description` : undefined
|
|
412
|
+
const heading = popover ? (
|
|
413
|
+
<div data-slot="popover-header">
|
|
414
|
+
<h2 data-slot="popover-title" id={titleId}>{popover.label}</h2>
|
|
415
|
+
{popover.description ? (
|
|
416
|
+
<p data-slot="popover-description" id={descriptionId}>{popover.description}</p>
|
|
417
|
+
) : null}
|
|
418
|
+
</div>
|
|
419
|
+
) : null
|
|
420
|
+
|
|
421
|
+
if (popover?.openOn === 'hover') {
|
|
422
|
+
const {
|
|
423
|
+
arrow = false,
|
|
424
|
+
children,
|
|
425
|
+
class: classes,
|
|
426
|
+
'data-slot': slot = 'popover-content',
|
|
427
|
+
ref,
|
|
428
|
+
style,
|
|
429
|
+
'set:onfocusin': onFocusIn,
|
|
430
|
+
'set:onfocusout': onFocusOut,
|
|
431
|
+
'set:onmouseleave': onMouseLeave,
|
|
432
|
+
'set:onmouseenter': onMouseEnter,
|
|
433
|
+
...attrs
|
|
434
|
+
} = args
|
|
435
|
+
|
|
436
|
+
return (
|
|
437
|
+
<div
|
|
438
|
+
{...attrs}
|
|
439
|
+
{...contentAttrs({
|
|
440
|
+
id: popover.contentId,
|
|
441
|
+
open: popover.open,
|
|
442
|
+
ref,
|
|
443
|
+
setContent: popover.setContent,
|
|
444
|
+
style: popover.contentStyle(style),
|
|
445
|
+
tabindex: '-1',
|
|
446
|
+
})}
|
|
447
|
+
aria-describedby={descriptionId}
|
|
448
|
+
aria-labelledby={titleId}
|
|
449
|
+
class={classes}
|
|
450
|
+
data-arrow={arrow ? 'true' : undefined}
|
|
451
|
+
data-slot={slot}
|
|
452
|
+
role="dialog"
|
|
453
|
+
set:onfocusout={(event: FocusEvent) => {
|
|
454
|
+
callHandler(onFocusOut, event)
|
|
455
|
+
const next = event.relatedTarget as Node | null
|
|
456
|
+
if (next && popover.content?.contains(next)) return
|
|
457
|
+
popover.registerContentFocus?.(false, event)
|
|
458
|
+
}}
|
|
459
|
+
set:onfocusin={(event: FocusEvent) => {
|
|
460
|
+
callHandler(onFocusIn, event)
|
|
461
|
+
popover.registerContentFocus?.(true, event)
|
|
462
|
+
}}
|
|
463
|
+
set:onmouseleave={(event: MouseEvent) => {
|
|
464
|
+
callHandler(onMouseLeave, event)
|
|
465
|
+
popover.registerContentHover?.(false, event)
|
|
466
|
+
}}
|
|
467
|
+
set:onmouseenter={(event: MouseEvent) => {
|
|
468
|
+
callHandler(onMouseEnter, event)
|
|
469
|
+
popover.registerContentHover?.(true, event)
|
|
470
|
+
}}
|
|
471
|
+
>
|
|
472
|
+
<PopupSurface arrow={arrow} popup={popover} />
|
|
473
|
+
{heading}
|
|
474
|
+
{children}
|
|
475
|
+
</div>
|
|
476
|
+
)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const {
|
|
480
|
+
arrow = false,
|
|
481
|
+
children,
|
|
482
|
+
class: classes,
|
|
483
|
+
'data-slot': slot = 'popover-content',
|
|
484
|
+
ref,
|
|
485
|
+
style,
|
|
486
|
+
...attrs
|
|
487
|
+
} = args
|
|
488
|
+
|
|
489
|
+
return (
|
|
490
|
+
<div
|
|
491
|
+
{...attrs}
|
|
492
|
+
{...contentAttrs({
|
|
493
|
+
id: popover?.contentId,
|
|
494
|
+
open: Boolean(popover?.open),
|
|
495
|
+
ref,
|
|
496
|
+
setContent: popover?.setContent,
|
|
497
|
+
style: popover?.contentStyle(style) ?? popupStyle(style),
|
|
498
|
+
tabindex: '-1',
|
|
499
|
+
})}
|
|
500
|
+
aria-describedby={descriptionId}
|
|
501
|
+
aria-labelledby={titleId}
|
|
502
|
+
class={classes}
|
|
503
|
+
data-arrow={arrow ? 'true' : undefined}
|
|
504
|
+
data-slot={slot}
|
|
505
|
+
role="dialog"
|
|
506
|
+
>
|
|
507
|
+
<PopupSurface arrow={arrow} popup={popover} />
|
|
508
|
+
{heading}
|
|
509
|
+
{children}
|
|
510
|
+
</div>
|
|
511
|
+
)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export {
|
|
515
|
+
Popover,
|
|
516
|
+
PopoverAnchor,
|
|
517
|
+
PopoverContent,
|
|
518
|
+
PopoverTrigger,
|
|
519
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Stateless } from 'ajo'
|
|
2
|
+
import type { PopupView } from './popup'
|
|
3
|
+
|
|
4
|
+
type PopupSurfaceArgs = {
|
|
5
|
+
/** Whether the surface extends toward its positioning reference. */
|
|
6
|
+
arrow?: boolean
|
|
7
|
+
popup?: Pick<PopupView, 'arrowAttrs'> | null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const probeStyle = 'position:absolute;width:14px;height:14px;pointer-events:none;background:transparent;border:0;box-shadow:none;color:transparent;opacity:0'
|
|
11
|
+
|
|
12
|
+
/** Internal visual surface and optional transparent Floating UI arrow probe. */
|
|
13
|
+
const PopupSurface: Stateless<PopupSurfaceArgs> = ({ arrow = false, popup }) => {
|
|
14
|
+
const probe = arrow ? popup?.arrowAttrs() : undefined
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
<span aria-hidden="true" data-slot="popup-surface" />
|
|
19
|
+
{arrow ? (
|
|
20
|
+
<span
|
|
21
|
+
aria-hidden="true"
|
|
22
|
+
data-slot="popup-arrow"
|
|
23
|
+
ref={probe?.ref}
|
|
24
|
+
style={[probeStyle, probe?.style].filter(Boolean).join(';')}
|
|
25
|
+
/>
|
|
26
|
+
) : null}
|
|
27
|
+
</>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { PopupSurface }
|