@deque/cauldron-react 6.7.0-canary.2d78ed57 → 6.7.0-canary.4be82cb3

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,15 @@
1
+ import React from 'react';
2
+ interface DrawerProps<T extends HTMLElement = HTMLElement> extends React.HTMLAttributes<HTMLDivElement> {
3
+ children: React.ReactNode;
4
+ position: 'top' | 'bottom' | 'left' | 'right';
5
+ open?: boolean;
6
+ behavior?: 'modal' | 'non-modal';
7
+ focusOptions?: {
8
+ initialFocus?: T | React.RefObject<T> | React.MutableRefObject<T>;
9
+ returnFocus?: T | React.RefObject<T> | React.MutableRefObject<T>;
10
+ };
11
+ onClose?: () => void;
12
+ portal?: React.RefObject<HTMLElement> | HTMLElement;
13
+ }
14
+ declare const Drawer: React.ForwardRefExoticComponent<DrawerProps<HTMLElement> & React.RefAttributes<HTMLDivElement>>;
15
+ export default Drawer;
package/lib/index.d.ts CHANGED
@@ -59,6 +59,7 @@ export { default as Popover } from './components/Popover';
59
59
  export { default as Timeline, TimelineItem } from './components/Timeline';
60
60
  export { default as TextEllipsis } from './components/TextEllipsis';
61
61
  export { default as CopyButton } from './components/CopyButton';
62
+ export { default as Drawer } from './components/Drawer';
62
63
  /**
63
64
  * Helpers / Utils
64
65
  */
package/lib/index.js CHANGED
@@ -791,6 +791,23 @@ var OptionsMenuWrapper = function (_a) {
791
791
  return (React__default["default"].createElement("div", tslib.__assign({ className: classNames__default["default"]('OptionsMenu', menuAlignment(align), className) }, other)));
792
792
  };
793
793
 
