@tamagui/dialog 1.0.1-beta.43

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.
@@ -0,0 +1,289 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ import { AnimatePresence } from "@tamagui/animate-presence";
4
+ import { useComposedRefs } from "@tamagui/compose-refs";
5
+ import {
6
+ Slot,
7
+ Theme,
8
+ composeEventHandlers,
9
+ isWeb,
10
+ styled,
11
+ useId,
12
+ useThemeName,
13
+ withStaticProperties
14
+ } from "@tamagui/core";
15
+ import { createContext, createContextScope } from "@tamagui/create-context";
16
+ import { Dismissable } from "@tamagui/dismissable";
17
+ import { FocusScope } from "@tamagui/focus-scope";
18
+ import { Portal } from "@tamagui/portal";
19
+ import { ThemeableStack, YStack } from "@tamagui/stacks";
20
+ import { H2, Paragraph } from "@tamagui/text";
21
+ import { useControllableState } from "@tamagui/use-controllable-state";
22
+ import * as React from "react";
23
+ import { RemoveScroll } from "react-remove-scroll";
24
+ const DIALOG_NAME = "Dialog";
25
+ const [createDialogContext, createDialogScope] = createContextScope(DIALOG_NAME);
26
+ const [DialogProvider, useDialogContext] = createDialogContext(DIALOG_NAME);
27
+ const TRIGGER_NAME = "DialogTrigger";
28
+ const DialogTriggerFrame = styled(YStack, {
29
+ name: TRIGGER_NAME
30
+ });
31
+ const DialogTrigger = React.forwardRef((props, forwardedRef) => {
32
+ const { __scopeDialog, ...triggerProps } = props;
33
+ const context = useDialogContext(TRIGGER_NAME, __scopeDialog);
34
+ const composedTriggerRef = useComposedRefs(forwardedRef, context.triggerRef);
35
+ return <DialogTriggerFrame tag="button" aria-haspopup="dialog" aria-expanded={context.open} aria-controls={context.contentId} data-state={getState(context.open)} {...triggerProps} ref={composedTriggerRef} onPress={composeEventHandlers(props.onPress, context.onOpenToggle)} />;
36
+ });
37
+ DialogTrigger.displayName = TRIGGER_NAME;
38
+ const PORTAL_NAME = "DialogPortal";
39
+ const [PortalProvider, usePortalContext] = createDialogContext(PORTAL_NAME, {
40
+ forceMount: void 0
41
+ });
42
+ const DialogPortal = /* @__PURE__ */ __name((props) => {
43
+ const { __scopeDialog, forceMount, children, ...rest } = props;
44
+ const themeName = useThemeName();
45
+ const context = useDialogContext(PORTAL_NAME, __scopeDialog);
46
+ const isShowing = forceMount || context.open;
47
+ const contents = <AnimatePresence>{isShowing ? children : null}</AnimatePresence>;
48
+ if (!context.modal) {
49
+ return contents;
50
+ }
51
+ return <PortalProvider scope={__scopeDialog} forceMount={forceMount}><Portal alignItems="center" justifyContent="center" zIndex={100} pointerEvents={isShowing ? "auto" : "none"} {...isWeb && {
52
+ maxHeight: "100vh"
53
+ }} {...rest}><Theme name={themeName}>{contents}</Theme></Portal></PortalProvider>;
54
+ }, "DialogPortal");
55
+ DialogPortal.displayName = PORTAL_NAME;
56
+ const OVERLAY_NAME = "DialogOverlay";
57
+ const DialogOverlayFrame = styled(ThemeableStack, {
58
+ name: OVERLAY_NAME,
59
+ pointerEvents: "auto",
60
+ backgrounded: true,
61
+ fullscreen: true
62
+ });
63
+ const DialogOverlay = React.forwardRef((props, forwardedRef) => {
64
+ const portalContext = usePortalContext(OVERLAY_NAME, props.__scopeDialog);
65
+ const { forceMount = portalContext.forceMount, ...overlayProps } = props;
66
+ const context = useDialogContext(OVERLAY_NAME, props.__scopeDialog);
67
+ if (!context.modal) {
68
+ return null;
69
+ }
70
+ return <DialogOverlayImpl {...overlayProps} ref={forwardedRef} />;
71
+ });
72
+ DialogOverlay.displayName = OVERLAY_NAME;
73
+ const DialogOverlayImpl = React.forwardRef((props, forwardedRef) => {
74
+ const { __scopeDialog, ...overlayProps } = props;
75
+ const context = useDialogContext(OVERLAY_NAME, __scopeDialog);
76
+ const content = <DialogOverlayFrame data-state={getState(context.open)} pointerEvents="auto" {...overlayProps} ref={forwardedRef} />;
77
+ if (!isWeb) {
78
+ return content;
79
+ }
80
+ return <RemoveScroll as={Slot} allowPinchZoom={context.allowPinchZoom} shards={[context.contentRef]}>{content}</RemoveScroll>;
81
+ });
82
+ const CONTENT_NAME = "DialogContent";
83
+ const DialogContentFrame = styled(ThemeableStack, {
84
+ name: CONTENT_NAME,
85
+ tag: "dialog",
86
+ pointerEvents: "auto",
87
+ position: "relative",
88
+ backgrounded: true,
89
+ padded: true,
90
+ radiused: true,
91
+ elevate: true,
92
+ variants: {
93
+ size: {
94
+ "...size": (val, extras) => {
95
+ return {};
96
+ }
97
+ }
98
+ },
99
+ defaultVariants: {
100
+ size: "$4"
101
+ }
102
+ });
103
+ const DialogContent = React.forwardRef((props, forwardedRef) => {
104
+ const portalContext = usePortalContext(CONTENT_NAME, props.__scopeDialog);
105
+ const { forceMount = portalContext.forceMount, ...contentProps } = props;
106
+ const context = useDialogContext(CONTENT_NAME, props.__scopeDialog);
107
+ return <>{context.modal ? <DialogContentModal {...contentProps} ref={forwardedRef} /> : <DialogContentNonModal {...contentProps} ref={forwardedRef} />}</>;
108
+ });
109
+ DialogContent.displayName = CONTENT_NAME;
110
+ const DialogContentModal = React.forwardRef((props, forwardedRef) => {
111
+ const context = useDialogContext(CONTENT_NAME, props.__scopeDialog);
112
+ const contentRef = React.useRef(null);
113
+ const composedRefs = useComposedRefs(forwardedRef, context.contentRef, contentRef);
114
+ return <DialogContentImpl {...props} ref={composedRefs} trapFocus={context.open} disableOutsidePointerEvents onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => {
115
+ var _a;
116
+ event.preventDefault();
117
+ (_a = context.triggerRef.current) == null ? void 0 : _a.focus();
118
+ })} onPointerDownOutside={composeEventHandlers(props.onPointerDownOutside, (event) => {
119
+ const originalEvent = event["detail"].originalEvent;
120
+ const ctrlLeftClick = originalEvent.button === 0 && originalEvent.ctrlKey === true;
121
+ const isRightClick = originalEvent.button === 2 || ctrlLeftClick;
122
+ if (isRightClick)
123
+ event.preventDefault();
124
+ })} onFocusOutside={composeEventHandlers(props.onFocusOutside, (event) => event.preventDefault())} />;
125
+ });
126
+ const DialogContentNonModal = React.forwardRef((props, forwardedRef) => {
127
+ const context = useDialogContext(CONTENT_NAME, props.__scopeDialog);
128
+ const hasInteractedOutsideRef = React.useRef(false);
129
+ return <DialogContentImpl {...props} ref={forwardedRef} trapFocus={false} disableOutsidePointerEvents={false} onCloseAutoFocus={(event) => {
130
+ var _a, _b;
131
+ (_a = props.onCloseAutoFocus) == null ? void 0 : _a.call(props, event);
132
+ if (!event.defaultPrevented) {
133
+ if (!hasInteractedOutsideRef.current)
134
+ (_b = context.triggerRef.current) == null ? void 0 : _b.focus();
135
+ event.preventDefault();
136
+ }
137
+ hasInteractedOutsideRef.current = false;
138
+ }} onInteractOutside={(event) => {
139
+ var _a;
140
+ (_a = props.onInteractOutside) == null ? void 0 : _a.call(props, event);
141
+ if (!event.defaultPrevented)
142
+ hasInteractedOutsideRef.current = true;
143
+ const target = event.target;
144
+ const trigger = context.triggerRef.current;
145
+ if (!(trigger instanceof HTMLElement))
146
+ return;
147
+ const targetIsTrigger = trigger.contains(target);
148
+ if (targetIsTrigger)
149
+ event.preventDefault();
150
+ }} />;
151
+ });
152
+ const DialogContentImpl = React.forwardRef((props, forwardedRef) => {
153
+ const {
154
+ __scopeDialog,
155
+ trapFocus,
156
+ onOpenAutoFocus,
157
+ onCloseAutoFocus,
158
+ disableOutsidePointerEvents,
159
+ onEscapeKeyDown,
160
+ onPointerDownOutside,
161
+ onFocusOutside,
162
+ onInteractOutside,
163
+ ...contentProps
164
+ } = props;
165
+ const context = useDialogContext(CONTENT_NAME, __scopeDialog);
166
+ const contentRef = React.useRef(null);
167
+ const composedRefs = useComposedRefs(forwardedRef, contentRef);
168
+ return <>
169
+ <FocusScope loop trapped={trapFocus} onMountAutoFocus={onOpenAutoFocus} onUnmountAutoFocus={onCloseAutoFocus}><Dismissable disableOutsidePointerEvents={disableOutsidePointerEvents} onEscapeKeyDown={onEscapeKeyDown} onPointerDownOutside={onPointerDownOutside} onFocusOutside={onFocusOutside} onInteractOutside={onInteractOutside} ref={composedRefs} onDismiss={() => context.onOpenChange(false)}><DialogContentFrame id={context.contentId} aria-describedby={context.descriptionId} aria-labelledby={context.titleId} data-state={getState(context.open)} {...contentProps} /></Dismissable></FocusScope>
170
+ {process.env.NODE_ENV !== "production" && <>
171
+ <TitleWarning titleId={context.titleId} />
172
+ <DescriptionWarning contentRef={contentRef} descriptionId={context.descriptionId} />
173
+ </>}
174
+ </>;
175
+ });
176
+ const TITLE_NAME = "DialogTitle";
177
+ const DialogTitleFrame = styled(H2, {
178
+ name: TITLE_NAME
179
+ });
180
+ const DialogTitle = React.forwardRef((props, forwardedRef) => {
181
+ const { __scopeDialog, ...titleProps } = props;
182
+ const context = useDialogContext(TITLE_NAME, __scopeDialog);
183
+ return <DialogTitleFrame id={context.titleId} {...titleProps} ref={forwardedRef} />;
184
+ });
185
+ DialogTitle.displayName = TITLE_NAME;
186
+ const DialogDescriptionFrame = styled(Paragraph, {
187
+ name: "DialogDescription"
188
+ });
189
+ const DESCRIPTION_NAME = "DialogDescription";
190
+ const DialogDescription = React.forwardRef((props, forwardedRef) => {
191
+ const { __scopeDialog, ...descriptionProps } = props;
192
+ const context = useDialogContext(DESCRIPTION_NAME, __scopeDialog);
193
+ return <DialogDescriptionFrame id={context.descriptionId} {...descriptionProps} ref={forwardedRef} />;
194
+ });
195
+ DialogDescription.displayName = DESCRIPTION_NAME;
196
+ const CLOSE_NAME = "DialogClose";
197
+ const DialogClose = React.forwardRef((props, forwardedRef) => {
198
+ const { __scopeDialog, ...closeProps } = props;
199
+ const context = useDialogContext(CLOSE_NAME, __scopeDialog);
200
+ return <YStack tag="button" {...closeProps} ref={forwardedRef} onPress={composeEventHandlers(props.onPress, () => context.onOpenChange(false))} />;
201
+ });
202
+ DialogClose.displayName = CLOSE_NAME;
203
+ function getState(open) {
204
+ return open ? "open" : "closed";
205
+ }
206
+ __name(getState, "getState");
207
+ const TITLE_WARNING_NAME = "DialogTitleWarning";
208
+ const [WarningProvider, useWarningContext] = createContext(TITLE_WARNING_NAME, {
209
+ contentName: CONTENT_NAME,
210
+ titleName: TITLE_NAME,
211
+ docsSlug: "dialog"
212
+ });
213
+ const TitleWarning = /* @__PURE__ */ __name(({ titleId }) => {
214
+ const titleWarningContext = useWarningContext(TITLE_WARNING_NAME);
215
+ const MESSAGE = `\`${titleWarningContext.contentName}\` requires a \`${titleWarningContext.titleName}\` for the component to be accessible for screen reader users.
216
+
217
+ If you want to hide the \`${titleWarningContext.titleName}\`, you can wrap it with our VisuallyHidden component.
218
+
219
+ For more information, see https://radix-ui.com/primitives/docs/components/${titleWarningContext.docsSlug}`;
220
+ React.useEffect(() => {
221
+ if (!isWeb)
222
+ return;
223
+ if (titleId) {
224
+ const hasTitle = document.getElementById(titleId);
225
+ if (!hasTitle)
226
+ throw new Error(MESSAGE);
227
+ }
228
+ }, [MESSAGE, titleId]);
229
+ return null;
230
+ }, "TitleWarning");
231
+ const DESCRIPTION_WARNING_NAME = "DialogDescriptionWarning";
232
+ const DescriptionWarning = /* @__PURE__ */ __name(({ contentRef, descriptionId }) => {
233
+ const descriptionWarningContext = useWarningContext(DESCRIPTION_WARNING_NAME);
234
+ const MESSAGE = `Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${descriptionWarningContext.contentName}}.`;
235
+ React.useEffect(() => {
236
+ if (!isWeb)
237
+ return;
238
+ const contentNode = contentRef.current;
239
+ if (!(contentNode instanceof HTMLElement)) {
240
+ return;
241
+ }
242
+ const describedById = contentNode.getAttribute("aria-describedby");
243
+ if (descriptionId && describedById) {
244
+ const hasDescription = document.getElementById(descriptionId);
245
+ if (!hasDescription)
246
+ console.warn(MESSAGE);
247
+ }
248
+ }, [MESSAGE, contentRef, descriptionId]);
249
+ return null;
250
+ }, "DescriptionWarning");
251
+ const Dialog = withStaticProperties(/* @__PURE__ */ __name(function Dialog2(props) {
252
+ const {
253
+ __scopeDialog,
254
+ children,
255
+ open: openProp,
256
+ defaultOpen = false,
257
+ onOpenChange,
258
+ modal = true,
259
+ allowPinchZoom
260
+ } = props;
261
+ const triggerRef = React.useRef(null);
262
+ const contentRef = React.useRef(null);
263
+ const [open = false, setOpen] = useControllableState({
264
+ prop: openProp,
265
+ defaultProp: defaultOpen,
266
+ onChange: onOpenChange
267
+ });
268
+ return <DialogProvider scope={__scopeDialog} triggerRef={triggerRef} contentRef={contentRef} contentId={useId() || ""} titleId={useId() || ""} descriptionId={useId() || ""} open={open} onOpenChange={setOpen} onOpenToggle={React.useCallback(() => setOpen((prevOpen) => !prevOpen), [setOpen])} modal={modal} allowPinchZoom={allowPinchZoom || false}>{children}</DialogProvider>;
269
+ }, "Dialog"), {
270
+ Trigger: DialogTrigger,
271
+ Portal: DialogPortal,
272
+ Overlay: DialogOverlay,
273
+ Content: DialogContent,
274
+ Title: DialogTitle,
275
+ Description: DialogDescription,
276
+ Close: DialogClose
277
+ });
278
+ export {
279
+ Dialog,
280
+ DialogClose,
281
+ DialogContent,
282
+ DialogDescription,
283
+ DialogOverlay,
284
+ DialogPortal,
285
+ DialogTitle,
286
+ DialogTrigger,
287
+ WarningProvider,
288
+ createDialogScope
289
+ };
@@ -0,0 +1 @@
1
+ export * from "./Dialog";
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@tamagui/dialog",
3
+ "version": "1.0.1-beta.43",
4
+ "sideEffects": true,
5
+ "source": "src/index.ts",
6
+ "types": "./types/index.d.ts",
7
+ "main": "dist/cjs",
8
+ "module": "dist/esm",
9
+ "module:jsx": "dist/jsx",
10
+ "files": [
11
+ "types",
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "build": "tamagui-build",
16
+ "watch": "tamagui-build --watch",
17
+ "clean": "tamagui-build clean",
18
+ "clean:build": "tamagui-build clean:build"
19
+ },
20
+ "dependencies": {
21
+ "@tamagui/animate-presence": "^1.0.1-beta.43",
22
+ "@tamagui/compose-refs": "^1.0.1-beta.43",
23
+ "@tamagui/core": "^1.0.1-beta.43",
24
+ "@tamagui/create-context": "^1.0.1-beta.43",
25
+ "@tamagui/dismissable": "^1.0.1-beta.43",
26
+ "@tamagui/focus-scope": "^1.0.1-beta.43",
27
+ "@tamagui/polyfill-dev": "^1.0.1-beta.43",
28
+ "@tamagui/popper": "^1.0.1-beta.43",
29
+ "@tamagui/portal": "^1.0.1-beta.43",
30
+ "@tamagui/stacks": "^1.0.1-beta.43",
31
+ "@tamagui/text": "^1.0.1-beta.43",
32
+ "@tamagui/use-controllable-state": "^1.0.1-beta.43",
33
+ "react-remove-scroll": "^2.5.3"
34
+ },
35
+ "peerDependencies": {
36
+ "react": "*",
37
+ "react-dom": "*",
38
+ "react-native": "*"
39
+ },
40
+ "devDependencies": {
41
+ "@tamagui/build": "^1.0.1-beta.43",
42
+ "@types/react-dom": "^18.0.3",
43
+ "@types/react-native": "^0.67.3",
44
+ "react": "*",
45
+ "react-dom": "*",
46
+ "react-native": "*"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }