@deque/cauldron-react 6.12.0-canary.67739754 → 6.12.0-canary.775bed0e

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.
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import AriaIsolate from '../../utils/aria-isolate';
3
2
  export interface DialogProps extends React.HTMLAttributes<HTMLDivElement> {
4
3
  children: React.ReactNode;
5
4
  className?: string;
@@ -14,31 +13,8 @@ export interface DialogProps extends React.HTMLAttributes<HTMLDivElement> {
14
13
  closeButtonText?: string;
15
14
  portal?: React.RefObject<HTMLElement> | HTMLElement;
16
15
  }
17
- interface DialogState {
18
- isolator?: AriaIsolate;
19
- }
20
- export default class Dialog extends React.Component<DialogProps, DialogState> {
21
- static defaultProps: {
22
- onClose: () => void;
23
- forceAction: boolean;
24
- closeButtonText: string;
25
- };
26
- private element;
27
- private heading;
28
- private headingId;
29
- constructor(props: DialogProps);
30
- componentDidMount(): void;
31
- componentWillUnmount(): void;
32
- componentDidUpdate(prevProps: DialogProps): void;
33
- private attachIsolator;
34
- render(): React.JSX.Element | null;
35
- close(): void;
36
- handleClickOutside(): void;
37
- focusHeading(): void;
38
- private handleEscape;
39
- private attachEventListeners;
40
- private removeEventListeners;
41
- }
16
+ declare const Dialog: React.ForwardRefExoticComponent<DialogProps & React.RefAttributes<HTMLDivElement>>;
17
+ export default Dialog;
42
18
  interface DialogAlignmentProps {
43
19
  align?: 'left' | 'center' | 'right';
44
20
  }
@@ -1,3 +1,4 @@
1
+ import type { ElementOrRef } from '../../types';
1
2
  import React from 'react';
2
3
  export interface DrawerProps<T extends HTMLElement = HTMLElement> extends React.HTMLAttributes<HTMLDivElement> {
3
4
  children: React.ReactNode;
@@ -5,8 +6,8 @@ export interface DrawerProps<T extends HTMLElement = HTMLElement> extends React.
5
6
  open?: boolean;
6
7
  behavior?: 'modal' | 'non-modal';
7
8
  focusOptions?: {
8
- initialFocus?: T | React.RefObject<T> | React.MutableRefObject<T>;
9
- returnFocus?: T | React.RefObject<T> | React.MutableRefObject<T>;
9
+ initialFocus?: ElementOrRef<T>;
10
+ returnFocus?: ElementOrRef<T>;
10
11
  };
11
12
  onClose?: () => void;
12
13
  portal?: React.RefObject<HTMLElement> | HTMLElement;
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  export type Column = {
3
3
  align: ColumnAlignment;
4
4
  width?: ColumnWidth;
5
+ maxWidth?: ColumnWidth;
5
6
  };
6
7
  export type ColumnAlignment = 'start' | 'center' | 'end';
7
8
  export type ColumnWidth = 'auto' | 'min-content' | 'max-content' | `${number}` | `${number}%` | `${number}fr`;
package/lib/index.js CHANGED
@@ -8,7 +8,7 @@ var classNames = require('classnames');
8
8
  var nextId = require('react-id-generator');
9
9
  var keyname = require('keyname');
10
10
  var reactDom = require('react-dom');
11
- var FocusTrap = require('focus-trap-react');
11
+ var focusable = require('focusable');
12
12
  var dom = require('@floating-ui/dom');
13
13
  var reactDom$1 = require('@floating-ui/react-dom');
14
14
  var SyntaxHighlighter = require('react-syntax-highlighter/dist/cjs/light');
@@ -23,7 +23,7 @@ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
23
23
  var classNames__default = /*#__PURE__*/_interopDefaultLegacy(classNames);
24
24
  var nextId__default = /*#__PURE__*/_interopDefaultLegacy(nextId);
25
25
  var keyname__default = /*#__PURE__*/_interopDefaultLegacy(keyname);
26
- var FocusTrap__default = /*#__PURE__*/_interopDefaultLegacy(FocusTrap);
26
+ var focusable__default = /*#__PURE__*/_interopDefaultLegacy(focusable);
27
27
  var SyntaxHighlighter__default = /*#__PURE__*/_interopDefaultLegacy(SyntaxHighlighter);
28
28
  var js__default = /*#__PURE__*/_interopDefaultLegacy(js);
29
29
  var css__default = /*#__PURE__*/_interopDefaultLegacy(css);
@@ -1342,133 +1342,277 @@ var AriaIsolate = /** @class */ (function () {
1342
1342
  return AriaIsolate;
1343
1343
  }());
1344
1344
 
1345
- var isEscape = function (event) {
1346
- return event.key === 'Escape' || event.key === 'Esc' || event.keyCode === 27;
1347
- };
1348
- var noop = function () {
1349
- //not empty
1345
+ /**
1346
+ * When a component needs to track an internal ref on a component that has a
1347
+ * forwarded ref, useSharedRef will return a MutableRefObject<T> that will
1348
+ * correctly invoke the parent ref as well providing an internal ref.
1349
+ *
1350
+ * @example
1351
+ * React.forwardRef(function Component({ children }, ref) {
1352
+ * const internalRef = useSharedRef<HTMLElement>(ref)
1353
+ * if (internalRef.current) {
1354
+ * // do something with the internal ref...
1355
+ * }
1356
+ * return (<div ref={internalRef}>...</div>)
1357
+ * })
1358
+ */
1359
+ function useSharedRef(ref) {
1360
+ var internalRef = React.useRef();
1361
+ React.useEffect(function () {
1362
+ setRef(ref, internalRef.current);
1363
+ }, [ref]);
1364
+ return internalRef;
1365
+ }
1366
+
1367
+ // When multiple focus traps are created, we need to keep track of all previous traps
1368
+ // in the stack and temporarily suspend any traps created before the most recent trap
1369
+ var focusTrapStack = [];
1370
+ var removeFocusTrapFromStack = function (focusTrap) {
1371
+ var focusTrapIndex = focusTrapStack.findIndex(function (trap) { return focusTrap.targetElement === trap.targetElement; });
1372
+ focusTrapStack.splice(focusTrapIndex, 1);
1350
1373
  };
1351
- var Dialog = /** @class */ (function (_super) {
1352
- tslib.__extends(Dialog, _super);
1353
- function Dialog(props) {
1354
- var _this = _super.call(this, props) || this;
1355
- _this.headingId = nextId__default["default"]('dialog-title-');
1356
- _this.close = _this.close.bind(_this);
1357
- _this.focusHeading = _this.focusHeading.bind(_this);
1358
- _this.handleClickOutside = _this.handleClickOutside.bind(_this);
1359
- _this.handleEscape = _this.handleEscape.bind(_this);
1360
- _this.state = {};
1361
- return _this;
1374
+ function getActiveElement(target) {
1375
+ var _a;
1376
+ return (((_a = target === null || target === void 0 ? void 0 : target.ownerDocument.activeElement) !== null && _a !== void 0 ? _a : document.activeElement) ||
1377
+ document.body);
1378
+ }
1379
+ function elementContains(containerElement, targetElement) {
1380
+ if (!targetElement) {
1381
+ return false;
1362
1382
  }
1363
- Dialog.prototype.componentDidMount = function () {
1364
- var _this = this;
1365
- if (this.props.show) {
1366
- this.attachEventListeners();
1367
- this.attachIsolator(function () { return setTimeout(_this.focusHeading); });
1383
+ if (containerElement.getRootNode() === targetElement.getRootNode()) {
1384
+ return containerElement.contains(targetElement);
1385
+ }
1386
+ var root = targetElement.getRootNode();
1387
+ while (root && root !== containerElement.getRootNode()) {
1388
+ if (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
1389
+ // likely a shadow root, and we need to get the host
1390
+ root = root.host;
1368
1391
  }
1392
+ else {
1393
+ break;
1394
+ }
1395
+ }
1396
+ return root && containerElement.contains(root);
1397
+ }
1398
+ function createTrapGuard() {
1399
+ var trapGuard = document.createElement('span');
1400
+ trapGuard.setAttribute('tabindex', '0');
1401
+ trapGuard.setAttribute('aria-hidden', 'true');
1402
+ return trapGuard;
1403
+ }
1404
+ function createFocusTrap(targetElement, initialFocusElement) {
1405
+ var _a;
1406
+ var startGuard = createTrapGuard();
1407
+ var endGuard = createTrapGuard();
1408
+ targetElement.insertAdjacentElement('beforebegin', startGuard);
1409
+ targetElement.insertAdjacentElement('afterend', endGuard);
1410
+ var focusTrapMetadata = {
1411
+ targetElement: targetElement,
1412
+ lastFocusedElement: null,
1413
+ suspended: false
1369
1414
  };
1370
- Dialog.prototype.componentWillUnmount = function () {
1371
- var isolator = this.state.isolator;
1372
- isolator === null || isolator === void 0 ? void 0 : isolator.deactivate();
1373
- this.removeEventListeners();
1374
- };
1375
- Dialog.prototype.componentDidUpdate = function (prevProps) {
1376
- if (!prevProps.show && this.props.show) {
1377
- this.attachIsolator(this.focusHeading);
1378
- this.attachEventListeners();
1415
+ var focusListener = function (event) {
1416
+ var _a, _b, _c, _d;
1417
+ var eventTarget = event.target;
1418
+ var elementContainsTarget = elementContains(targetElement, eventTarget);
1419
+ if (focusTrapMetadata.suspended) {
1420
+ return;
1379
1421
  }
1380
- else if (prevProps.show && !this.props.show) {
1381
- this.removeEventListeners();
1382
- this.close();
1422
+ if (!elementContainsTarget) {
1423
+ // If the event target element is not contained within the target element
1424
+ // for this focus trap, we need to prevent focus from escaping the container
1425
+ event.stopImmediatePropagation();
1383
1426
  }
1384
- };
1385
- Dialog.prototype.attachIsolator = function (done) {
1386
- this.setState({
1387
- isolator: new AriaIsolate(this.element)
1388
- }, done);
1389
- };
1390
- Dialog.prototype.render = function () {
1391
- var _this = this;
1392
- var _a = this.props, dialogRef = _a.dialogRef, forceAction = _a.forceAction, className = _a.className, children = _a.children, closeButtonText = _a.closeButtonText, heading = _a.heading, show = _a.show, other = tslib.__rest(_a, ["dialogRef", "forceAction", "className", "children", "closeButtonText", "heading", "show"]);
1393
- if (!show || !isBrowser()) {
1394
- return null;
1427
+ else if (eventTarget instanceof HTMLElement) {
1428
+ // Ensure we keep track of the most recent valid focus element if we
1429
+ // need to redirect focus later
1430
+ focusTrapMetadata.lastFocusedElement = eventTarget;
1431
+ return;
1395
1432
  }
1396
- var portal = this.props.portal || document.body;
1397
- var close = !forceAction ? (React__default["default"].createElement("button", { className: "Dialog__close", type: "button", onClick: this.close },
1398
- React__default["default"].createElement(Icon, { type: "close", "aria-hidden": "true" }),
1399
- React__default["default"].createElement(Offscreen, null, closeButtonText))) : null;
1400
- var Heading = "h".concat(typeof heading === 'object' && 'level' in heading && !!heading.level
1401
- ? heading.level
1402
- : 2);
1403
- var Dialog = (React__default["default"].createElement(FocusTrap__default["default"], { focusTrapOptions: {
1404
- allowOutsideClick: true,
1405
- escapeDeactivates: false,
1406
- fallbackFocus: '.Dialog__heading'
1407
- } },
1408
- React__default["default"].createElement(ClickOutsideListener$1, { onClickOutside: this.handleClickOutside },
1409
- React__default["default"].createElement("div", tslib.__assign({ role: "dialog", className: classNames__default["default"]('Dialog', className, {
1410
- 'Dialog--show': show
1411
- }), ref: function (el) {
1412
- _this.element = el;
1413
- if (!dialogRef) {
1414
- return;
1415
- }
1416
- setRef(dialogRef, el);
1417
- }, "aria-labelledby": this.headingId }, other),
1418
- React__default["default"].createElement("div", { className: "Dialog__inner" },
1419
- React__default["default"].createElement("div", { className: "Dialog__header" },
1420
- React__default["default"].createElement(Heading, { className: "Dialog__heading", ref: function (el) { return (_this.heading = el); }, tabIndex: -1, id: this.headingId }, typeof heading === 'object' && 'text' in heading
1421
- ? heading.text
1422
- : heading),
1423
- close),
1424
- children)))));
1425
- return reactDom.createPortal(Dialog, ('current' in portal ? portal.current : portal) || document.body);
1426
- };
1427
- Dialog.prototype.close = function () {
1428
- var _a, _b, _c;
1429
- (_a = this.state.isolator) === null || _a === void 0 ? void 0 : _a.deactivate();
1430
- if (this.props.show) {
1431
- (_c = (_b = this.props).onClose) === null || _c === void 0 ? void 0 : _c.call(_b);
1433
+ var focusableElements = Array.from((targetElement === null || targetElement === void 0 ? void 0 : targetElement.querySelectorAll(focusable__default["default"])) || []);
1434
+ // If focus reaches the trap guards, we need to wrap focus around to the leading
1435
+ // or trailing focusable element depending on which guard obtained focus
1436
+ if (focusableElements.length && eventTarget === startGuard) {
1437
+ (_a = focusableElements.reverse()[0]) === null || _a === void 0 ? void 0 : _a.focus();
1438
+ return;
1432
1439
  }
1433
- };
1434
- Dialog.prototype.handleClickOutside = function () {
1435
- var _a = this.props, show = _a.show, forceAction = _a.forceAction;
1436
- if (show && !forceAction) {
1437
- this.close();
1440
+ else if (focusableElements.length && eventTarget === endGuard) {
1441
+ (_b = focusableElements[0]) === null || _b === void 0 ? void 0 : _b.focus();
1442
+ return;
1443
+ }
1444
+ // If focus somehow escaped the trap, we need to try to restore focus to
1445
+ // to a suitable focusable element within the focus trap target. Otherwise
1446
+ // we'll need to focus on an alternative within the container.
1447
+ if (elementContains(targetElement, focusTrapMetadata.lastFocusedElement)) {
1448
+ (_c = focusTrapMetadata.lastFocusedElement) === null || _c === void 0 ? void 0 : _c.focus();
1449
+ }
1450
+ else if (focusableElements.length) {
1451
+ (_d = focusableElements[0]) === null || _d === void 0 ? void 0 : _d.focus();
1452
+ }
1453
+ else {
1454
+ // if there are no focusable elements, just focus the container
1455
+ targetElement.focus();
1438
1456
  }
1439
1457
  };
1440
- Dialog.prototype.focusHeading = function () {
1458
+ document.addEventListener('focus', focusListener, true);
1459
+ if (focusTrapStack.length >= 1) {
1460
+ // Suspend any other traps in the stack while this one is active
1461
+ focusTrapStack.forEach(function (trap) {
1462
+ trap.suspended = true;
1463
+ });
1464
+ }
1465
+ focusTrapStack.push(focusTrapMetadata);
1466
+ if (initialFocusElement) {
1467
+ initialFocusElement.focus();
1468
+ }
1469
+ else {
1470
+ // Try to find a suitable focus element
1471
+ var focusableElements = Array.from((targetElement === null || targetElement === void 0 ? void 0 : targetElement.querySelectorAll(focusable__default["default"])) ||
1472
+ /* istanbul ignore else */ []);
1473
+ (_a = focusableElements[0]) === null || _a === void 0 ? void 0 : _a.focus();
1474
+ }
1475
+ return tslib.__assign(tslib.__assign({}, focusTrapMetadata), { initialFocusElement: initialFocusElement, destroy: function () {
1476
+ var _a, _b;
1477
+ document.removeEventListener('focus', focusListener, true);
1478
+ removeFocusTrapFromStack(focusTrapMetadata);
1479
+ (_a = startGuard.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(startGuard);
1480
+ (_b = endGuard.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(endGuard);
1481
+ // If there are any remaining focus traps in the stack, we need
1482
+ // to unsuspend the most recently added focus trap
1483
+ if (focusTrapStack.length) {
1484
+ focusTrapStack[focusTrapStack.length - 1].suspended = false;
1485
+ }
1486
+ } });
1487
+ }
1488
+ function useFocusTrap(target, options) {
1489
+ if (options === void 0) { options = {}; }
1490
+ var _a = options.disabled, disabled = _a === void 0 ? false : _a, _b = options.returnFocus, returnFocus = _b === void 0 ? true : _b, initialFocusElementOrRef = options.initialFocusElement, returnFocusElement = options.returnFocusElement;
1491
+ var focusTrap = React.useRef(null);
1492
+ var returnFocusElementRef = React.useRef();
1493
+ function restoreFocusToReturnFocusElement() {
1441
1494
  var _a;
1442
- if (this.heading) {
1443
- this.heading.focus();
1495
+ var resolvedReturnFocusElement = resolveElement(returnFocusElement);
1496
+ if (resolvedReturnFocusElement instanceof HTMLElement) {
1497
+ resolvedReturnFocusElement.focus();
1444
1498
  }
1445
- (_a = this.state.isolator) === null || _a === void 0 ? void 0 : _a.activate();
1446
- };
1447
- Dialog.prototype.handleEscape = function (keyboardEvent) {
1499
+ else {
1500
+ (_a = returnFocusElementRef.current) === null || _a === void 0 ? void 0 : _a.focus();
1501
+ returnFocusElementRef.current = null;
1502
+ }
1503
+ }
1504
+ React.useEffect(function () {
1505
+ var targetElement = resolveElement(target);
1506
+ var initialFocusElement = resolveElement(initialFocusElementOrRef);
1507
+ if (!targetElement || disabled) {
1508
+ return;
1509
+ }
1510
+ returnFocusElementRef.current = getActiveElement(targetElement);
1511
+ focusTrap.current = createFocusTrap(targetElement, initialFocusElement);
1512
+ return function () {
1513
+ var _a;
1514
+ (_a = focusTrap.current) === null || _a === void 0 ? void 0 : _a.destroy();
1515
+ focusTrap.current = null;
1516
+ // istanbul ignore else
1517
+ if (returnFocus) {
1518
+ restoreFocusToReturnFocusElement();
1519
+ }
1520
+ };
1521
+ }, [target, disabled]);
1522
+ return focusTrap;
1523
+ }
1524
+
1525
+ var isEscape = function (event) {
1526
+ return event.key === 'Escape' || event.key === 'Esc' || event.keyCode === 27;
1527
+ };
1528
+ var Dialog = React.forwardRef(function (_a, ref) {
1529
+ var dialogRefProp = _a.dialogRef, _b = _a.forceAction, forceAction = _b === void 0 ? false : _b, className = _a.className, children = _a.children, _c = _a.closeButtonText, closeButtonText = _c === void 0 ? 'Close' : _c, heading = _a.heading, _d = _a.show, show = _d === void 0 ? false : _d, portal = _a.portal, _e = _a.onClose, onClose = _e === void 0 ? function () { return null; } : _e, other = tslib.__rest(_a, ["dialogRef", "forceAction", "className", "children", "closeButtonText", "heading", "show", "portal", "onClose"]);
1530
+ var dialogRef = useSharedRef(dialogRefProp || ref);
1531
+ var _f = tslib.__read(nextId.useId(1, 'dialog-title-'), 1), headingId = _f[0];
1532
+ var headingRef = React.useRef(null);
1533
+ var isolatorRef = React.useRef();
1534
+ var handleClose = React.useCallback(function () {
1535
+ var _a;
1536
+ (_a = isolatorRef.current) === null || _a === void 0 ? void 0 : _a.deactivate();
1537
+ if (show) {
1538
+ onClose();
1539
+ }
1540
+ }, [show, onClose]);
1541
+ var handleClickOutside = React.useCallback(function () {
1542
+ if (show && !forceAction) {
1543
+ handleClose();
1544
+ }
1545
+ }, [show, forceAction, handleClose]);
1546
+ var focusHeading = React.useCallback(function () {
1547
+ var _a, _b;
1548
+ (_a = headingRef.current) === null || _a === void 0 ? void 0 : _a.focus();
1549
+ (_b = isolatorRef.current) === null || _b === void 0 ? void 0 : _b.activate();
1550
+ }, []);
1551
+ var handleEscape = React.useCallback(function (keyboardEvent) {
1448
1552
  if (!keyboardEvent.defaultPrevented && isEscape(keyboardEvent)) {
1449
- this.close();
1553
+ handleClose();
1450
1554
  }
1451
- };
1452
- Dialog.prototype.attachEventListeners = function () {
1453
- var forceAction = this.props.forceAction;
1555
+ }, [handleClose]);
1556
+ React.useEffect(function () {
1557
+ if (!show || !dialogRef.current)
1558
+ return;
1559
+ isolatorRef.current = new AriaIsolate(dialogRef.current);
1560
+ setTimeout(focusHeading);
1561
+ return function () {
1562
+ var _a;
1563
+ (_a = isolatorRef.current) === null || _a === void 0 ? void 0 : _a.deactivate();
1564
+ };
1565
+ }, [show, focusHeading]);
1566
+ React.useEffect(function () {
1454
1567
  if (!forceAction) {
1455
- var portal = this.props.portal || document.body;
1456
- var targetElement = portal instanceof HTMLElement ? portal : portal.current;
1457
- targetElement === null || targetElement === void 0 ? void 0 : targetElement.addEventListener('keyup', this.handleEscape);
1568
+ var portalElement_1 = portal
1569
+ ? 'current' in portal
1570
+ ? portal.current
1571
+ : portal
1572
+ : document.body;
1573
+ if (show) {
1574
+ portalElement_1 === null || portalElement_1 === void 0 ? void 0 : portalElement_1.addEventListener('keyup', handleEscape);
1575
+ }
1576
+ return function () {
1577
+ portalElement_1 === null || portalElement_1 === void 0 ? void 0 : portalElement_1.removeEventListener('keyup', handleEscape);
1578
+ };
1458
1579
  }
1459
- };
1460
- Dialog.prototype.removeEventListeners = function () {
1461
- var portal = this.props.portal || document.body;
1462
- var targetElement = portal instanceof HTMLElement ? portal : portal.current;
1463
- targetElement === null || targetElement === void 0 ? void 0 : targetElement.removeEventListener('keyup', this.handleEscape);
1464
- };
1465
- Dialog.defaultProps = {
1466
- onClose: noop,
1467
- forceAction: false,
1468
- closeButtonText: 'Close'
1469
- };
1470
- return Dialog;
1471
- }(React__default["default"].Component));
1580
+ }, [show, forceAction, portal, handleEscape]);
1581
+ useFocusTrap(dialogRef, {
1582
+ disabled: !show,
1583
+ initialFocusElement: headingRef
1584
+ });
1585
+ if (!show || !isBrowser()) {
1586
+ return null;
1587
+ }
1588
+ var portalElement = portal
1589
+ ? 'current' in portal
1590
+ ? portal.current
1591
+ : portal
1592
+ : // eslint-disable-next-line ssr-friendly/no-dom-globals-in-react-fc
1593
+ document.body;
1594
+ var closeButton = !forceAction ? (React__default["default"].createElement("button", { className: "Dialog__close", type: "button", onClick: handleClose },
1595
+ React__default["default"].createElement(Icon, { type: "close", "aria-hidden": "true" }),
1596
+ React__default["default"].createElement(Offscreen, null, closeButtonText))) : null;
1597
+ var HeadingLevel = "h".concat(typeof heading === 'object' && 'level' in heading && heading.level
1598
+ ? heading.level
1599
+ : 2);
1600
+ var dialog = (React__default["default"].createElement(ClickOutsideListener$1, { onClickOutside: handleClickOutside },
1601
+ React__default["default"].createElement("div", tslib.__assign({ role: "dialog", className: classNames__default["default"]('Dialog', className, {
1602
+ 'Dialog--show': show
1603
+ }), ref: dialogRef, "aria-labelledby": headingId }, other),
1604
+ React__default["default"].createElement("div", { className: "Dialog__inner" },
1605
+ React__default["default"].createElement("div", { className: "Dialog__header" },
1606
+ React__default["default"].createElement(HeadingLevel, { className: "Dialog__heading", ref: headingRef, tabIndex: -1, id: headingId }, typeof heading === 'object' && 'text' in heading
1607
+ ? heading.text
1608
+ : heading),
1609
+ closeButton),
1610
+ children))));
1611
+ return reactDom.createPortal(dialog,
1612
+ // eslint-disable-next-line ssr-friendly/no-dom-globals-in-react-fc
1613
+ portalElement || document.body);
1614
+ });
1615
+ Dialog.displayName = 'Dialog';
1472
1616
  var DialogContent = function (_a) {
1473
1617
  var children = _a.children, className = _a.className, align = _a.align, other = tslib.__rest(_a, ["children", "className", "align"]);
1474
1618
  return (React__default["default"].createElement("div", tslib.__assign({ className: classNames__default["default"]('Dialog__content', className, {
@@ -1584,28 +1728,6 @@ var Button = React.forwardRef(function (_a, ref) {
1584
1728
  });
1585
1729
  Button.displayName = 'Button';
1586
1730
 
1587
- /**
1588
- * When a component needs to track an internal ref on a component that has a
1589
- * forwarded ref, useSharedRef will return a MutableRefObject<T> that will
1590
- * correctly invoke the parent ref as well providing an internal ref.
1591
- *
1592
- * @example
1593
- * React.forwardRef(function Component({ children }, ref) {
1594
- * const internalRef = useSharedRef<HTMLElement>(ref)
1595
- * if (internalRef.current) {
1596
- * // do something with the internal ref...
1597
- * }
1598
- * return (<div ref={internalRef}>...</div>)
1599
- * })
1600
- */
1601
- function useSharedRef(ref) {
1602
- var internalRef = React.useRef();
1603
- React.useEffect(function () {
1604
- setRef(ref, internalRef.current);
1605
- }, [ref]);
1606
- return internalRef;
1607
- }
1608
-
1609
1731
  var isEscapeKey = function (event) {
1610
1732
  return event.key === 'Escape' || event.key === 'Esc' || event.keyCode === 27;
1611
1733
  };
@@ -2717,21 +2839,16 @@ var LoaderOverlay = React.forwardRef(function (_a, ref) {
2717
2839
  overlayRef.current.focus();
2718
2840
  }
2719
2841
  }, []);
2720
- var Wrapper = focusTrap ? FocusTrap__default["default"] : React__default["default"].Fragment;
2721
- var wrapperProps = focusTrap
2722
- ? {
2723
- focusTrapOptions: {
2724
- fallbackFocus: '.Loader__overlay'
2725
- }
2726
- }
2727
- : {};
2728
- return (React__default["default"].createElement(Wrapper, tslib.__assign({}, wrapperProps),
2729
- React__default["default"].createElement("div", tslib.__assign({ className: classNames__default["default"]('Loader__overlay', className), ref: overlayRef, tabIndex: -1 }, other),
2730
- React__default["default"].createElement("div", { className: "Loader__overlay__loader" },
2731
- React__default["default"].createElement(Loader, null),
2732
- React__default["default"].createElement(AxeLoader, null)),
2733
- label ? (React__default["default"].createElement("span", { className: "Loader__overlay__label" }, label)) : null,
2734
- children)));
2842
+ useFocusTrap(overlayRef, {
2843
+ disabled: !focusTrap,
2844
+ initialFocusElement: overlayRef
2845
+ });
2846
+ return (React__default["default"].createElement("div", tslib.__assign({ className: classNames__default["default"]('Loader__overlay', className), ref: overlayRef, tabIndex: -1 }, other),
2847
+ React__default["default"].createElement("div", { className: "Loader__overlay__loader" },
2848
+ React__default["default"].createElement(Loader, null),
2849
+ React__default["default"].createElement(AxeLoader, null)),
2850
+ label ? React__default["default"].createElement("span", { className: "Loader__overlay__label" }, label) : null,
2851
+ children));
2735
2852
  });
2736
2853
  LoaderOverlay.displayName = 'LoaderOverlay';
2737
2854
 
@@ -2815,6 +2932,16 @@ function useTable() {
2815
2932
  return React.useContext(TableContext);
2816
2933
  }
2817
2934
 
2935
+ function parseColumnWidth(width) {
2936
+ var number = Number(width);
2937
+ if (!isNaN(number)) {
2938
+ return "".concat(number, "px");
2939
+ }
2940
+ if (!width) {
2941
+ return 'auto';
2942
+ }
2943
+ return width;
2944
+ }
2818
2945
  var Table = React__default["default"].forwardRef(function (_a, ref) {
2819
2946
  var children = _a.children, className = _a.className, variant = _a.variant, layout = _a.layout, _b = _a.columns, columnsProp = _b === void 0 ? [] : _b, style = _a.style, other = tslib.__rest(_a, ["children", "className", "variant", "layout", "columns", "style"]);
2820
2947
  var isGridLayout = layout === 'grid';
@@ -2835,8 +2962,11 @@ var Table = React__default["default"].forwardRef(function (_a, ref) {
2835
2962
  }
2836
2963
  return columns
2837
2964
  .map(function (_a) {
2838
- var width = _a.width;
2839
- return width || 'auto';
2965
+ var width = _a.width, maxWidth = _a.maxWidth;
2966
+ if (maxWidth) {
2967
+ return "minmax(".concat(parseColumnWidth(width), ", ").concat(parseColumnWidth(maxWidth), ")");
2968
+ }
2969
+ return parseColumnWidth(width);
2840
2970
  })
2841
2971
  .join(' ');
2842
2972
  }, [layout, columns]);
@@ -3404,16 +3534,14 @@ var TwoColumnPanel = React.forwardRef(function (_a, ref) {
3404
3534
  setCollapsed(true);
3405
3535
  }
3406
3536
  };
3537
+ useFocusTrap(columnLeftRef, {
3538
+ disabled: !showPanel || !isFocusTrap
3539
+ });
3407
3540
  return (React__default["default"].createElement("div", tslib.__assign({ className: classNames__default["default"]('TwoColumnPanel', className, {
3408
3541
  'TwoColumnPanel--show': !isCollapsed,
3409
3542
  'TwoColumnPanel--hide': isCollapsed
3410
3543
  }) }, props, { ref: ref }),
3411
3544
  React__default["default"].createElement(React__default["default"].Fragment, null,
3412
- React__default["default"].createElement(FocusTrap__default["default"], { active: !isCollapsed && isFocusTrap, focusTrapOptions: {
3413
- escapeDeactivates: true,
3414
- allowOutsideClick: true,
3415
- fallbackFocus: columnLeftRef.current
3416
- }, containerElements: [columnLeftRef.current] }),
3417
3545
  React__default["default"].createElement(ClickOutsideListener$1, { onClickOutside: handleClickOutside, target: columnLeftRef.current }),
3418
3546
  isCollapsed ? null : skipLink,
3419
3547
  showPanel ? ColumnLeftComponent : null,
@@ -4207,15 +4335,8 @@ var Popover = React.forwardRef(function (_a, ref) {
4207
4335
  attachIsolator();
4208
4336
  }, [popoverRef.current]);
4209
4337
  React.useEffect(function () {
4210
- if (show && popoverRef.current) {
4211
- // Find the first focusable element inside the container
4212
- var firstFocusableElement = popoverRef.current.querySelector(focusableSelector);
4213
- if (firstFocusableElement instanceof HTMLElement) {
4214
- firstFocusableElement.focus();
4215
- }
4216
- }
4217
4338
  targetElement === null || targetElement === void 0 ? void 0 : targetElement.setAttribute('aria-expanded', Boolean(show).toString());
4218
- }, [show, popoverRef.current]);
4339
+ }, [show]);
4219
4340
  React.useEffect(function () {
4220
4341
  var attrText = targetElement === null || targetElement === void 0 ? void 0 : targetElement.getAttribute('aria-controls');
4221
4342
  var hasPopupAttr = targetElement === null || targetElement === void 0 ? void 0 : targetElement.getAttribute('aria-haspopup');
@@ -4249,20 +4370,17 @@ var Popover = React.forwardRef(function (_a, ref) {
4249
4370
  callback: handleClosePopover,
4250
4371
  active: show
4251
4372
  }, [show]);
4373
+ useFocusTrap(popoverRef, { disabled: !show, returnFocus: true });
4252
4374
  if (!show || !isBrowser())
4253
4375
  return null;
4254
- return reactDom.createPortal(React__default["default"].createElement(FocusTrap__default["default"], { focusTrapOptions: {
4255
- allowOutsideClick: true,
4256
- fallbackFocus: '.Popover__borderLeft'
4257
- } },
4258
- React__default["default"].createElement(ClickOutsideListener$1, { onClickOutside: handleClickOutside },
4259
- React__default["default"].createElement(AnchoredOverlay, tslib.__assign({ id: id, className: classNames__default["default"]('Popover', "Popover--".concat(placement), className, {
4260
- 'Popover--hidden': !show,
4261
- 'Popover--prompt': variant === 'prompt'
4262
- }), ref: popoverRef, role: "dialog", target: target, open: show, placement: initialPlacement, onPlacementChange: setPlacement, offset: 8 }, additionalProps, props),
4263
- React__default["default"].createElement("div", { className: "Popover__popoverArrow" }),
4264
- React__default["default"].createElement("div", { className: "Popover__borderLeft" }),
4265
- variant === 'prompt' ? (React__default["default"].createElement(PromptPopoverContent, { applyButtonText: applyButtonText, onApply: onApply, closeButtonText: closeButtonText, infoText: infoText || '', onClose: handleClosePopover, infoTextId: "".concat(id, "-label") })) : (children)))), (portal && 'current' in portal ? portal.current : portal) ||
4376
+ return reactDom.createPortal(React__default["default"].createElement(ClickOutsideListener$1, { onClickOutside: handleClickOutside },
4377
+ React__default["default"].createElement(AnchoredOverlay, tslib.__assign({ id: id, className: classNames__default["default"]('Popover', "Popover--".concat(placement), className, {
4378
+ 'Popover--hidden': !show,
4379
+ 'Popover--prompt': variant === 'prompt'
4380
+ }), ref: popoverRef, role: "dialog", target: target, open: show, placement: initialPlacement, onPlacementChange: setPlacement, offset: 8 }, additionalProps, props),
4381
+ React__default["default"].createElement("div", { className: "Popover__popoverArrow" }),
4382
+ React__default["default"].createElement("div", { className: "Popover__borderLeft" }),
4383
+ variant === 'prompt' ? (React__default["default"].createElement(PromptPopoverContent, { applyButtonText: applyButtonText, onApply: onApply, closeButtonText: closeButtonText, infoText: infoText || '', onClose: handleClosePopover, infoTextId: "".concat(id, "-label") })) : (children))), (portal && 'current' in portal ? portal.current : portal) ||
4266
4384
  // Dependent on "isBrowser" check above:
4267
4385
  // eslint-disable-next-line ssr-friendly/no-dom-globals-in-react-fc
4268
4386
  document.body);
@@ -4330,7 +4448,6 @@ var Drawer = React.forwardRef(function (_a, ref) {
4330
4448
  var children = _a.children, className = _a.className, position = _a.position, _b = _a.open, open = _b === void 0 ? false : _b, _c = _a.behavior, behavior = _c === void 0 ? 'modal' : _c, _d = _a.focusOptions, focusOptions = _d === void 0 ? {} : _d, portal = _a.portal, onClose = _a.onClose, style = _a.style, props = tslib.__rest(_a, ["children", "className", "position", "open", "behavior", "focusOptions", "portal", "onClose", "style"]);
4331
4449
  var drawerRef = useSharedRef(ref);
4332
4450
  var openRef = React.useRef(!!open);
4333
- var previousActiveElementRef = React.useRef(null);
4334
4451
  var focusInitial = focusOptions.initialFocus, focusReturn = focusOptions.returnFocus;
4335
4452
  var _e = tslib.__read(React.useState(!!open), 2), isTransitioning = _e[0], setIsTransitioning = _e[1];
4336
4453
  var isModal = behavior === 'modal';
@@ -4370,60 +4487,27 @@ var Drawer = React.forwardRef(function (_a, ref) {
4370
4487
  isolator.deactivate();
4371
4488
  };
4372
4489
  }, [isModal, open]);
4373
- React.useLayoutEffect(function () {
4374
- var _a, _b, _c;
4375
- if (open) {
4376
- previousActiveElementRef.current =
4377
- document.activeElement;
4378
- var initialFocusElement = resolveElement(focusInitial);
4379
- if (initialFocusElement) {
4380
- initialFocusElement.focus();
4381
- }
4382
- else {
4383
- var focusable = (_a = drawerRef.current) === null || _a === void 0 ? void 0 : _a.querySelector(focusableSelector);
4384
- if (focusable) {
4385
- focusable.focus();
4386
- }
4387
- else {
4388
- // fallback focus
4389
- (_b = drawerRef.current) === null || _b === void 0 ? void 0 : _b.focus();
4390
- }
4391
- }
4392
- }
4393
- else if (previousActiveElementRef.current) {
4394
- var returnFocusElement = resolveElement(focusReturn);
4395
- if (returnFocusElement) {
4396
- returnFocusElement.focus();
4397
- }
4398
- else {
4399
- // fallback focus
4400
- (_c = previousActiveElementRef.current) === null || _c === void 0 ? void 0 : _c.focus();
4401
- }
4402
- }
4403
- }, [open, focusInitial, focusReturn]);
4404
4490
  useEscapeKey({ callback: handleClose, active: open, defaultPrevented: true }, [onClose]);
4405
4491
  // istanbul ignore next
4406
4492
  if (!isBrowser()) {
4407
4493
  return null;
4408
4494
  }
4495
+ useFocusTrap(drawerRef, {
4496
+ disabled: !isModal || !open,
4497
+ initialFocusElement: focusInitial || drawerRef,
4498
+ returnFocus: true,
4499
+ returnFocusElement: focusReturn
4500
+ });
4409
4501
  var portalElement = resolveElement(portal);
4410
4502
  return reactDom.createPortal(React__default["default"].createElement(React__default["default"].Fragment, null,
4411
4503
  React__default["default"].createElement(ClickOutsideListener$1, { onClickOutside: handleClose, mouseEvent: open ? undefined : false, touchEvent: open ? undefined : false, target: drawerRef },
4412
- React__default["default"].createElement(FocusTrap__default["default"], { active: !!isModal && !!open, focusTrapOptions: {
4413
- allowOutsideClick: true,
4414
- escapeDeactivates: false,
4415
- clickOutsideDeactivates: false,
4416
- initialFocus: false,
4417
- setReturnFocus: false,
4418
- fallbackFocus: function () { return drawerRef.current; }
4419
- } },
4420
- React__default["default"].createElement("div", tslib.__assign({ ref: drawerRef, className: classNames__default["default"](className, 'Drawer', {
4421
- 'Drawer--open': !!open,
4422
- 'Drawer--top': position === 'top',
4423
- 'Drawer--bottom': position === 'bottom',
4424
- 'Drawer--left': position === 'left',
4425
- 'Drawer--right': position === 'right'
4426
- }), "aria-hidden": !open || undefined, style: tslib.__assign({ visibility: !open && !isTransitioning ? 'hidden' : undefined }, style), tabIndex: open ? -1 : undefined }, props), children))),
4504
+ React__default["default"].createElement("div", tslib.__assign({ ref: drawerRef, className: classNames__default["default"](className, 'Drawer', {
4505
+ 'Drawer--open': !!open,
4506
+ 'Drawer--top': position === 'top',
4507
+ 'Drawer--bottom': position === 'bottom',
4508
+ 'Drawer--left': position === 'left',
4509
+ 'Drawer--right': position === 'right'
4510
+ }), "aria-hidden": !open || undefined, style: tslib.__assign({ visibility: !open && !isTransitioning ? 'hidden' : undefined }, style), tabIndex: open ? -1 : undefined }, props), children)),
4427
4511
  React__default["default"].createElement(Scrim, { show: !!open && !!isModal })), portalElement ||
4428
4512
  (
4429
4513
  // eslint-disable-next-line ssr-friendly/no-dom-globals-in-react-fc
package/lib/types.d.ts CHANGED
@@ -11,3 +11,4 @@ export declare namespace Cauldron {
11
11
  * Explicit equivalent of Exclude<ReactNode, boolean | null | undefined>
12
12
  */
13
13
  export type ContentNode = string | number | ReactPortal | ReactElement;
14
+ export type ElementOrRef<E extends Element> = E | React.RefObject<E> | React.MutableRefObject<E>;
@@ -1,6 +1,6 @@
1
- import type { RefObject, MutableRefObject } from 'react';
1
+ import type { ElementOrRef } from '../types';
2
2
  /**
3
3
  * When an element can be passed as a value that is either an element or an
4
4
  * elementRef, this will resolve the property down to the resulting element
5
5
  */
6
- export default function resolveElement<T extends Element = Element>(elementOrRef: T | RefObject<T> | MutableRefObject<T> | undefined): T | null;
6
+ export default function resolveElement<T extends Element = Element>(elementOrRef: ElementOrRef<T> | undefined): T | null;
@@ -0,0 +1,34 @@
1
+ import type { ElementOrRef } from '../types';
2
+ type FocusTrapMetadata = {
3
+ targetElement: Element;
4
+ lastFocusedElement: HTMLElement | null;
5
+ suspended: boolean;
6
+ };
7
+ type FocusTrap = {
8
+ initialFocusElement: HTMLElement | null;
9
+ destroy: () => void;
10
+ } & FocusTrapMetadata;
11
+ export default function useFocusTrap<TargetElement extends Element = Element, FocusElement extends HTMLElement = HTMLElement>(target: ElementOrRef<TargetElement>, options?: {
12
+ /**
13
+ * When set to false, deactivates the focus trap. This can be necessary if
14
+ * a component needs to conditionally manage focus traps.
15
+ */
16
+ disabled?: boolean;
17
+ /**
18
+ * When the trap is activated, an optional custom element or ref
19
+ * can be provided to override the default initial focus element behavior.
20
+ */
21
+ initialFocusElement?: ElementOrRef<FocusElement>;
22
+ /**
23
+ * When set to true and the trap is deactivated, this will return focus
24
+ * back to the original active element or the return focus element if
25
+ * provided.
26
+ */
27
+ returnFocus?: boolean;
28
+ /**
29
+ * When the trap is deactivated, an optional custom element or ref
30
+ * can be provided to override the default active element behavior.
31
+ */
32
+ returnFocusElement?: ElementOrRef<FocusElement>;
33
+ }): React.RefObject<Readonly<FocusTrap>>;
34
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deque/cauldron-react",
3
- "version": "6.12.0-canary.67739754",
3
+ "version": "6.12.0-canary.775bed0e",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Fully accessible react components library for Deque Cauldron",
6
6
  "homepage": "https://cauldron.dequelabs.com/",
@@ -25,7 +25,6 @@
25
25
  "dependencies": {
26
26
  "@floating-ui/react-dom": "^2.1.2",
27
27
  "classnames": "^2.2.6",
28
- "focus-trap-react": "^10.2.3",
29
28
  "focusable": "^2.3.0",
30
29
  "keyname": "^0.1.0",
31
30
  "react-id-generator": "^3.0.1",