@jobber/components 6.98.2 → 6.100.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/dist/Menu-es.js CHANGED
@@ -1,36 +1,126 @@
1
- import React__default, { useState, useId, useMemo, useRef } from 'react';
1
+ import React__default, { useEffect, createContext, useState, useRef, useId, useMemo, useContext } from 'react';
2
2
  import classnames from 'classnames';
3
- import { AnimatePresence, motion } from 'framer-motion';
4
- import { useWindowDimensions, useRefocusOnActivator, useFocusTrap, useIsMounted } from '@jobber/hooks';
5
- import { o as offset, f as flip, s as size, u as useFloating, a as useDismiss, b as useInteractions, F as FloatingPortal, c as autoUpdate } from './floating-ui.react-es.js';
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import { useWindowDimensions, useRefocusOnActivator, useIsMounted } from '@jobber/hooks';
5
+ import { o as offset, f as flip, c as size, u as useFloating, d as useDismiss, e as useListNavigation, g as useInteractions, F as FloatingPortal, b as autoUpdate } from './floating-ui.react-es.js';
6
+ import { $ as $df56164dff5785e2$export$4338b53315abf666, a as $f6c31cce2adf654f$export$45712eceda6fad21, b as $f645667febf57a63$export$4c014de7c8940b4c, c as $431fbd86ca7dc216$export$f21a1ffae260145a, d as $b4b717babfbb907b$export$4c063cf1350e6fed, e as $3ef42575df84b30b$export$9d1611c77c2fe928, f as $5dc95899b306f630$export$c9058316764c140e, g as $3674c52c6b3c5bce$export$d9b273488cd8ce6f, h as $3674c52c6b3c5bce$export$2ce376c2cc3355c8, i as $3674c52c6b3c5bce$export$27d2ad3c5815583e, j as $07b14b47974efb58$export$5b6b19405a83ff9d, k as $431f98aba6844401$export$1ff3c3f08ae963c0, l as $3674c52c6b3c5bce$export$4b1545b4f2016d26, m as $72a5793c14baf454$export$8b251419efc915eb } from './Tree-es.js';
7
+ import 'react-dom';
6
8
  import { B as Button } from './Button-es.js';
7
9
  import { T as Typography } from './Typography-es.js';
8
10
  import { I as Icon } from './Icon-es.js';
9
11
  import { f as formFieldFocusAttribute } from './useFormFieldFocus-es.js';
10
12
  import { c as calculateMaxHeight } from './maxHeight-es.js';
11
13
 
12
- var styles = {"wrapper":"fpi0W91w2ag-","floatingContainer":"ba-Kj6mvQUw-","menu":"-DayvgvIcVo-","section":"X0W9r8kjA6Q-","sectionHeader":"Bq7pLWj3jm4-","action":"M1BgN-oCmKw-","destructive":"U9ihZZavz9w-","overlay":"_7we5hh6kYs8-","fullWidth":"_5OJ7B6mFcwk-","screenReaderOnly":"Bzvkz60bwWE-","spinning":"_0-VzuzHdi8o-"};
14
+ /*
15
+ * Copyright 2020 Adobe. All rights reserved.
16
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
17
+ * you may not use this file except in compliance with the License. You may obtain a copy
18
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
19
+ *
20
+ * Unless required by applicable law or agreed to in writing, software distributed under
21
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
22
+ * OF ANY KIND, either express or implied. See the License for the specific language
23
+ * governing permissions and limitations under the License.
24
+ */
25
+
26
+
27
+
28
+ const $3b117e43dc0ca95d$export$27c701ed9e449e99 = /*#__PURE__*/ (React__default).forwardRef(({ children: children, ...props }, ref)=>{
29
+ ref = ($df56164dff5785e2$export$4338b53315abf666)(ref);
30
+ let { pressProps: pressProps } = ($f6c31cce2adf654f$export$45712eceda6fad21)({
31
+ ...props,
32
+ ref: ref
33
+ });
34
+ let { focusableProps: focusableProps } = ($f645667febf57a63$export$4c014de7c8940b4c)(props, ref);
35
+ let child = (React__default).Children.only(children);
36
+ (useEffect)(()=>{
37
+ if (process.env.NODE_ENV === 'production') return;
38
+ let el = ref.current;
39
+ if (!el || !(el instanceof ($431fbd86ca7dc216$export$f21a1ffae260145a)(el).Element)) {
40
+ console.error('<Pressable> child must forward its ref to a DOM element.');
41
+ return;
42
+ }
43
+ if (!props.isDisabled && !($b4b717babfbb907b$export$4c063cf1350e6fed)(el)) {
44
+ console.warn('<Pressable> child must be focusable. Please ensure the tabIndex prop is passed through.');
45
+ return;
46
+ }
47
+ if (el.localName !== 'button' && el.localName !== 'input' && el.localName !== 'select' && el.localName !== 'textarea' && el.localName !== 'a' && el.localName !== 'area' && el.localName !== 'summary') {
48
+ let role = el.getAttribute('role');
49
+ if (!role) console.warn('<Pressable> child must have an interactive ARIA role.');
50
+ else if (// https://w3c.github.io/aria/#widget_roles
51
+ role !== 'application' && role !== 'button' && role !== 'checkbox' && role !== 'combobox' && role !== 'gridcell' && role !== 'link' && role !== 'menuitem' && role !== 'menuitemcheckbox' && role !== 'menuitemradio' && role !== 'option' && role !== 'radio' && role !== 'searchbox' && role !== 'separator' && role !== 'slider' && role !== 'spinbutton' && role !== 'switch' && role !== 'tab' && role !== 'textbox' && role !== 'treeitem') console.warn(`<Pressable> child must have an interactive ARIA role. Got "${role}".`);
52
+ }
53
+ }, [
54
+ ref,
55
+ props.isDisabled
56
+ ]);
57
+ // @ts-ignore
58
+ let childRef = parseInt((React__default).version, 10) < 19 ? child.ref : child.props.ref;
59
+ return /*#__PURE__*/ (React__default).cloneElement(child, {
60
+ ...($3ef42575df84b30b$export$9d1611c77c2fe928)(pressProps, focusableProps, child.props),
61
+ // @ts-ignore
62
+ ref: ($5dc95899b306f630$export$c9058316764c140e)(childRef, ref)
63
+ });
64
+ });
65
+
66
+ var styles = {"wrapper":"fpi0W91w2ag-","floatingContainer":"ba-Kj6mvQUw-","menu":"-DayvgvIcVo-","legacySection":"VsRRunpS6Yo-","separator":"jnggqK3YTIU-","triggerWrapper":"oRQQVVq-yxA-","ariaMenu":"QInUBKqkrl0-","ariaItem":"bWR8m7-LKg4-","ariaSection":"PH5vvtLgvXI-","ariaSectionHeader":"iJjIifpa9bk-","sectionHeader":"Bq7pLWj3jm4-","legacyAction":"P4Sdaq0-lZs-","action":"M1BgN-oCmKw-","destructive":"U9ihZZavz9w-","overlay":"_7we5hh6kYs8-","fullWidth":"_5OJ7B6mFcwk-","screenReaderOnly":"Bzvkz60bwWE-","spinning":"_0-VzuzHdi8o-"};
13
67
 
14
68
  const SMALL_SCREEN_BREAKPOINT = 490;
15
69
  const MENU_OFFSET = 6;
16
70
  const MENU_MAX_HEIGHT_PERCENTAGE = 72;
71
+ const Y_TRANSLATION_DESKTOP = 10;
72
+ const Y_TRANSLATION_MOBILE = 150;
73
+ const MENU_ANIMATION_DURATION = 0.25;
74
+ const OVERLAY_ANIMATION_DURATION = 0.15;
75
+ const MENU_ANIMATION_CONFIG = {
76
+ duration: MENU_ANIMATION_DURATION,
77
+ type: "tween",
78
+ };
79
+ const OVERLAY_ANIMATION_CONFIG = {
80
+ duration: OVERLAY_ANIMATION_DURATION,
81
+ type: "tween",
82
+ };
83
+
84
+ const composeOverlayVariation = {
85
+ hidden: { opacity: 0 },
86
+ visible: { opacity: 1 },
87
+ };
17
88
  const variation = {
18
89
  overlayStartStop: { opacity: 0 },
19
90
  startOrStop: (placement) => {
20
- let y = 10;
91
+ let y = Y_TRANSLATION_DESKTOP;
21
92
  if (placement === null || placement === void 0 ? void 0 : placement.includes("bottom"))
22
93
  y *= -1;
23
- if (window.innerWidth < SMALL_SCREEN_BREAKPOINT)
24
- y = 150;
94
+ if (isMobileDevice())
95
+ y = Y_TRANSLATION_MOBILE;
25
96
  return { opacity: 0, y };
26
97
  },
27
98
  done: { opacity: 1, y: 0 },
28
99
  };
100
+ function isMobileDevice() {
101
+ if (typeof window === "undefined") {
102
+ return false;
103
+ }
104
+ return window.innerWidth <= SMALL_SCREEN_BREAKPOINT;
105
+ }
106
+ function isLegacy(props) {
107
+ return "items" in props;
108
+ }
109
+ const MotionMenu = motion.create($3674c52c6b3c5bce$export$d9b273488cd8ce6f);
110
+ // Single implementation
111
+ function Menu(props) {
112
+ if (isLegacy(props)) {
113
+ return React__default.createElement(MenuLegacy, Object.assign({}, props));
114
+ }
115
+ return (React__default.createElement(MenuComposable, { onOpenChange: props.onOpenChange, open: props.open, defaultOpen: props.defaultOpen }, props.children));
116
+ }
29
117
  // eslint-disable-next-line max-statements
30
- function Menu({ activator, items, UNSAFE_className, UNSAFE_style, }) {
118
+ function MenuLegacy({ activator, items, UNSAFE_className, UNSAFE_style, }) {
31
119
  var _a;
32
120
  const [visible, setVisible] = useState(false);
33
121
  const [referenceElement, setReferenceElement] = useState(null);
122
+ const [activeIndex, setActiveIndex] = useState(null);
123
+ const listRef = useRef([]);
34
124
  const { width } = useWindowDimensions();
35
125
  const buttonID = useId();
36
126
  const menuID = useId();
@@ -38,9 +128,8 @@ function Menu({ activator, items, UNSAFE_className, UNSAFE_style, }) {
38
128
  const wrapperClasses = classnames(styles.wrapper, {
39
129
  [styles.fullWidth]: fullWidth,
40
130
  });
41
- // useRefocusOnActivator must come before useFocusTrap for them both to work
131
+ // Ensure focus returns to the activator when closed
42
132
  useRefocusOnActivator(visible);
43
- const menuRef = useFocusTrap(visible);
44
133
  const isLargeScreen = width >= SMALL_SCREEN_BREAKPOINT;
45
134
  const middleware = useMemo(() => {
46
135
  if (isLargeScreen) {
@@ -69,7 +158,9 @@ function Menu({ activator, items, UNSAFE_className, UNSAFE_style, }) {
69
158
  }, [isLargeScreen]);
70
159
  const { refs, floatingStyles, context } = useFloating({
71
160
  open: visible,
72
- onOpenChange: setVisible,
161
+ onOpenChange: (isOpen) => {
162
+ setVisible(isOpen);
163
+ },
73
164
  placement: "bottom-start",
74
165
  strategy: "fixed",
75
166
  middleware,
@@ -79,7 +170,13 @@ function Menu({ activator, items, UNSAFE_className, UNSAFE_style, }) {
79
170
  whileElementsMounted: autoUpdate,
80
171
  });
81
172
  const dismiss = useDismiss(context);
82
- const { getFloatingProps } = useInteractions([dismiss]);
173
+ const listNavigation = useListNavigation(context, {
174
+ listRef,
175
+ activeIndex,
176
+ onNavigate: setActiveIndex,
177
+ loop: true,
178
+ });
179
+ const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([dismiss, listNavigation]);
83
180
  const positionAttributes = isLargeScreen
84
181
  ? {
85
182
  style: floatingStyles,
@@ -88,8 +185,17 @@ function Menu({ activator, items, UNSAFE_className, UNSAFE_style, }) {
88
185
  if (!activator) {
89
186
  activator = (React__default.createElement(Button, { fullWidth: true, label: "More Actions", icon: "more", type: "secondary" }));
90
187
  }
188
+ let itemIndexCounter = 0;
189
+ const renderedSections = items === null || items === void 0 ? void 0 : items.map((item, key) => (React__default.createElement("div", { key: key, className: styles.legacySection },
190
+ item.header && (React__default.createElement(SectionHeader, { text: item.header, UNSAFE_style: UNSAFE_style === null || UNSAFE_style === void 0 ? void 0 : UNSAFE_style.header, UNSAFE_className: UNSAFE_className === null || UNSAFE_className === void 0 ? void 0 : UNSAFE_className.header })),
191
+ item.actions.map(action => {
192
+ const currentIndex = itemIndexCounter++;
193
+ return (React__default.createElement(Action, Object.assign({ UNSAFE_style: UNSAFE_style === null || UNSAFE_style === void 0 ? void 0 : UNSAFE_style.action, UNSAFE_className: UNSAFE_className === null || UNSAFE_className === void 0 ? void 0 : UNSAFE_className.action, sectionLabel: item.header, key: action.label, tabIndex: activeIndex === currentIndex ? 0 : -1, setItemNode: node => {
194
+ listRef.current[currentIndex] = node;
195
+ }, getItemProps: getItemProps }, action)));
196
+ }))));
91
197
  return (React__default.createElement("div", { className: wrapperClasses, onClick: handleParentClick },
92
- React__default.createElement("div", { ref: setReferenceElement }, React__default.cloneElement(activator, {
198
+ React__default.createElement("div", Object.assign({ ref: setReferenceElement }, getReferenceProps()), React__default.cloneElement(activator, {
93
199
  onClick: toggle(activator.props.onClick),
94
200
  id: buttonID,
95
201
  ariaControls: menuID,
@@ -98,16 +204,14 @@ function Menu({ activator, items, UNSAFE_className, UNSAFE_style, }) {
98
204
  })),
99
205
  React__default.createElement(MenuPortal, null,
100
206
  React__default.createElement(AnimatePresence, null, visible && (React__default.createElement(React__default.Fragment, null,
101
- React__default.createElement(motion.div, { className: styles.overlay, onClick: toggle(), variants: variation, initial: "overlayStartStop", animate: "done", exit: "overlayStartStop", transition: {
102
- type: "tween",
103
- duration: 0.15,
104
- } }),
105
- React__default.createElement("div", Object.assign({ ref: refs.setFloating, className: styles.floatingContainer }, getFloatingProps(), positionAttributes, formFieldFocusAttribute), items.length > 0 && (React__default.createElement(motion.div, { className: classnames(styles.menu, UNSAFE_className === null || UNSAFE_className === void 0 ? void 0 : UNSAFE_className.menu), role: "menu", "data-elevation": "elevated", "aria-labelledby": buttonID, id: menuID, onClick: hide, variants: variation, initial: "startOrStop", animate: "done", exit: "startOrStop", custom: context === null || context === void 0 ? void 0 : context.placement, ref: menuRef, transition: {
106
- type: "tween",
107
- duration: 0.25,
108
- }, style: UNSAFE_style === null || UNSAFE_style === void 0 ? void 0 : UNSAFE_style.menu }, items.map((item, key) => (React__default.createElement("div", { key: key, className: styles.section },
109
- item.header && (React__default.createElement(SectionHeader, { text: item.header, UNSAFE_style: UNSAFE_style === null || UNSAFE_style === void 0 ? void 0 : UNSAFE_style.header, UNSAFE_className: UNSAFE_className === null || UNSAFE_className === void 0 ? void 0 : UNSAFE_className.header })),
110
- item.actions.map(action => (React__default.createElement(Action, Object.assign({ UNSAFE_style: UNSAFE_style === null || UNSAFE_style === void 0 ? void 0 : UNSAFE_style.action, UNSAFE_className: UNSAFE_className === null || UNSAFE_className === void 0 ? void 0 : UNSAFE_className.action, sectionLabel: item.header, key: action.label }, action))))))))))))))));
207
+ React__default.createElement(motion.div, { className: styles.overlay, onClick: toggle(), variants: variation, initial: "overlayStartStop", animate: "done", exit: "overlayStartStop", transition: Object.assign({}, OVERLAY_ANIMATION_CONFIG) }),
208
+ React__default.createElement("div", Object.assign({ ref: refs.setFloating, className: styles.floatingContainer }, getFloatingProps({
209
+ onKeyDown: event => {
210
+ if (event.key === "Tab") {
211
+ event.preventDefault();
212
+ }
213
+ },
214
+ }), positionAttributes, formFieldFocusAttribute), items.length > 0 && (React__default.createElement(motion.div, { className: classnames(styles.menu, UNSAFE_className === null || UNSAFE_className === void 0 ? void 0 : UNSAFE_className.menu), role: "menu", "data-elevation": "elevated", "aria-labelledby": buttonID, id: menuID, onClick: hide, variants: variation, initial: "startOrStop", animate: "done", exit: "startOrStop", custom: context === null || context === void 0 ? void 0 : context.placement, transition: Object.assign({}, MENU_ANIMATION_CONFIG), style: UNSAFE_style === null || UNSAFE_style === void 0 ? void 0 : UNSAFE_style.menu }, renderedSections)))))))));
111
215
  function toggle(callbackPassthrough) {
112
216
  return (event) => {
113
217
  setVisible(!visible);
@@ -116,6 +220,7 @@ function Menu({ activator, items, UNSAFE_className, UNSAFE_style, }) {
116
220
  }
117
221
  function hide() {
118
222
  setVisible(false);
223
+ setActiveIndex(null);
119
224
  }
120
225
  function handleParentClick(event) {
121
226
  // Since the menu is being rendered within the same parent as the activator,
@@ -127,20 +232,32 @@ function Menu({ activator, items, UNSAFE_className, UNSAFE_style, }) {
127
232
  }
128
233
  function SectionHeader({ text, UNSAFE_style, UNSAFE_className, }) {
129
234
  return (React__default.createElement("div", { className: classnames(styles.sectionHeader, UNSAFE_className), "aria-hidden": true, style: UNSAFE_style },
130
- React__default.createElement(Typography, { element: "h6", size: "base", textColor: "textSecondary", fontWeight: "regular", textCase: "none" }, text)));
235
+ React__default.createElement(DefaultHeaderContent, null, text)));
131
236
  }
132
- function Action({ label, sectionLabel, icon, iconColor, destructive, UNSAFE_style, UNSAFE_className, onClick, }) {
133
- const actionButtonRef = useRef();
134
- const buttonClasses = classnames(styles.action, {
237
+ function Action(props) {
238
+ const { label, sectionLabel, icon, iconColor, destructive, UNSAFE_style, UNSAFE_className, onClick, tabIndex, setItemNode, getItemProps, } = props;
239
+ const buttonClasses = classnames(styles.action, styles.legacyAction, {
135
240
  [styles.destructive]: destructive,
136
241
  });
137
- return (React__default.createElement("button", { role: "menuitem", type: "button", className: classnames(buttonClasses, UNSAFE_className), key: label, onClick: onClick, ref: actionButtonRef, style: UNSAFE_style },
242
+ return (React__default.createElement("button", Object.assign({ className: classnames(buttonClasses, UNSAFE_className), style: UNSAFE_style, key: label, type: "button" }, getItemProps({
243
+ ref: setItemNode,
244
+ role: "menuitem",
245
+ onClick,
246
+ tabIndex,
247
+ })),
248
+ React__default.createElement(DefaultItemContent, { label: label, icon: icon, iconColor: iconColor, destructive: destructive, sectionLabel: sectionLabel })));
249
+ }
250
+ function DefaultItemContent({ label, icon, iconColor, destructive, sectionLabel, }) {
251
+ return (React__default.createElement(React__default.Fragment, null,
138
252
  icon && (React__default.createElement("div", null,
139
253
  React__default.createElement(Icon, { color: destructive ? "destructive" : iconColor, name: icon }))),
140
- React__default.createElement(Typography, { element: "span", fontWeight: "semiBold", textColor: "text" },
254
+ React__default.createElement(Typography, { element: "span", fontWeight: "semiBold", textColor: destructive ? "destructive" : "text" },
141
255
  sectionLabel && (React__default.createElement("span", { className: styles.screenReaderOnly }, sectionLabel)),
142
256
  label)));
143
257
  }
258
+ function DefaultHeaderContent({ children, }) {
259
+ return (React__default.createElement(Typography, { element: "h6", size: "base", textColor: "textSecondary", fontWeight: "regular", textCase: "none" }, children));
260
+ }
144
261
  function MenuPortal({ children }) {
145
262
  const mounted = useIsMounted();
146
263
  if (!(mounted === null || mounted === void 0 ? void 0 : mounted.current)) {
@@ -148,5 +265,122 @@ function MenuPortal({ children }) {
148
265
  }
149
266
  return React__default.createElement(FloatingPortal, null, children);
150
267
  }
268
+ const MenuAnimationContext = createContext(null);
269
+ function useMenuAnimation() {
270
+ const ctx = useContext(MenuAnimationContext);
271
+ if (!ctx) {
272
+ throw new Error("MenuAnimationContext used outside provider");
273
+ }
274
+ return ctx;
275
+ }
276
+ function MenuComposable({ children, onOpenChange, open, defaultOpen, }) {
277
+ const isInitiallyOpen = Boolean(open !== null && open !== void 0 ? open : defaultOpen);
278
+ const [animation, setAnimation] = useState(isInitiallyOpen ? "visible" : "unmounted");
279
+ const derivedAnimation = getDerivedAnimation(open, animation);
280
+ return (React__default.createElement(MenuAnimationContext.Provider, { value: { state: derivedAnimation, setState: setAnimation } },
281
+ React__default.createElement($3674c52c6b3c5bce$export$27d2ad3c5815583e, { isOpen: open, defaultOpen: defaultOpen, onOpenChange: isOpen => {
282
+ setAnimation(isOpen ? "visible" : "hidden");
283
+ onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(isOpen);
284
+ } }, children)));
285
+ }
286
+ function getDerivedAnimation(open, animation) {
287
+ const isControlled = open !== undefined;
288
+ if (!isControlled)
289
+ return animation;
290
+ if (open)
291
+ return "visible";
292
+ // When controlled and closing, allow local state to progress to "unmounted"
293
+ // so the Popover can be removed from the DOM once exit completes.
294
+ return animation === "unmounted" ? "unmounted" : "hidden";
295
+ }
296
+ const MenuTriggerComposable = React__default.forwardRef(function MenuTriggerComposable({ ariaLabel, children }, ref) {
297
+ return (React__default.createElement($3b117e43dc0ca95d$export$27c701ed9e449e99, { "aria-label": ariaLabel },
298
+ React__default.createElement("div", { role: "button", className: styles.triggerWrapper, ref: ref }, children)));
299
+ });
300
+ function MenuContentComposable({ children, UNSAFE_style, UNSAFE_className, }) {
301
+ const { state: animation, setState } = useMenuAnimation();
302
+ const isMobile = isMobileDevice();
303
+ return (React__default.createElement(React__default.Fragment, null,
304
+ React__default.createElement($07b14b47974efb58$export$5b6b19405a83ff9d, { isExiting: animation === "hidden", placement: "bottom start", offset: MENU_OFFSET }, ({ placement }) => {
305
+ const directionModifier = (placement === null || placement === void 0 ? void 0 : placement.includes("bottom")) ? -1 : 1;
306
+ const variants = isMobile
307
+ ? {
308
+ hidden: { opacity: 0, y: Y_TRANSLATION_MOBILE },
309
+ visible: { opacity: 1, y: 0 },
310
+ }
311
+ : {
312
+ hidden: {
313
+ opacity: 0,
314
+ y: Y_TRANSLATION_DESKTOP * directionModifier,
315
+ },
316
+ visible: { opacity: 1, y: 0 },
317
+ };
318
+ return (React__default.createElement(MotionMenu, { key: `menu-content-${placement !== null && placement !== void 0 ? placement : "pending"}`, className: classnames(styles.menu, styles.ariaMenu, UNSAFE_className), style: UNSAFE_style, variants: variants, initial: "hidden",
319
+ // placement is null on first render cycle, so we need to wait for it to be defined
320
+ animate: placement ? animation : false, transition: Object.assign({}, MENU_ANIMATION_CONFIG), onAnimationComplete: animationState => {
321
+ setState(prev => animationState === "hidden" && prev === "hidden"
322
+ ? "unmounted"
323
+ : prev);
324
+ } }, children));
325
+ }),
326
+ isMobile && React__default.createElement(MenuMobileUnderlay, { animation: animation })));
327
+ }
328
+ function MenuMobileUnderlay({ animation }) {
329
+ if (animation === "unmounted")
330
+ return null;
331
+ return (React__default.createElement(motion.div, { key: "menu-mobile-underlay", variants: composeOverlayVariation, initial: "hidden", transition: Object.assign({}, OVERLAY_ANIMATION_CONFIG), className: styles.overlay, animate: animation }));
332
+ }
333
+ function MenuSeparatorComposable({ UNSAFE_style, UNSAFE_className, }) {
334
+ return (React__default.createElement($431f98aba6844401$export$1ff3c3f08ae963c0, { className: classnames(styles.separator, UNSAFE_className), style: UNSAFE_style, "data-testid": "ATL-Menu-Separator" }));
335
+ }
336
+ function MenuSectionComposable({ children, UNSAFE_style, UNSAFE_className, ariaLabel, }) {
337
+ return (React__default.createElement($3674c52c6b3c5bce$export$4b1545b4f2016d26, { "aria-label": ariaLabel, className: classnames(styles.ariaSection, UNSAFE_className), style: UNSAFE_style }, children));
338
+ }
339
+ function MenuHeaderComposable(props) {
340
+ const { UNSAFE_style, UNSAFE_className } = props;
341
+ return (React__default.createElement($72a5793c14baf454$export$8b251419efc915eb, { className: classnames(styles.sectionHeader, styles.ariaSectionHeader, UNSAFE_className), style: UNSAFE_style }, props.children));
342
+ }
343
+ const MenuItemComposable = React__default.forwardRef(function MenuItemComposable(props, ref) {
344
+ const { UNSAFE_style, UNSAFE_className } = props;
345
+ const className = classnames(styles.action, styles.ariaItem, UNSAFE_className);
346
+ if (props.href) {
347
+ const { href, target, rel, onClick } = props;
348
+ return (React__default.createElement($3674c52c6b3c5bce$export$2ce376c2cc3355c8, { ref: ref, className: className, style: UNSAFE_style, textValue: props.textValue, href: href, target: target, rel: rel, onClick: onClick },
349
+ React__default.createElement(MenuItemContext.Provider, { value: { destructive: props.destructive } }, props.children)));
350
+ }
351
+ return (React__default.createElement($3674c52c6b3c5bce$export$2ce376c2cc3355c8, { ref: ref, className: className, style: UNSAFE_style, textValue: props.textValue, onAction: () => {
352
+ var _a;
353
+ // Zero-arg activation for non-link items
354
+ (_a = props.onClick) === null || _a === void 0 ? void 0 : _a.call(props);
355
+ } },
356
+ React__default.createElement(MenuItemContext.Provider, { value: { destructive: props.destructive } }, props.children)));
357
+ });
358
+ const MenuItemContext = createContext(null);
359
+ function useMenuItemContext() {
360
+ const ctx = useContext(MenuItemContext);
361
+ return ctx !== null && ctx !== void 0 ? ctx : {};
362
+ }
363
+ function MenuItemIconComposable(props) {
364
+ const { destructive } = useMenuItemContext();
365
+ return (React__default.createElement("div", { "data-menu-slot": "icon" },
366
+ React__default.createElement(Icon, Object.assign({}, props, { color: destructive ? "destructive" : props.color }))));
367
+ }
368
+ function MenuItemLabelComposable(props) {
369
+ const { destructive } = useMenuItemContext();
370
+ return (React__default.createElement("div", { "data-menu-slot": "label" },
371
+ React__default.createElement(Typography, { element: "span", fontWeight: "semiBold", textColor: destructive ? "destructive" : "text" }, props.children)));
372
+ }
373
+ function MenuHeaderLabel(props) {
374
+ return React__default.createElement(DefaultHeaderContent, null, props.children);
375
+ }
376
+ Menu.Section = MenuSectionComposable;
377
+ Menu.Header = MenuHeaderComposable;
378
+ Menu.Item = MenuItemComposable;
379
+ Menu.Trigger = MenuTriggerComposable;
380
+ Menu.Content = MenuContentComposable;
381
+ Menu.Separator = MenuSeparatorComposable;
382
+ Menu.ItemIcon = MenuItemIconComposable;
383
+ Menu.ItemLabel = MenuItemLabelComposable;
384
+ Menu.HeaderLabel = MenuHeaderLabel;
151
385
 
152
386
  export { Menu as M };
@@ -53,6 +53,11 @@ export interface ModalContextType {
53
53
  * Floating-ui props to position the modal.
54
54
  */
55
55
  readonly getFloatingProps: UseInteractionsReturn["getFloatingProps"];
56
+ /**
57
+ * Tracks whether the current pointer interaction began inside the dialog.
58
+ * Used to disambiguate outsidePress after nested overlay closes.
59
+ */
60
+ readonly startedInsideRef?: MutableRefObject<boolean>;
56
61
  }
57
62
  export interface ModalActionsProps {
58
63
  /**
@@ -79,7 +79,7 @@ function Actions({ primary, secondary, tertiary }) {
79
79
  React.createElement(Button.Button, Object.assign({}, tertiary))))))));
80
80
  }
81
81
 
82
- function useModal({ open, activatorRef: refProp, onRequestClose, }) {
82
+ function useModal({ open, activatorRef: refProp, onRequestClose, startedInsideRef, }) {
83
83
  const nodeId = floatingUi_react.useFloatingNodeId();
84
84
  const defaultActivatorRef = React.useRef(null);
85
85
  const activatorRef = refProp !== null && refProp !== void 0 ? refProp : defaultActivatorRef;
@@ -87,14 +87,31 @@ function useModal({ open, activatorRef: refProp, onRequestClose, }) {
87
87
  elements: { reference: activatorRef === null || activatorRef === void 0 ? void 0 : activatorRef.current },
88
88
  nodeId,
89
89
  onOpenChange: (newOpen) => {
90
- if (!newOpen) {
90
+ if (!newOpen)
91
91
  onRequestClose === null || onRequestClose === void 0 ? void 0 : onRequestClose();
92
- }
93
92
  },
94
93
  open: open,
95
94
  });
95
+ React.useEffect(() => {
96
+ if (!startedInsideRef)
97
+ return;
98
+ if (open) {
99
+ // Ensure the first interaction after open is treated as inside
100
+ startedInsideRef.current = true;
101
+ }
102
+ else {
103
+ // Reset on close
104
+ startedInsideRef.current = false;
105
+ }
106
+ }, [open, startedInsideRef]);
96
107
  const dismiss = floatingUi_react.useDismiss(floatingContext, {
108
+ // Use pointerdown so the dialog/overlay capture handlers run first and set intent
97
109
  outsidePressEvent: "pointerdown",
110
+ outsidePress: () => {
111
+ var _a;
112
+ const startedInside = (_a = startedInsideRef === null || startedInsideRef === void 0 ? void 0 : startedInsideRef.current) !== null && _a !== void 0 ? _a : true;
113
+ return !startedInside;
114
+ },
98
115
  escapeKey: true,
99
116
  bubbles: false,
100
117
  });
@@ -123,10 +140,12 @@ const ModalContext = React.createContext({
123
140
  getFloatingProps: identity.identity,
124
141
  });
125
142
  function ModalProvider({ children, open = false, size, onRequestClose = noop.noop, activatorRef: refProp, dismissible = true, modalLabelledBy = MODAL_HEADER_ID, ariaLabel, }) {
143
+ const startedInsideRef = React.useRef(true);
126
144
  const { floatingRefs, floatingContext, nodeId, activatorRef, parentId, getFloatingProps, } = useModal({
127
145
  open,
128
146
  activatorRef: refProp,
129
147
  onRequestClose,
148
+ startedInsideRef,
130
149
  });
131
150
  const content = (React.createElement(ModalContext.Provider, { value: {
132
151
  onRequestClose,
@@ -140,6 +159,7 @@ function ModalProvider({ children, open = false, size, onRequestClose = noop.noo
140
159
  modalLabelledBy,
141
160
  ariaLabel,
142
161
  getFloatingProps,
162
+ startedInsideRef,
143
163
  } }, children));
144
164
  if (parentId) {
145
165
  return content;
@@ -195,13 +215,17 @@ function ModalActivator({ children }) {
195
215
  */
196
216
  function ModalOverlay({ children }) {
197
217
  const { overlay, overlayBackground } = useModalStyles();
198
- const { floatingNodeId } = useModalContext();
218
+ const { floatingNodeId, startedInsideRef } = useModalContext();
199
219
  return (React.createElement(floatingUi_react.FloatingOverlay, { lockScroll: true, className: overlay, "data-modal-node-id": floatingNodeId },
200
- React.createElement(framerMotion.motion.div, { "aria-hidden": "true", className: overlayBackground, initial: { opacity: 0 }, animate: { opacity: 0.8 }, exit: { opacity: 0 }, transition: { duration: 0.2 }, "data-modal-node-id": floatingNodeId }),
220
+ React.createElement(framerMotion.motion.div, { "aria-hidden": "true", className: overlayBackground, initial: { opacity: 0 }, animate: { opacity: 0.8 }, exit: { opacity: 0 }, transition: { duration: 0.2 }, "data-modal-node-id": floatingNodeId, onPointerDownCapture: () => {
221
+ // Interaction began on overlay: mark as outside for the next click
222
+ if (startedInsideRef)
223
+ startedInsideRef.current = false;
224
+ } }),
201
225
  children));
202
226
  }
203
227
  function ModalContent({ children }) {
204
- const { open, floatingContext, activatorRef, floatingRefs, size, floatingNodeId, modalLabelledBy, ariaLabel, getFloatingProps, } = useModalContext();
228
+ const { open, floatingContext, activatorRef, floatingRefs, size, floatingNodeId, modalLabelledBy, ariaLabel, getFloatingProps, startedInsideRef, } = useModalContext();
205
229
  const { modal } = useModalStyles(size);
206
230
  return (React.createElement(framerMotion.AnimatePresence, null, open && (React.createElement(floatingUi_react.FloatingNode, { id: floatingNodeId },
207
231
  React.createElement(floatingUi_react.FloatingPortal, null,
@@ -218,7 +242,11 @@ function ModalContent({ children }) {
218
242
  "aria-labelledby": modalLabelledBy,
219
243
  "aria-label": ariaLabel,
220
244
  "aria-modal": true,
221
- })), children))))))))));
245
+ }), { onPointerDownCapture: () => {
246
+ // Interaction began inside dialog
247
+ if (startedInsideRef)
248
+ startedInsideRef.current = true;
249
+ } }), children))))))))));
222
250
  }
223
251
 
224
252
  function Modal(props) {
@@ -1,4 +1,4 @@
1
- import React__default, { useRef, createContext, useContext } from 'react';
1
+ import React__default, { useRef, useEffect, createContext, useContext } from 'react';
2
2
  import ReactDOM__default from 'react-dom';
3
3
  import classnames from 'classnames';
4
4
  import { AnimatePresence, motion } from 'framer-motion';
@@ -7,7 +7,7 @@ import { H as Heading } from '../Heading-es.js';
7
7
  import { B as Button } from '../Button-es.js';
8
8
  import { B as ButtonDismiss } from '../ButtonDismiss-es.js';
9
9
  import { n as noop } from '../noop-es.js';
10
- import { h as useFloatingNodeId, u as useFloating, a as useDismiss, m as useRole, b as useInteractions, g as useFloatingParentNodeId, i as FloatingTree, j as FloatingNode, F as FloatingPortal, n as FloatingFocusManager, p as FloatingOverlay } from '../floating-ui.react-es.js';
10
+ import { i as useFloatingNodeId, u as useFloating, d as useDismiss, n as useRole, g as useInteractions, h as useFloatingParentNodeId, j as FloatingTree, k as FloatingNode, F as FloatingPortal, p as FloatingFocusManager, q as FloatingOverlay } from '../floating-ui.react-es.js';
11
11
  import { b as identity } from '../identity-es.js';
12
12
  import { A as AtlantisPortalContent } from '../AtlantisPortalContent-es.js';
13
13
  import '../Typography-es.js';
@@ -77,7 +77,7 @@ function Actions({ primary, secondary, tertiary }) {
77
77
  React__default.createElement(Button, Object.assign({}, tertiary))))))));