794
+ /**
795
+ * When an element can be passed as a value that is either an element or an
796
+ * elementRef, this will resolve the property down to the resulting element
797
+ */
798
+ function resolveElement(elementOrRef) {
799
+ if (elementOrRef instanceof Element) {
800
+ return elementOrRef;
801
+ }
802
+ if (elementOrRef &&
803
+ typeof elementOrRef === 'object' &&
804
+ 'current' in elementOrRef &&
805
+ elementOrRef.current instanceof Element) {
806
+ return elementOrRef.current;
807
+ }
808
+ return null;
809
+ }
810
+
794
811
  function ClickOutsideListener(_a, ref) {
795
812
  var children = _a.children, _b = _a.mouseEvent, mouseEvent = _b === void 0 ? 'click' : _b, _c = _a.touchEvent, touchEvent = _c === void 0 ? 'touchend' : _c, target = _a.target, _d = _a.onClickOutside, onClickOutside = _d === void 0 ? function () { return null; } : _d;
796
813
  var childElementRef = React.useRef();
@@ -799,11 +816,9 @@ function ClickOutsideListener(_a, ref) {
799
816
  return;
800
817
  }
801
818
  var eventTarget = event.target;
802
- if (target && (target instanceof HTMLElement || 'current' in target)) {
803
- var elementTarget = target instanceof HTMLElement ? target : target.current;
804
- if (!(elementTarget === null || elementTarget === void 0 ? void 0 : elementTarget.contains(eventTarget))) {
805
- onClickOutside(event);
806
- }
819
+ var elementTarget = resolveElement(target);
820
+ if (target && !(elementTarget === null || elementTarget === void 0 ? void 0 : elementTarget.contains(eventTarget))) {
821
+ onClickOutside(event);
807
822
  // If a target is passed in via a prop, we defer to utilizing that
808
823
  // target instead of a child element target
809
824
  return;
@@ -1648,6 +1663,46 @@ function hasIdRef(ids, id) {
1648
1663
  return idRefs(ids).has(id);
1649
1664
  }
1650
1665
 
1666
+ var isEscapeKey = function (event) {
1667
+ return event.key === 'Escape' || event.key === 'Esc' || event.keyCode === 27;
1668
+ };
1669
+ /**
1670
+ * When a component needs to implement an escape handler, such as in modal
1671
+ * dialogs, useEscapeKey will handle the events and call the provided callback
1672
+ * handler when an escape key event has been fired.
1673
+ *
1674
+ * @example
1675
+ * useEscapeKey(() => close())
1676
+ */
1677
+ function useEscapeKey(options, dependencies) {
1678
+ if (dependencies === void 0) { dependencies = []; }
1679
+ var callback = options.callback;
1680
+ var event = options.event || 'keyup';
1681
+ var target = resolveElement(options.target) || document.body;
1682
+ var active = typeof options.active === 'boolean' ? options.active : true;
1683
+ React.useEffect(function () {
1684
+ var eventListener = function (event) {
1685
+ if (isEscapeKey(event) &&
1686
+ (options.defaultPrevented ? !event.defaultPrevented : true)) {
1687
+ callback(event);
1688
+ }
1689
+ };
1690
+ if (active) {
1691
+ target === null || target === void 0 ? void 0 : target.addEventListener(event, eventListener, options.capture);
1692
+ }
1693
+ return function () {
1694
+ target === null || target === void 0 ? void 0 : target.removeEventListener(event, eventListener, options.capture);
1695
+ };
1696
+ }, tslib.__spreadArray([
1697
+ active,
1698
+ callback,
1699
+ event,
1700
+ target,
1701
+ options.capture,
1702
+ options.defaultPrevented
1703
+ ], tslib.__read(dependencies), false));
1704
+ }
1705
+
1651
1706
  var TIP_HIDE_DELAY = 100;
1652
1707
  // fires a custom "cauldron:tooltip:show" / "cauldron:tooltip:hide" event
1653
1708
  // to allow projects using cauldron to hook into when a tooltip is shown/hidden
@@ -1736,30 +1791,14 @@ function Tooltip(_a) {
1736
1791
  attributes.popper['data-popper-placement']) ||
1737
1792
  initialPlacement;
1738
1793
  // Only listen to key ups when the tooltip is visible
1739
- React.useEffect(function () {
1740
- var handleEscape = function (event) {
1741
- if (event.key === 'Escape' ||
1742
- event.key === 'Esc' ||
1743
- event.keyCode === 27) {
1744
- event.preventDefault();
1745
- setShowTooltip(false);
1746
- }
1747
- };
1748
- var targetElement = document.body;
1749
- if (showTooltip && typeof showProp !== 'boolean') {
1750
- targetElement.addEventListener('keyup', handleEscape, { capture: true });
1751
- }
1752
- else {
1753
- targetElement.removeEventListener('keyup', handleEscape, {
1754
- capture: true
1755
- });
1756
- }
1757
- return function () {
1758
- targetElement.removeEventListener('keyup', handleEscape, {
1759
- capture: true
1760
- });
1761
- };
1762
- }, [showTooltip, showProp]);
1794
+ useEscapeKey({
1795
+ callback: function (event) {
1796
+ event.preventDefault();
1797
+ setShowTooltip(false);
1798
+ },
1799
+ capture: true,
1800
+ active: showTooltip && typeof showProp !== 'boolean'
1801
+ }, [setShowTooltip]);
1763
1802
  // Handle hover and focus events for the targetElement
1764
1803
  React.useEffect(function () {
1765
1804
  if (typeof showProp !== 'boolean') {
@@ -3275,25 +3314,10 @@ var TwoColumnPanel = React.forwardRef(function (_a, ref) {
3275
3314
  mediaQueryList.removeEventListener('change', listener);
3276
3315
  };
3277
3316
  }, []);
3278
- React.useEffect(function () {
3279
- var handleEscape = function (event) {
3280
- if (event.key === 'Escape' ||
3281
- event.key === 'Esc' ||
3282
- event.keyCode === 27) {
3283
- setCollapsed(true);
3284
- }
3285
- };
3286
- var targetElement = document.body;
3287
- if (isFocusTrap) {
3288
- targetElement.addEventListener('keyup', handleEscape);
3289
- }
3290
- else {
3291
- targetElement.removeEventListener('keyup', handleEscape);
3292
- }
3293
- return function () {
3294
- targetElement.removeEventListener('keyup', handleEscape);
3295
- };
3296
- }, [isFocusTrap]);
3317
+ useEscapeKey({
3318
+ callback: function () { return setCollapsed(true); },
3319
+ active: isFocusTrap
3320
+ }, [setCollapsed]);
3297
3321
  var handleClickOutside = function () {
3298
3322
  if (!isCollapsed && isFocusTrap) {
3299
3323
  setCollapsed(true);
@@ -4076,24 +4100,6 @@ var Popover = React.forwardRef(function (_a, ref) {
4076
4100
  }
4077
4101
  targetElement === null || targetElement === void 0 ? void 0 : targetElement.setAttribute('aria-expanded', Boolean(show).toString());
4078
4102
  }, [show, popoverRef.current]);
4079
- React.useEffect(function () {
4080
- var handleEscape = function (event) {
4081
- if (event.key === 'Escape' ||
4082
- event.key === 'Esc' ||
4083
- event.keyCode === 27) {
4084
- handleClosePopover();
4085
- }
4086
- };
4087
- if (show) {
4088
- document.body.addEventListener('keyup', handleEscape);
4089
- }
4090
- else {
4091
- document.body.removeEventListener('keyup', handleEscape);
4092
- }
4093
- return function () {
4094
- document.body.removeEventListener('keyup', handleEscape);
4095
- };
4096
- }, [show]);
4097
4103
  React.useEffect(function () {
4098
4104
  var attrText = targetElement === null || targetElement === void 0 ? void 0 : targetElement.getAttribute('aria-controls');
4099
4105
  var hasPopupAttr = targetElement === null || targetElement === void 0 ? void 0 : targetElement.getAttribute('aria-haspopup');
@@ -4120,6 +4126,10 @@ var Popover = React.forwardRef(function (_a, ref) {
4120
4126
  onClose();
4121
4127
  }
4122
4128
  };
4129
+ useEscapeKey({
4130
+ callback: handleClosePopover,
4131
+ active: show
4132
+ }, [show]);
4123
4133
  if (!show || !isBrowser())
4124
4134
  return null;
4125
4135
  return reactDom.createPortal(React__default["default"].createElement(FocusTrap__default["default"], { focusTrapOptions: {
@@ -4197,6 +4207,111 @@ var TextEllipsis = React__default["default"].forwardRef(function (_a, ref) {
4197
4207
  });
4198
4208
  TextEllipsis.displayName = 'TextEllipsis';
4199
4209
 
4210
+ var Drawer = React.forwardRef(function (_a, ref) {
4211
+ 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"]);
4212
+ var drawerRef = useSharedRef(ref);
4213
+ var openRef = React.useRef(!!open);
4214
+ var previousActiveElementRef = React.useRef(null);
4215
+ var focusInitial = focusOptions.initialFocus, focusReturn = focusOptions.returnFocus;
4216
+ var _e = tslib.__read(React.useState(!!open), 2), isTransitioning = _e[0], setIsTransitioning = _e[1];
4217
+ var isModal = behavior === 'modal';
4218
+ var handleClose = React.useCallback(function () {
4219
+ // istanbul ignore next
4220
+ if (open && typeof onClose === 'function') {
4221
+ onClose();
4222
+ }
4223
+ }, [open, onClose]);
4224
+ React.useEffect(function () {
4225
+ // jsdom does not trigger transitionend event
4226
+ // istanbul ignore next
4227
+ var transitionEndHandler = function () { return setIsTransitioning(false); };
4228
+ document.addEventListener('transitionend', transitionEndHandler);
4229
+ return function () {
4230
+ document.removeEventListener('transitionend', transitionEndHandler);
4231
+ };
4232
+ }, [setIsTransitioning]);
4233
+ React.useEffect(function () {
4234
+ if (openRef.current !== open) {
4235
+ setIsTransitioning(true);
4236
+ }
4237
+ openRef.current = open;
4238
+ }, [open, setIsTransitioning]);
4239
+ React.useEffect(function () {
4240
+ if (!isModal) {
4241
+ return;
4242
+ }
4243
+ var isolator = new AriaIsolate(drawerRef.current);
4244
+ if (open) {
4245
+ isolator.activate();
4246
+ }
4247
+ else {
4248
+ isolator.deactivate();
4249
+ }
4250
+ return function () {
4251
+ isolator.deactivate();
4252
+ };
4253
+ }, [isModal, open]);
4254
+ React.useLayoutEffect(function () {
4255
+ var _a, _b, _c;
4256
+ if (open) {
4257
+ previousActiveElementRef.current =
4258
+ document.activeElement;
4259
+ var initialFocusElement = resolveElement(focusInitial);
4260
+ if (initialFocusElement) {
4261
+ initialFocusElement.focus();
4262
+ }
4263
+ else {
4264
+ var focusable = (_a = drawerRef.current) === null || _a === void 0 ? void 0 : _a.querySelector(focusableSelector);
4265
+ if (focusable) {
4266
+ focusable.focus();
4267
+ }
4268
+ else {
4269
+ // fallback focus
4270
+ (_b = drawerRef.current) === null || _b === void 0 ? void 0 : _b.focus();
4271
+ }
4272
+ }
4273
+ }
4274
+ else if (previousActiveElementRef.current) {
4275
+ var returnFocusElement = resolveElement(focusReturn);
4276
+ if (returnFocusElement) {
4277
+ returnFocusElement.focus();
4278
+ }
4279
+ else {
4280
+ // fallback focus
4281
+ (_c = previousActiveElementRef.current) === null || _c === void 0 ? void 0 : _c.focus();
4282
+ }
4283
+ }
4284
+ }, [open, focusInitial, focusReturn]);
4285
+ useEscapeKey({ callback: handleClose, active: open, defaultPrevented: true }, [onClose]);
4286
+ // istanbul ignore next
4287
+ if (!isBrowser()) {
4288
+ return null;
4289
+ }
4290
+ var portalElement = resolveElement(portal);
4291
+ return reactDom.createPortal(React__default["default"].createElement(React__default["default"].Fragment, null,
4292
+ React__default["default"].createElement(ClickOutsideListener$1, { onClickOutside: handleClose, mouseEvent: open ? undefined : false, touchEvent: open ? undefined : false, target: drawerRef },
4293
+ React__default["default"].createElement(FocusTrap__default["default"], { active: !!isModal && !!open, focusTrapOptions: {
4294
+ allowOutsideClick: true,
4295
+ escapeDeactivates: false,
4296
+ clickOutsideDeactivates: false,
4297
+ initialFocus: false,
4298
+ setReturnFocus: false,
4299
+ fallbackFocus: function () { return drawerRef.current; }
4300
+ } },
4301
+ React__default["default"].createElement("div", tslib.__assign({ ref: drawerRef, className: classNames__default["default"](className, 'Drawer', {
4302
+ 'Drawer--open': !!open,
4303
+ 'Drawer--top': position === 'top',
4304
+ 'Drawer--bottom': position === 'bottom',
4305
+ 'Drawer--left': position === 'left',
4306
+ 'Drawer--right': position === 'right'
4307
+ }), "aria-hidden": !open || undefined, style: tslib.__assign({ visibility: !open && !isTransitioning ? 'hidden' : undefined }, style), tabIndex: open ? -1 : undefined }, props), children))),
4308
+ React__default["default"].createElement(Scrim, { show: !!open && !!isModal })), portalElement ||
4309
+ (
4310
+ // eslint-disable-next-line ssr-friendly/no-dom-globals-in-react-fc
4311
+ document === null || document === void 0 ? void 0 : document.body));
4312
+ });
4313
+ Drawer.displayName = 'Drawer';
4314
+
4200
4315
  var LIGHT_THEME_CLASS = 'cauldron--theme-light';
4201
4316
  var DARK_THEME_CLASS = 'cauldron--theme-dark';
4202
4317
  var ThemeContext = React.createContext({
@@ -4299,6 +4414,7 @@ exports.DescriptionTerm = DescriptionTerm;
4299
4414
  exports.Dialog = Dialog;
4300
4415
  exports.DialogContent = DialogContent;
4301
4416
  exports.DialogFooter = DialogFooter;
4417
+ exports.Drawer = Drawer;
4302
4418
  exports.ExpandCollapsePanel = ExpandCollapsePanel;
4303
4419
  exports.FieldWrap = FieldWrap;
4304
4420
  exports.Icon = Icon;
@@ -0,0 +1,6 @@
1
+ import type { RefObject, MutableRefObject } from 'react';
2
+ /**
3
+ * When an element can be passed as a value that is either an element or an
4
+ * elementRef, this will resolve the property down to the resulting element
5
+ */
6
+ export default function resolveElement<T extends Element = Element>(elementOrRef: T | RefObject<T> | MutableRefObject<T> | undefined): T | null;
@@ -0,0 +1,17 @@
1
+ import { type DependencyList } from 'react';
2
+ /**
3
+ * When a component needs to implement an escape handler, such as in modal
4
+ * dialogs, useEscapeKey will handle the events and call the provided callback
5
+ * handler when an escape key event has been fired.
6
+ *
7
+ * @example
8
+ * useEscapeKey(() => close())
9
+ */
10
+ export default function useEscapeKey<T extends HTMLElement = HTMLElement>(options: {
11
+ active?: boolean;
12
+ callback: (event: KeyboardEvent) => void;
13
+ event?: 'keydown' | 'keypress' | 'keyup';
14
+ target?: T | React.RefObject<T> | React.MutableRefObject<T>;
15
+ defaultPrevented?: boolean;
16
+ capture?: boolean;
17
+ }, dependencies?: DependencyList): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deque/cauldron-react",
3
- "version": "6.7.0-canary.2d78ed57",
3
+ "version": "6.7.0-canary.4be82cb3",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Fully accessible react components library for Deque Cauldron",
6
6
  "homepage": "https://cauldron.dequelabs.com/",