@zag-js/popover 0.0.0-dev-20230526205409 → 0.0.0-dev-20230526210710
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/package.json +13 -12
- package/src/index.ts +4 -0
- package/src/popover.anatomy.ts +14 -0
- package/src/popover.connect.ts +119 -0
- package/src/popover.dom.ts +39 -0
- package/src/popover.machine.ts +222 -0
- package/src/popover.types.ts +106 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zag-js/popover",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20230526210710",
|
|
4
4
|
"description": "Core logic for the popover widget implemented as a state machine",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"js",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"repository": "https://github.com/chakra-ui/zag/tree/main/packages/popover",
|
|
18
18
|
"sideEffects": false,
|
|
19
19
|
"files": [
|
|
20
|
-
"dist
|
|
20
|
+
"dist",
|
|
21
|
+
"src"
|
|
21
22
|
],
|
|
22
23
|
"publishConfig": {
|
|
23
24
|
"access": "public"
|
|
@@ -27,16 +28,16 @@
|
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
30
|
"focus-trap": "7.4.1",
|
|
30
|
-
"@zag-js/anatomy": "0.0.0-dev-
|
|
31
|
-
"@zag-js/aria-hidden": "0.0.0-dev-
|
|
32
|
-
"@zag-js/core": "0.0.0-dev-
|
|
33
|
-
"@zag-js/dom-query": "0.0.0-dev-
|
|
34
|
-
"@zag-js/utils": "0.0.0-dev-
|
|
35
|
-
"@zag-js/dismissable": "0.0.0-dev-
|
|
36
|
-
"@zag-js/tabbable": "0.0.0-dev-
|
|
37
|
-
"@zag-js/popper": "0.0.0-dev-
|
|
38
|
-
"@zag-js/remove-scroll": "0.0.0-dev-
|
|
39
|
-
"@zag-js/types": "0.0.0-dev-
|
|
31
|
+
"@zag-js/anatomy": "0.0.0-dev-20230526210710",
|
|
32
|
+
"@zag-js/aria-hidden": "0.0.0-dev-20230526210710",
|
|
33
|
+
"@zag-js/core": "0.0.0-dev-20230526210710",
|
|
34
|
+
"@zag-js/dom-query": "0.0.0-dev-20230526210710",
|
|
35
|
+
"@zag-js/utils": "0.0.0-dev-20230526210710",
|
|
36
|
+
"@zag-js/dismissable": "0.0.0-dev-20230526210710",
|
|
37
|
+
"@zag-js/tabbable": "0.0.0-dev-20230526210710",
|
|
38
|
+
"@zag-js/popper": "0.0.0-dev-20230526210710",
|
|
39
|
+
"@zag-js/remove-scroll": "0.0.0-dev-20230526210710",
|
|
40
|
+
"@zag-js/types": "0.0.0-dev-20230526210710"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"clean-package": "2.2.0"
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createAnatomy } from "@zag-js/anatomy"
|
|
2
|
+
|
|
3
|
+
export const anatomy = createAnatomy("popover").parts(
|
|
4
|
+
"arrow",
|
|
5
|
+
"arrowTip",
|
|
6
|
+
"anchor",
|
|
7
|
+
"trigger",
|
|
8
|
+
"positioner",
|
|
9
|
+
"content",
|
|
10
|
+
"title",
|
|
11
|
+
"description",
|
|
12
|
+
"closeTrigger",
|
|
13
|
+
)
|
|
14
|
+
export const parts = anatomy.build()
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { dataAttr } from "@zag-js/dom-query"
|
|
2
|
+
import type { PositioningOptions } from "@zag-js/popper"
|
|
3
|
+
import { getPlacementStyles } from "@zag-js/popper"
|
|
4
|
+
import type { NormalizeProps, PropTypes } from "@zag-js/types"
|
|
5
|
+
import { parts } from "./popover.anatomy"
|
|
6
|
+
import { dom } from "./popover.dom"
|
|
7
|
+
import type { Send, State } from "./popover.types"
|
|
8
|
+
|
|
9
|
+
export function connect<T extends PropTypes>(state: State, send: Send, normalize: NormalizeProps<T>) {
|
|
10
|
+
const isOpen = state.matches("open")
|
|
11
|
+
|
|
12
|
+
const currentPlacement = state.context.currentPlacement
|
|
13
|
+
const portalled = state.context.currentPortalled
|
|
14
|
+
const rendered = state.context.renderedElements
|
|
15
|
+
|
|
16
|
+
const popperStyles = getPlacementStyles({
|
|
17
|
+
placement: currentPlacement,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
/**
|
|
22
|
+
* Whether the popover is portalled
|
|
23
|
+
*/
|
|
24
|
+
portalled,
|
|
25
|
+
/**
|
|
26
|
+
* Whether the popover is open
|
|
27
|
+
*/
|
|
28
|
+
isOpen,
|
|
29
|
+
/**
|
|
30
|
+
* Function to open the popover
|
|
31
|
+
*/
|
|
32
|
+
open() {
|
|
33
|
+
send("OPEN")
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* Function to close the popover
|
|
37
|
+
*/
|
|
38
|
+
close() {
|
|
39
|
+
send("CLOSE")
|
|
40
|
+
},
|
|
41
|
+
/**
|
|
42
|
+
* Function to reposition the popover
|
|
43
|
+
*/
|
|
44
|
+
setPositioning(options: Partial<PositioningOptions> = {}) {
|
|
45
|
+
send({ type: "SET_POSITIONING", options })
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
arrowProps: normalize.element({
|
|
49
|
+
id: dom.getArrowId(state.context),
|
|
50
|
+
...parts.arrow.attrs,
|
|
51
|
+
style: popperStyles.arrow,
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
arrowTipProps: normalize.element({
|
|
55
|
+
...parts.arrowTip.attrs,
|
|
56
|
+
style: popperStyles.arrowTip,
|
|
57
|
+
}),
|
|
58
|
+
|
|
59
|
+
anchorProps: normalize.element({
|
|
60
|
+
...parts.anchor.attrs,
|
|
61
|
+
id: dom.getAnchorId(state.context),
|
|
62
|
+
}),
|
|
63
|
+
|
|
64
|
+
triggerProps: normalize.button({
|
|
65
|
+
...parts.trigger.attrs,
|
|
66
|
+
type: "button",
|
|
67
|
+
"data-placement": currentPlacement,
|
|
68
|
+
id: dom.getTriggerId(state.context),
|
|
69
|
+
"aria-haspopup": "dialog",
|
|
70
|
+
"aria-expanded": isOpen,
|
|
71
|
+
"data-expanded": dataAttr(isOpen),
|
|
72
|
+
"aria-controls": dom.getContentId(state.context),
|
|
73
|
+
onClick() {
|
|
74
|
+
send("TOGGLE")
|
|
75
|
+
},
|
|
76
|
+
onBlur(event) {
|
|
77
|
+
send({ type: "TRIGGER_BLUR", target: event.relatedTarget })
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
|
|
81
|
+
positionerProps: normalize.element({
|
|
82
|
+
id: dom.getPositionerId(state.context),
|
|
83
|
+
...parts.positioner.attrs,
|
|
84
|
+
style: popperStyles.floating,
|
|
85
|
+
}),
|
|
86
|
+
|
|
87
|
+
contentProps: normalize.element({
|
|
88
|
+
...parts.content.attrs,
|
|
89
|
+
id: dom.getContentId(state.context),
|
|
90
|
+
tabIndex: -1,
|
|
91
|
+
role: "dialog",
|
|
92
|
+
hidden: !isOpen,
|
|
93
|
+
"data-expanded": dataAttr(isOpen),
|
|
94
|
+
"aria-labelledby": rendered.title ? dom.getTitleId(state.context) : undefined,
|
|
95
|
+
"aria-describedby": rendered.description ? dom.getDescriptionId(state.context) : undefined,
|
|
96
|
+
"data-placement": currentPlacement,
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
titleProps: normalize.element({
|
|
100
|
+
...parts.title.attrs,
|
|
101
|
+
id: dom.getTitleId(state.context),
|
|
102
|
+
}),
|
|
103
|
+
|
|
104
|
+
descriptionProps: normalize.element({
|
|
105
|
+
...parts.description.attrs,
|
|
106
|
+
id: dom.getDescriptionId(state.context),
|
|
107
|
+
}),
|
|
108
|
+
|
|
109
|
+
closeTriggerProps: normalize.button({
|
|
110
|
+
...parts.closeTrigger.attrs,
|
|
111
|
+
id: dom.getCloseTriggerId(state.context),
|
|
112
|
+
type: "button",
|
|
113
|
+
"aria-label": "close",
|
|
114
|
+
onClick() {
|
|
115
|
+
send("REQUEST_CLOSE")
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createScope } from "@zag-js/dom-query"
|
|
2
|
+
import { getFirstTabbable, getFocusables, getLastTabbable, getTabbables } from "@zag-js/tabbable"
|
|
3
|
+
import { runIfFn } from "@zag-js/utils"
|
|
4
|
+
import type { MachineContext as Ctx } from "./popover.types"
|
|
5
|
+
|
|
6
|
+
export const dom = createScope({
|
|
7
|
+
getActiveEl: (ctx: Ctx) => dom.getDoc(ctx).activeElement,
|
|
8
|
+
|
|
9
|
+
getAnchorId: (ctx: Ctx) => ctx.ids?.anchor ?? `popover:${ctx.id}:anchor`,
|
|
10
|
+
getTriggerId: (ctx: Ctx) => ctx.ids?.trigger ?? `popover:${ctx.id}:trigger`,
|
|
11
|
+
getContentId: (ctx: Ctx) => ctx.ids?.content ?? `popover:${ctx.id}:content`,
|
|
12
|
+
getPositionerId: (ctx: Ctx) => ctx.ids?.positioner ?? `popover:${ctx.id}:popper`,
|
|
13
|
+
getArrowId: (ctx: Ctx) => ctx.ids?.arrow ?? `popover:${ctx.id}:arrow`,
|
|
14
|
+
getTitleId: (ctx: Ctx) => ctx.ids?.title ?? `popover:${ctx.id}:title`,
|
|
15
|
+
getDescriptionId: (ctx: Ctx) => ctx.ids?.description ?? `popover:${ctx.id}:desc`,
|
|
16
|
+
getCloseTriggerId: (ctx: Ctx) => ctx.ids?.closeTrigger ?? `popover:${ctx.id}:close`,
|
|
17
|
+
|
|
18
|
+
getAnchorEl: (ctx: Ctx) => dom.getById(ctx, dom.getAnchorId(ctx)),
|
|
19
|
+
getTriggerEl: (ctx: Ctx) => dom.getById(ctx, dom.getTriggerId(ctx)),
|
|
20
|
+
getContentEl: (ctx: Ctx) => dom.getById(ctx, dom.getContentId(ctx)),
|
|
21
|
+
getPositionerEl: (ctx: Ctx) => dom.getById(ctx, dom.getPositionerId(ctx)),
|
|
22
|
+
getTitleEl: (ctx: Ctx) => dom.getById(ctx, dom.getTitleId(ctx)),
|
|
23
|
+
getDescriptionEl: (ctx: Ctx) => dom.getById(ctx, dom.getDescriptionId(ctx)),
|
|
24
|
+
|
|
25
|
+
getFocusableEls: (ctx: Ctx) => getFocusables(dom.getContentEl(ctx)),
|
|
26
|
+
getFirstFocusableEl: (ctx: Ctx) => dom.getFocusableEls(ctx)[0],
|
|
27
|
+
|
|
28
|
+
getDocTabbableEls: (ctx: Ctx) => getTabbables(dom.getDoc(ctx).body),
|
|
29
|
+
getTabbableEls: (ctx: Ctx) => getTabbables(dom.getContentEl(ctx), "if-empty"),
|
|
30
|
+
getFirstTabbableEl: (ctx: Ctx) => getFirstTabbable(dom.getContentEl(ctx), "if-empty"),
|
|
31
|
+
getLastTabbableEl: (ctx: Ctx) => getLastTabbable(dom.getContentEl(ctx), "if-empty"),
|
|
32
|
+
|
|
33
|
+
getInitialFocusEl: (ctx: Ctx) => {
|
|
34
|
+
let el: HTMLElement | null = runIfFn(ctx.initialFocusEl)
|
|
35
|
+
if (!el && ctx.autoFocus) el = dom.getFirstFocusableEl(ctx)
|
|
36
|
+
if (!el) el = dom.getContentEl(ctx)
|
|
37
|
+
return el
|
|
38
|
+
},
|
|
39
|
+
})
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { ariaHidden } from "@zag-js/aria-hidden"
|
|
2
|
+
import { createMachine } from "@zag-js/core"
|
|
3
|
+
import { trackDismissableElement } from "@zag-js/dismissable"
|
|
4
|
+
import { nextTick, raf } from "@zag-js/dom-query"
|
|
5
|
+
import { getPlacement } from "@zag-js/popper"
|
|
6
|
+
import { preventBodyScroll } from "@zag-js/remove-scroll"
|
|
7
|
+
import { proxyTabFocus } from "@zag-js/tabbable"
|
|
8
|
+
import { compact, runIfFn } from "@zag-js/utils"
|
|
9
|
+
import { FocusTrap, createFocusTrap } from "focus-trap"
|
|
10
|
+
import { dom } from "./popover.dom"
|
|
11
|
+
import type { MachineContext, MachineState, UserDefinedContext } from "./popover.types"
|
|
12
|
+
|
|
13
|
+
export function machine(userContext: UserDefinedContext) {
|
|
14
|
+
const ctx = compact(userContext)
|
|
15
|
+
return createMachine<MachineContext, MachineState>(
|
|
16
|
+
{
|
|
17
|
+
id: "popover",
|
|
18
|
+
initial: ctx.open ? "open" : "closed",
|
|
19
|
+
context: {
|
|
20
|
+
closeOnInteractOutside: true,
|
|
21
|
+
closeOnEsc: true,
|
|
22
|
+
autoFocus: true,
|
|
23
|
+
modal: false,
|
|
24
|
+
positioning: {
|
|
25
|
+
placement: "bottom",
|
|
26
|
+
...ctx.positioning,
|
|
27
|
+
},
|
|
28
|
+
currentPlacement: undefined,
|
|
29
|
+
...ctx,
|
|
30
|
+
renderedElements: {
|
|
31
|
+
title: true,
|
|
32
|
+
description: true,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
computed: {
|
|
37
|
+
currentPortalled: (ctx) => !!ctx.modal || !!ctx.portalled,
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
watch: {
|
|
41
|
+
open: ["toggleVisibility"],
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
entry: ["checkRenderedElements"],
|
|
45
|
+
|
|
46
|
+
states: {
|
|
47
|
+
closed: {
|
|
48
|
+
on: {
|
|
49
|
+
TOGGLE: {
|
|
50
|
+
target: "open",
|
|
51
|
+
actions: ["invokeOnOpen"],
|
|
52
|
+
},
|
|
53
|
+
OPEN: {
|
|
54
|
+
target: "open",
|
|
55
|
+
actions: ["invokeOnOpen"],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
open: {
|
|
61
|
+
activities: [
|
|
62
|
+
"trapFocus",
|
|
63
|
+
"preventScroll",
|
|
64
|
+
"hideContentBelow",
|
|
65
|
+
"trackPositioning",
|
|
66
|
+
"trackDismissableElement",
|
|
67
|
+
"proxyTabFocus",
|
|
68
|
+
],
|
|
69
|
+
entry: ["setInitialFocus"],
|
|
70
|
+
on: {
|
|
71
|
+
CLOSE: {
|
|
72
|
+
target: "closed",
|
|
73
|
+
actions: ["invokeOnClose"],
|
|
74
|
+
},
|
|
75
|
+
REQUEST_CLOSE: {
|
|
76
|
+
target: "closed",
|
|
77
|
+
actions: ["restoreFocusIfNeeded", "invokeOnClose"],
|
|
78
|
+
},
|
|
79
|
+
TOGGLE: {
|
|
80
|
+
target: "closed",
|
|
81
|
+
actions: ["invokeOnClose"],
|
|
82
|
+
},
|
|
83
|
+
SET_POSITIONING: {
|
|
84
|
+
actions: "setPositioning",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
activities: {
|
|
92
|
+
trackPositioning(ctx) {
|
|
93
|
+
ctx.currentPlacement = ctx.positioning.placement
|
|
94
|
+
const anchorEl = dom.getAnchorEl(ctx) ?? dom.getTriggerEl(ctx)
|
|
95
|
+
const getPositionerEl = () => dom.getPositionerEl(ctx)
|
|
96
|
+
return getPlacement(anchorEl, getPositionerEl, {
|
|
97
|
+
...ctx.positioning,
|
|
98
|
+
defer: true,
|
|
99
|
+
onComplete(data) {
|
|
100
|
+
ctx.currentPlacement = data.placement
|
|
101
|
+
},
|
|
102
|
+
onCleanup() {
|
|
103
|
+
ctx.currentPlacement = undefined
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
},
|
|
107
|
+
trackDismissableElement(ctx, _evt, { send }) {
|
|
108
|
+
const getContentEl = () => dom.getContentEl(ctx)
|
|
109
|
+
let restoreFocus = true
|
|
110
|
+
return trackDismissableElement(getContentEl, {
|
|
111
|
+
pointerBlocking: ctx.modal,
|
|
112
|
+
exclude: dom.getTriggerEl(ctx),
|
|
113
|
+
defer: true,
|
|
114
|
+
onEscapeKeyDown(event) {
|
|
115
|
+
ctx.onEscapeKeyDown?.(event)
|
|
116
|
+
if (ctx.closeOnEsc) return
|
|
117
|
+
event.preventDefault()
|
|
118
|
+
},
|
|
119
|
+
onInteractOutside(event) {
|
|
120
|
+
ctx.onInteractOutside?.(event)
|
|
121
|
+
if (event.defaultPrevented) return
|
|
122
|
+
restoreFocus = !(event.detail.focusable || event.detail.contextmenu)
|
|
123
|
+
if (!ctx.closeOnInteractOutside) {
|
|
124
|
+
event.preventDefault()
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
onPointerDownOutside(event) {
|
|
128
|
+
ctx.onPointerDownOutside?.(event)
|
|
129
|
+
},
|
|
130
|
+
onFocusOutside(event) {
|
|
131
|
+
ctx.onFocusOutside?.(event)
|
|
132
|
+
},
|
|
133
|
+
onDismiss() {
|
|
134
|
+
send({ type: "REQUEST_CLOSE", src: "interact-outside", restoreFocus })
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
proxyTabFocus(ctx) {
|
|
139
|
+
if (ctx.modal || !ctx.portalled) return
|
|
140
|
+
const getContentEl = () => dom.getContentEl(ctx)
|
|
141
|
+
return proxyTabFocus(getContentEl, {
|
|
142
|
+
triggerElement: dom.getTriggerEl(ctx),
|
|
143
|
+
defer: true,
|
|
144
|
+
onFocus(el) {
|
|
145
|
+
el.focus({ preventScroll: true })
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
},
|
|
149
|
+
hideContentBelow(ctx) {
|
|
150
|
+
if (!ctx.modal) return
|
|
151
|
+
const getElements = () => [dom.getContentEl(ctx), dom.getTriggerEl(ctx)]
|
|
152
|
+
return ariaHidden(getElements, { defer: true })
|
|
153
|
+
},
|
|
154
|
+
preventScroll(ctx) {
|
|
155
|
+
if (!ctx.modal) return
|
|
156
|
+
return preventBodyScroll(dom.getDoc(ctx))
|
|
157
|
+
},
|
|
158
|
+
trapFocus(ctx) {
|
|
159
|
+
if (!ctx.modal) return
|
|
160
|
+
let trap: FocusTrap | undefined
|
|
161
|
+
nextTick(() => {
|
|
162
|
+
const el = dom.getContentEl(ctx)
|
|
163
|
+
if (!el) return
|
|
164
|
+
trap = createFocusTrap(el, {
|
|
165
|
+
escapeDeactivates: false,
|
|
166
|
+
allowOutsideClick: true,
|
|
167
|
+
preventScroll: true,
|
|
168
|
+
returnFocusOnDeactivate: true,
|
|
169
|
+
document: dom.getDoc(ctx),
|
|
170
|
+
fallbackFocus: el,
|
|
171
|
+
initialFocus: runIfFn(ctx.initialFocusEl),
|
|
172
|
+
})
|
|
173
|
+
try {
|
|
174
|
+
trap.activate()
|
|
175
|
+
} catch {}
|
|
176
|
+
})
|
|
177
|
+
return () => trap?.deactivate()
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
actions: {
|
|
181
|
+
setPositioning(ctx, evt) {
|
|
182
|
+
const anchorEl = dom.getAnchorEl(ctx) ?? dom.getTriggerEl(ctx)
|
|
183
|
+
const getPositionerEl = () => dom.getPositionerEl(ctx)
|
|
184
|
+
getPlacement(anchorEl, getPositionerEl, {
|
|
185
|
+
...ctx.positioning,
|
|
186
|
+
...evt.options,
|
|
187
|
+
defer: true,
|
|
188
|
+
listeners: false,
|
|
189
|
+
})
|
|
190
|
+
},
|
|
191
|
+
checkRenderedElements(ctx) {
|
|
192
|
+
raf(() => {
|
|
193
|
+
Object.assign(ctx.renderedElements, {
|
|
194
|
+
title: !!dom.getTitleEl(ctx),
|
|
195
|
+
description: !!dom.getDescriptionEl(ctx),
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
},
|
|
199
|
+
setInitialFocus(ctx) {
|
|
200
|
+
raf(() => {
|
|
201
|
+
dom.getInitialFocusEl(ctx)?.focus({ preventScroll: true })
|
|
202
|
+
})
|
|
203
|
+
},
|
|
204
|
+
restoreFocusIfNeeded(ctx, evt) {
|
|
205
|
+
if (!evt.restoreFocus) return
|
|
206
|
+
raf(() => {
|
|
207
|
+
dom.getTriggerEl(ctx)?.focus({ preventScroll: true })
|
|
208
|
+
})
|
|
209
|
+
},
|
|
210
|
+
invokeOnOpen(ctx) {
|
|
211
|
+
ctx.onOpen?.()
|
|
212
|
+
},
|
|
213
|
+
invokeOnClose(ctx) {
|
|
214
|
+
ctx.onClose?.()
|
|
215
|
+
},
|
|
216
|
+
toggleVisibility(ctx, _evt, { send }) {
|
|
217
|
+
send({ type: ctx.open ? "OPEN" : "CLOSE", src: "controlled" })
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
)
|
|
222
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { StateMachine as S } from "@zag-js/core"
|
|
2
|
+
import type { DismissableElementHandlers } from "@zag-js/dismissable"
|
|
3
|
+
import type { PositioningOptions, Placement } from "@zag-js/popper"
|
|
4
|
+
import type { CommonProperties, Context, MaybeElement, RequiredBy } from "@zag-js/types"
|
|
5
|
+
|
|
6
|
+
type ElementIds = Partial<{
|
|
7
|
+
anchor: string
|
|
8
|
+
trigger: string
|
|
9
|
+
content: string
|
|
10
|
+
title: string
|
|
11
|
+
description: string
|
|
12
|
+
closeTrigger: string
|
|
13
|
+
positioner: string
|
|
14
|
+
arrow: string
|
|
15
|
+
}>
|
|
16
|
+
|
|
17
|
+
type PublicContext = DismissableElementHandlers &
|
|
18
|
+
CommonProperties & {
|
|
19
|
+
/**
|
|
20
|
+
* The ids of the elements in the popover. Useful for composition.
|
|
21
|
+
*/
|
|
22
|
+
ids?: ElementIds
|
|
23
|
+
/**
|
|
24
|
+
* Whether the popover should be modal. When set to `true`:
|
|
25
|
+
* - interaction with outside elements will be disabled
|
|
26
|
+
* - only popover content will be visible to screen readers
|
|
27
|
+
* - scrolling is blocked
|
|
28
|
+
* - focus is trapped within the popover
|
|
29
|
+
*
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
modal?: boolean
|
|
33
|
+
/**
|
|
34
|
+
* Whether the popover is rendered in a portal
|
|
35
|
+
*/
|
|
36
|
+
portalled?: boolean
|
|
37
|
+
/**
|
|
38
|
+
* Whether to automatically set focus on the first focusable
|
|
39
|
+
* content within the popover when opened.
|
|
40
|
+
*/
|
|
41
|
+
autoFocus?: boolean
|
|
42
|
+
/**
|
|
43
|
+
* The element to focus on when the popover is opened.
|
|
44
|
+
*/
|
|
45
|
+
initialFocusEl?: MaybeElement | (() => MaybeElement)
|
|
46
|
+
/**
|
|
47
|
+
* Whether to close the popover when the user clicks outside of the popover.
|
|
48
|
+
*/
|
|
49
|
+
closeOnInteractOutside?: boolean
|
|
50
|
+
/**
|
|
51
|
+
* Whether to close the popover when the escape key is pressed.
|
|
52
|
+
*/
|
|
53
|
+
closeOnEsc?: boolean
|
|
54
|
+
/**
|
|
55
|
+
* Function invoked when the popover is closed
|
|
56
|
+
*/
|
|
57
|
+
onClose?: VoidFunction
|
|
58
|
+
/**
|
|
59
|
+
* Function invoked when the popover is opened
|
|
60
|
+
*/
|
|
61
|
+
onOpen?: VoidFunction
|
|
62
|
+
/**
|
|
63
|
+
* The user provided options used to position the popover content
|
|
64
|
+
*/
|
|
65
|
+
positioning: PositioningOptions
|
|
66
|
+
/**
|
|
67
|
+
* Whether the popover is open
|
|
68
|
+
*/
|
|
69
|
+
open?: boolean
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type UserDefinedContext = RequiredBy<PublicContext, "id">
|
|
73
|
+
|
|
74
|
+
type ComputedContext = Readonly<{
|
|
75
|
+
/**
|
|
76
|
+
* @computed
|
|
77
|
+
* The computed value of `portalled`
|
|
78
|
+
*/
|
|
79
|
+
currentPortalled: boolean
|
|
80
|
+
}>
|
|
81
|
+
|
|
82
|
+
type PrivateContext = Context<{
|
|
83
|
+
/**
|
|
84
|
+
* @internal
|
|
85
|
+
* The elements that are rendered on mount
|
|
86
|
+
*/
|
|
87
|
+
renderedElements: {
|
|
88
|
+
title: boolean
|
|
89
|
+
description: boolean
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* @internal
|
|
93
|
+
* The computed placement (maybe different from initial placement)
|
|
94
|
+
*/
|
|
95
|
+
currentPlacement?: Placement
|
|
96
|
+
}>
|
|
97
|
+
|
|
98
|
+
export type MachineContext = PublicContext & ComputedContext & PrivateContext
|
|
99
|
+
|
|
100
|
+
export type MachineState = {
|
|
101
|
+
value: "open" | "closed"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type State = S.State<MachineContext, MachineState>
|
|
105
|
+
|
|
106
|
+
export type Send = S.Send<S.AnyEventObject>
|