78
78
  }
79
79
 
80
- function useModal({ open, activatorRef: refProp, onRequestClose, }) {
80
+ function useModal({ open, activatorRef: refProp, onRequestClose, startedInsideRef, }) {
81
81
  const nodeId = useFloatingNodeId();
82
82
  const defaultActivatorRef = useRef(null);
83
83
  const activatorRef = refProp !== null && refProp !== void 0 ? refProp : defaultActivatorRef;
@@ -85,14 +85,31 @@ function useModal({ open, activatorRef: refProp, onRequestClose, }) {
85
85
  elements: { reference: activatorRef === null || activatorRef === void 0 ? void 0 : activatorRef.current },
86
86
  nodeId,
87
87
  onOpenChange: (newOpen) => {
88
- if (!newOpen) {
88
+ if (!newOpen)
89
89
  onRequestClose === null || onRequestClose === void 0 ? void 0 : onRequestClose();
90
- }
91
90
  },
92
91
  open: open,
93
92
  });
93
+ useEffect(() => {
94
+ if (!startedInsideRef)
95
+ return;
96
+ if (open) {
97
+ // Ensure the first interaction after open is treated as inside
98
+ startedInsideRef.current = true;
99
+ }
100
+ else {
101
+ // Reset on close
102
+ startedInsideRef.current = false;
103
+ }
104
+ }, [open, startedInsideRef]);
94
105
  const dismiss = useDismiss(floatingContext, {
106
+ // Use pointerdown so the dialog/overlay capture handlers run first and set intent
95
107
  outsidePressEvent: "pointerdown",
108
+ outsidePress: () => {
109
+ var _a;
110
+ const startedInside = (_a = startedInsideRef === null || startedInsideRef === void 0 ? void 0 : startedInsideRef.current) !== null && _a !== void 0 ? _a : true;
111
+ return !startedInside;
112
+ },
96
113
  escapeKey: true,
97
114
  bubbles: false,
98
115
  });
@@ -121,10 +138,12 @@ const ModalContext = createContext({
121
138
  getFloatingProps: identity,
122
139
  });
123
140
  function ModalProvider({ children, open = false, size, onRequestClose = noop, activatorRef: refProp, dismissible = true, modalLabelledBy = MODAL_HEADER_ID, ariaLabel, }) {
141
+ const startedInsideRef = useRef(true);
124
142
  const { floatingRefs, floatingContext, nodeId, activatorRef, parentId, getFloatingProps, } = useModal({
125
143
  open,
126
144
  activatorRef: refProp,
127
145
  onRequestClose,
146
+ startedInsideRef,
128
147
  });
129
148
  const content = (React__default.createElement(ModalContext.Provider, { value: {
130
149
  onRequestClose,
@@ -138,6 +157,7 @@ function ModalProvider({ children, open = false, size, onRequestClose = noop, ac
138
157
  modalLabelledBy,
139
158
  ariaLabel,
140
159
  getFloatingProps,
160
+ startedInsideRef,
141
161
  } }, children));
142
162
  if (parentId) {
143
163
  return content;
@@ -193,13 +213,17 @@ function ModalActivator({ children }) {
193
213
  */
194
214
  function ModalOverlay({ children }) {
195
215
  const { overlay, overlayBackground } = useModalStyles();
196
- const { floatingNodeId } = useModalContext();
216
+ const { floatingNodeId, startedInsideRef } = useModalContext();
197
217
  return (React__default.createElement(FloatingOverlay, { lockScroll: true, className: overlay, "data-modal-node-id": floatingNodeId },
198
- React__default.createElement(motion.div, { "aria-hidden": "true", className: overlayBackground, initial: { opacity: 0 }, animate: { opacity: 0.8 }, exit: { opacity: 0 }, transition: { duration: 0.2 }, "data-modal-node-id": floatingNodeId }),
218
+ React__default.createElement(motion.div, { "aria-hidden": "true", className: overlayBackground, initial: { opacity: 0 }, animate: { opacity: 0.8 }, exit: { opacity: 0 }, transition: { duration: 0.2 }, "data-modal-node-id": floatingNodeId, onPointerDownCapture: () => {
219
+ // Interaction began on overlay: mark as outside for the next click
220
+ if (startedInsideRef)
221
+ startedInsideRef.current = false;
222
+ } }),
199
223
  children));
200
224
  }
201
225
  function ModalContent({ children }) {
202
- const { open, floatingContext, activatorRef, floatingRefs, size, floatingNodeId, modalLabelledBy, ariaLabel, getFloatingProps, } = useModalContext();
226
+ const { open, floatingContext, activatorRef, floatingRefs, size, floatingNodeId, modalLabelledBy, ariaLabel, getFloatingProps, startedInsideRef, } = useModalContext();
203
227
  const { modal } = useModalStyles(size);
204
228
  return (React__default.createElement(AnimatePresence, null, open && (React__default.createElement(FloatingNode, { id: floatingNodeId },
205
229
  React__default.createElement(FloatingPortal, null,
@@ -216,7 +240,11 @@ function ModalContent({ children }) {
216
240
  "aria-labelledby": modalLabelledBy,
217
241
  "aria-label": ariaLabel,
218
242
  "aria-modal": true,
219
- })), children))))))))));
243
+ }), { onPointerDownCapture: () => {
244
+ // Interaction began inside dialog
245
+ if (startedInsideRef)
246
+ startedInsideRef.current = true;
247
+ } }), children))))))))));
220
248
  }
221
249
 
222
250
  function Modal(props) {
@@ -1,11 +1,13 @@
1
+ import type { MutableRefObject } from "react";
1
2
  interface UseModalProps {
2
3
  open: boolean;
3
4
  activatorRef?: React.RefObject<HTMLElement | null> | null;
4
5
  onRequestClose: () => void;
6
+ startedInsideRef?: MutableRefObject<boolean> | undefined;
5
7
  }
6
- export declare function useModal({ open, activatorRef: refProp, onRequestClose, }: UseModalProps): {
8
+ export declare function useModal({ open, activatorRef: refProp, onRequestClose, startedInsideRef, }: UseModalProps): {
7
9
  floatingRefs: {
8
- reference: import("react").MutableRefObject<import("@floating-ui/react-dom").ReferenceType | null>;
10
+ reference: MutableRefObject<import("@floating-ui/react-dom").ReferenceType | null>;
9
11
  floating: React.MutableRefObject<HTMLElement | null>;
10
12
  setReference: (node: import("@floating-ui/react-dom").ReferenceType | null) => void;
11
13
  setFloating: (node: HTMLElement | null) => void;
@@ -21,6 +21,8 @@ require('../Menu-cjs.js');
21
21
  require('framer-motion');
22
22
  require('../floating-ui.react-cjs.js');
23
23
  require('react-dom');
24
+ require('../Tree-cjs.js');
25
+ require('../clsx-cjs.js');
24
26
  require('../useFormFieldFocus-cjs.js');
25
27
  require('../maxHeight-cjs.js');
26
28
 
@@ -19,5 +19,7 @@ import '../Menu-es.js';
19
19
  import 'framer-motion';
20
20
  import '../floating-ui.react-es.js';
21
21
  import 'react-dom';
22
+ import '../Tree-es.js';
23
+ import '../clsx-es.js';
22
24
  import '../useFormFieldFocus-es.js';
23
25
  import '../maxHeight-es.js';