@reach/dialog
Advanced tools
Comparing version 0.4.0-beta.0 to 0.4.0
288
es/index.js
@@ -6,3 +6,2 @@ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } | ||
import React from "react"; | ||
import Component from "@reach/component-component"; | ||
import Portal from "@reach/portal"; | ||
@@ -12,7 +11,148 @@ import { checkStyles, wrapEvent, assignRef } from "@reach/utils"; | ||
import { RemoveScroll } from "react-remove-scroll"; | ||
import { func, bool } from "prop-types"; | ||
import { string, func, bool } from "prop-types"; | ||
var createAriaHider = function createAriaHider(dialogNode) { | ||
var noop = function noop() {}; //////////////////////////////////////////////////////////////////////////////// | ||
export var DialogOverlay = React.forwardRef(function DialogOverlay(_ref, forwardedRef) { | ||
var _ref$isOpen = _ref.isOpen, | ||
isOpen = _ref$isOpen === void 0 ? true : _ref$isOpen, | ||
props = _objectWithoutPropertiesLoose(_ref, ["isOpen"]); | ||
var ownRef = React.useRef(null); | ||
var ref = forwardedRef || ownRef; | ||
React.useEffect(function () { | ||
checkStyles("dialog"); | ||
}, []); | ||
return isOpen ? React.createElement(Portal, { | ||
"data-reach-dialog-wrapper": true | ||
}, React.createElement(DialogInner, _extends({ | ||
ref: ref | ||
}, props))) : null; | ||
}); | ||
if (process.env.NODE_ENV !== "production") { | ||
DialogOverlay.propTypes = { | ||
initialFocusRef: function initialFocusRef() {} | ||
}; | ||
DialogOverlay.displayName = "DialogOverlay"; | ||
} //////////////////////////////////////////////////////////////////////////////// | ||
var DialogInner = React.forwardRef(function DialogInner(_ref2, forwardedRef) { | ||
var initialFocusRef = _ref2.initialFocusRef, | ||
onClick = _ref2.onClick, | ||
_ref2$onDismiss = _ref2.onDismiss, | ||
onDismiss = _ref2$onDismiss === void 0 ? noop : _ref2$onDismiss, | ||
onMouseDown = _ref2.onMouseDown, | ||
onKeyDown = _ref2.onKeyDown, | ||
props = _objectWithoutPropertiesLoose(_ref2, ["initialFocusRef", "onClick", "onDismiss", "onMouseDown", "onKeyDown"]); | ||
var mouseDownTarget = React.useRef(null); | ||
var overlayNode = React.useRef(null); | ||
var ref = useForkedRef(overlayNode, forwardedRef); | ||
React.useEffect(function () { | ||
return createAriaHider(forwardedRef.current); | ||
}, [forwardedRef]); | ||
return React.createElement(FocusLock, { | ||
autoFocus: true, | ||
returnFocus: true, | ||
onActivation: function onActivation() { | ||
if (initialFocusRef && initialFocusRef.current) { | ||
initialFocusRef.current.focus(); | ||
} | ||
} | ||
}, React.createElement(RemoveScroll, null, React.createElement("div", _extends({ | ||
"data-reach-dialog-overlay": true, | ||
onClick: wrapEvent(onClick, function (event) { | ||
if (mouseDownTarget.current === event.target) { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
}), | ||
onMouseDown: wrapEvent(onMouseDown, function (event) { | ||
mouseDownTarget.current = event.target; | ||
}), | ||
onKeyDown: wrapEvent(onKeyDown, function (event) { | ||
if (event.key === "Escape") { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
}), | ||
ref: ref | ||
}, props)))); | ||
}); | ||
if (process.env.NODE_ENV !== "production") { | ||
DialogOverlay.displayName = "DialogOverlay"; | ||
} //////////////////////////////////////////////////////////////////////////////// | ||
export var DialogContent = React.forwardRef(function DialogContent(_ref3, forwardedRef) { | ||
var onClick = _ref3.onClick, | ||
onKeyDown = _ref3.onKeyDown, | ||
props = _objectWithoutPropertiesLoose(_ref3, ["onClick", "onKeyDown"]); | ||
return React.createElement("div", _extends({ | ||
role: "dialog", | ||
"aria-modal": "true", | ||
"data-reach-dialog-content": true, | ||
tabIndex: "-1", | ||
onClick: wrapEvent(onClick, function (event) { | ||
event.stopPropagation(); | ||
}), | ||
ref: forwardedRef | ||
}, props)); | ||
}); | ||
if (process.env.NODE_ENV !== "production") { | ||
DialogContent.propTypes = { | ||
"aria-label": ariaLabelType, | ||
"aria-labelledby": ariaLabelType | ||
}; | ||
DialogContent.displayName = "DialogContent"; | ||
} //////////////////////////////////////////////////////////////////////////////// | ||
export var Dialog = React.forwardRef(function Dialog(_ref4, forwardedRef) { | ||
var isOpen = _ref4.isOpen, | ||
_ref4$onDismiss = _ref4.onDismiss, | ||
onDismiss = _ref4$onDismiss === void 0 ? noop : _ref4$onDismiss, | ||
initialFocusRef = _ref4.initialFocusRef, | ||
props = _objectWithoutPropertiesLoose(_ref4, ["isOpen", "onDismiss", "initialFocusRef"]); | ||
var ownRef = React.useRef(null); | ||
var ref = forwardedRef || ownRef; | ||
return React.createElement(DialogOverlay, { | ||
isOpen: isOpen, | ||
onDismiss: onDismiss, | ||
initialFocusRef: initialFocusRef | ||
}, React.createElement(DialogContent, _extends({ | ||
ref: ref | ||
}, props))); | ||
}); | ||
if (process.env.NODE_ENV !== "production") { | ||
Dialog.propTypes = { | ||
isOpen: bool, | ||
onDismiss: func, | ||
"aria-label": ariaLabelType, | ||
"aria-labelledby": ariaLabelType | ||
}; | ||
Dialog.displayName = "Dialog"; | ||
} //////////////////////////////////////////////////////////////////////////////// | ||
function createAriaHider(dialogNode) { | ||
var originalValues = []; | ||
var rootNodes = []; | ||
if (!dialogNode) { | ||
if (process.env.NODE_ENV !== "production") { | ||
console.warn("A ref has not yet been attached to a dialog node when attempting to call `createAriaHider`."); | ||
} | ||
return noop; | ||
} | ||
Array.prototype.forEach.call(document.querySelectorAll("body > *"), function (node) { | ||
@@ -47,121 +187,41 @@ var portalNode = dialogNode.parentNode.parentNode.parentNode; | ||
}; | ||
}; | ||
} // TODO: Remove and import from @reach/utils once it's been added to the package | ||
var k = function k() {}; | ||
var checkDialogStyles = function checkDialogStyles() { | ||
return checkStyles("dialog"); | ||
}; | ||
function useForkedRef() { | ||
for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) { | ||
refs[_key] = arguments[_key]; | ||
} | ||
var portalDidMount = function portalDidMount(refs) { | ||
refs.disposeAriaHider = createAriaHider(refs.overlayNode); | ||
}; | ||
return React.useMemo(function () { | ||
if (refs.every(function (ref) { | ||
return ref == null; | ||
})) { | ||
return null; | ||
} | ||
var contentWillUnmount = function contentWillUnmount(_ref) { | ||
var refs = _ref.refs; | ||
refs.disposeAriaHider(); | ||
}; // eslint-disable-next-line no-unused-vars | ||
return function (node) { | ||
refs.forEach(function (ref) { | ||
assignRef(ref, node); | ||
}); | ||
}; // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, refs); | ||
} | ||
function ariaLabelType(props, name, compName) { | ||
var details = "\nSee https://www.w3.org/TR/wai-aria/#aria-label for details."; | ||
var FocusContext = React.createContext(); | ||
var DialogOverlay = React.forwardRef(function (_ref2, forwardedRef) { | ||
var _ref2$isOpen = _ref2.isOpen, | ||
isOpen = _ref2$isOpen === void 0 ? true : _ref2$isOpen, | ||
_ref2$onDismiss = _ref2.onDismiss, | ||
onDismiss = _ref2$onDismiss === void 0 ? k : _ref2$onDismiss, | ||
onMouseDown = _ref2.onMouseDown, | ||
initialFocusRef = _ref2.initialFocusRef, | ||
onClick = _ref2.onClick, | ||
onKeyDown = _ref2.onKeyDown, | ||
props = _objectWithoutPropertiesLoose(_ref2, ["isOpen", "onDismiss", "onMouseDown", "initialFocusRef", "onClick", "onKeyDown"]); | ||
if (!props["aria-label"] && !props["aria-labelledby"]) { | ||
return new Error("A <" + compName + "> must have either an `aria-label` or `aria-labelledby` prop.\n " + details); | ||
} | ||
return React.createElement(Component, { | ||
didMount: checkDialogStyles | ||
}, isOpen ? React.createElement(Portal, { | ||
"data-reach-dialog-wrapper": true | ||
}, React.createElement(Component, { | ||
refs: { | ||
overlayNode: null, | ||
mouseDownTarget: null | ||
}, | ||
didMount: function didMount(_ref3) { | ||
var refs = _ref3.refs; | ||
portalDidMount(refs); | ||
}, | ||
willUnmount: contentWillUnmount | ||
}, function (_ref4) { | ||
var refs = _ref4.refs; | ||
return React.createElement(FocusLock, { | ||
returnFocus: true, | ||
onActivation: function onActivation() { | ||
if (initialFocusRef) { | ||
initialFocusRef.current.focus(); | ||
} | ||
} | ||
}, React.createElement(RemoveScroll, null, React.createElement("div", _extends({ | ||
"data-reach-dialog-overlay": true, | ||
onClick: wrapEvent(onClick, function (event) { | ||
if (refs.mouseDownTarget === event.target) { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
}), | ||
onMouseDown: wrapEvent(onMouseDown, function (event) { | ||
refs.mouseDownTarget = event.target; | ||
}), | ||
onKeyDown: wrapEvent(onKeyDown, function (event) { | ||
if (event.key === "Escape") { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
}), | ||
ref: function ref(node) { | ||
refs.overlayNode = node; | ||
assignRef(forwardedRef, node); | ||
} | ||
}, props)))); | ||
})) : null); | ||
}); | ||
DialogOverlay.propTypes = { | ||
initialFocusRef: function initialFocusRef() {} | ||
}; | ||
if (props["aria-label"] && props["aria-labelledby"]) { | ||
return new Error("You provided both `aria-label` and `aria-labelledby` props to a <" + compName + ">. If the a label for this component is visible on the screen, that label's component should be given a unique ID prop, and that ID should be passed as the `aria-labelledby` prop into <" + compName + ">. If the label cannot be determined programmatically from the content of the element, an alternative label should be provided as the `aria-label` prop, which will be used as an `aria-label` on the HTML tag." + details); | ||
} | ||
var stopPropagation = function stopPropagation(event) { | ||
return event.stopPropagation(); | ||
}; | ||
for (var _len2 = arguments.length, rest = new Array(_len2 > 3 ? _len2 - 3 : 0), _key2 = 3; _key2 < _len2; _key2++) { | ||
rest[_key2 - 3] = arguments[_key2]; | ||
} | ||
var DialogContent = React.forwardRef(function (_ref5, forwardedRef) { | ||
var onClick = _ref5.onClick, | ||
onKeyDown = _ref5.onKeyDown, | ||
props = _objectWithoutPropertiesLoose(_ref5, ["onClick", "onKeyDown"]); | ||
return React.createElement("div", _extends({ | ||
"aria-modal": "true", | ||
"data-reach-dialog-content": true, | ||
tabIndex: "-1", | ||
onClick: wrapEvent(onClick, stopPropagation), | ||
ref: function ref(node) { | ||
assignRef(forwardedRef, node); | ||
} | ||
}, props)); | ||
}); | ||
var Dialog = function Dialog(_ref6) { | ||
var isOpen = _ref6.isOpen, | ||
_ref6$onDismiss = _ref6.onDismiss, | ||
onDismiss = _ref6$onDismiss === void 0 ? k : _ref6$onDismiss, | ||
initialFocusRef = _ref6.initialFocusRef, | ||
props = _objectWithoutPropertiesLoose(_ref6, ["isOpen", "onDismiss", "initialFocusRef"]); | ||
return React.createElement(DialogOverlay, { | ||
isOpen: isOpen, | ||
onDismiss: onDismiss, | ||
initialFocusRef: initialFocusRef | ||
}, React.createElement(DialogContent, props)); | ||
}; | ||
Dialog.propTypes = { | ||
isOpen: bool, | ||
onDismiss: func | ||
}; | ||
export { DialogOverlay, DialogContent, Dialog }; | ||
return string.apply(void 0, [name, props, compName].concat(rest)); | ||
} |
306
index.js
@@ -8,4 +8,2 @@ "use strict"; | ||
var _componentComponent = _interopRequireDefault(require("@reach/component-component")); | ||
var _portal = _interopRequireDefault(require("@reach/portal")); | ||
@@ -27,5 +25,160 @@ | ||
var createAriaHider = function createAriaHider(dialogNode) { | ||
var noop = function noop() {}; //////////////////////////////////////////////////////////////////////////////// | ||
var DialogOverlay = _react["default"].forwardRef(function DialogOverlay(_ref, forwardedRef) { | ||
var _ref$isOpen = _ref.isOpen, | ||
isOpen = _ref$isOpen === void 0 ? true : _ref$isOpen, | ||
props = _objectWithoutPropertiesLoose(_ref, ["isOpen"]); | ||
var ownRef = _react["default"].useRef(null); | ||
var ref = forwardedRef || ownRef; | ||
_react["default"].useEffect(function () { | ||
(0, _utils.checkStyles)("dialog"); | ||
}, []); | ||
return isOpen ? _react["default"].createElement(_portal["default"], { | ||
"data-reach-dialog-wrapper": true | ||
}, _react["default"].createElement(DialogInner, _extends({ | ||
ref: ref | ||
}, props))) : null; | ||
}); | ||
exports.DialogOverlay = DialogOverlay; | ||
if (process.env.NODE_ENV !== "production") { | ||
DialogOverlay.propTypes = { | ||
initialFocusRef: function initialFocusRef() {} | ||
}; | ||
DialogOverlay.displayName = "DialogOverlay"; | ||
} //////////////////////////////////////////////////////////////////////////////// | ||
var DialogInner = _react["default"].forwardRef(function DialogInner(_ref2, forwardedRef) { | ||
var initialFocusRef = _ref2.initialFocusRef, | ||
onClick = _ref2.onClick, | ||
_ref2$onDismiss = _ref2.onDismiss, | ||
onDismiss = _ref2$onDismiss === void 0 ? noop : _ref2$onDismiss, | ||
onMouseDown = _ref2.onMouseDown, | ||
onKeyDown = _ref2.onKeyDown, | ||
props = _objectWithoutPropertiesLoose(_ref2, ["initialFocusRef", "onClick", "onDismiss", "onMouseDown", "onKeyDown"]); | ||
var mouseDownTarget = _react["default"].useRef(null); | ||
var overlayNode = _react["default"].useRef(null); | ||
var ref = useForkedRef(overlayNode, forwardedRef); | ||
_react["default"].useEffect(function () { | ||
return createAriaHider(forwardedRef.current); | ||
}, [forwardedRef]); | ||
return _react["default"].createElement(_reactFocusLock["default"], { | ||
autoFocus: true, | ||
returnFocus: true, | ||
onActivation: function onActivation() { | ||
if (initialFocusRef && initialFocusRef.current) { | ||
initialFocusRef.current.focus(); | ||
} | ||
} | ||
}, _react["default"].createElement(_reactRemoveScroll.RemoveScroll, null, _react["default"].createElement("div", _extends({ | ||
"data-reach-dialog-overlay": true, | ||
onClick: (0, _utils.wrapEvent)(onClick, function (event) { | ||
if (mouseDownTarget.current === event.target) { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
}), | ||
onMouseDown: (0, _utils.wrapEvent)(onMouseDown, function (event) { | ||
mouseDownTarget.current = event.target; | ||
}), | ||
onKeyDown: (0, _utils.wrapEvent)(onKeyDown, function (event) { | ||
if (event.key === "Escape") { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
}), | ||
ref: ref | ||
}, props)))); | ||
}); | ||
if (process.env.NODE_ENV !== "production") { | ||
DialogOverlay.displayName = "DialogOverlay"; | ||
} //////////////////////////////////////////////////////////////////////////////// | ||
var DialogContent = _react["default"].forwardRef(function DialogContent(_ref3, forwardedRef) { | ||
var onClick = _ref3.onClick, | ||
onKeyDown = _ref3.onKeyDown, | ||
props = _objectWithoutPropertiesLoose(_ref3, ["onClick", "onKeyDown"]); | ||
return _react["default"].createElement("div", _extends({ | ||
role: "dialog", | ||
"aria-modal": "true", | ||
"data-reach-dialog-content": true, | ||
tabIndex: "-1", | ||
onClick: (0, _utils.wrapEvent)(onClick, function (event) { | ||
event.stopPropagation(); | ||
}), | ||
ref: forwardedRef | ||
}, props)); | ||
}); | ||
exports.DialogContent = DialogContent; | ||
if (process.env.NODE_ENV !== "production") { | ||
DialogContent.propTypes = { | ||
"aria-label": ariaLabelType, | ||
"aria-labelledby": ariaLabelType | ||
}; | ||
DialogContent.displayName = "DialogContent"; | ||
} //////////////////////////////////////////////////////////////////////////////// | ||
var Dialog = _react["default"].forwardRef(function Dialog(_ref4, forwardedRef) { | ||
var isOpen = _ref4.isOpen, | ||
_ref4$onDismiss = _ref4.onDismiss, | ||
onDismiss = _ref4$onDismiss === void 0 ? noop : _ref4$onDismiss, | ||
initialFocusRef = _ref4.initialFocusRef, | ||
props = _objectWithoutPropertiesLoose(_ref4, ["isOpen", "onDismiss", "initialFocusRef"]); | ||
var ownRef = _react["default"].useRef(null); | ||
var ref = forwardedRef || ownRef; | ||
return _react["default"].createElement(DialogOverlay, { | ||
isOpen: isOpen, | ||
onDismiss: onDismiss, | ||
initialFocusRef: initialFocusRef | ||
}, _react["default"].createElement(DialogContent, _extends({ | ||
ref: ref | ||
}, props))); | ||
}); | ||
exports.Dialog = Dialog; | ||
if (process.env.NODE_ENV !== "production") { | ||
Dialog.propTypes = { | ||
isOpen: _propTypes.bool, | ||
onDismiss: _propTypes.func, | ||
"aria-label": ariaLabelType, | ||
"aria-labelledby": ariaLabelType | ||
}; | ||
Dialog.displayName = "Dialog"; | ||
} //////////////////////////////////////////////////////////////////////////////// | ||
function createAriaHider(dialogNode) { | ||
var originalValues = []; | ||
var rootNodes = []; | ||
if (!dialogNode) { | ||
if (process.env.NODE_ENV !== "production") { | ||
console.warn("A ref has not yet been attached to a dialog node when attempting to call `createAriaHider`."); | ||
} | ||
return noop; | ||
} | ||
Array.prototype.forEach.call(document.querySelectorAll("body > *"), function (node) { | ||
@@ -60,126 +213,41 @@ var portalNode = dialogNode.parentNode.parentNode.parentNode; | ||
}; | ||
}; | ||
} // TODO: Remove and import from @reach/utils once it's been added to the package | ||
var k = function k() {}; | ||
var checkDialogStyles = function checkDialogStyles() { | ||
return (0, _utils.checkStyles)("dialog"); | ||
}; | ||
function useForkedRef() { | ||
for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) { | ||
refs[_key] = arguments[_key]; | ||
} | ||
var portalDidMount = function portalDidMount(refs) { | ||
refs.disposeAriaHider = createAriaHider(refs.overlayNode); | ||
}; | ||
return _react["default"].useMemo(function () { | ||
if (refs.every(function (ref) { | ||
return ref == null; | ||
})) { | ||
return null; | ||
} | ||
var contentWillUnmount = function contentWillUnmount(_ref) { | ||
var refs = _ref.refs; | ||
refs.disposeAriaHider(); | ||
}; // eslint-disable-next-line no-unused-vars | ||
return function (node) { | ||
refs.forEach(function (ref) { | ||
(0, _utils.assignRef)(ref, node); | ||
}); | ||
}; // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, refs); | ||
} | ||
function ariaLabelType(props, name, compName) { | ||
var details = "\nSee https://www.w3.org/TR/wai-aria/#aria-label for details."; | ||
var FocusContext = _react["default"].createContext(); | ||
if (!props["aria-label"] && !props["aria-labelledby"]) { | ||
return new Error("A <" + compName + "> must have either an `aria-label` or `aria-labelledby` prop.\n " + details); | ||
} | ||
var DialogOverlay = _react["default"].forwardRef(function (_ref2, forwardedRef) { | ||
var _ref2$isOpen = _ref2.isOpen, | ||
isOpen = _ref2$isOpen === void 0 ? true : _ref2$isOpen, | ||
_ref2$onDismiss = _ref2.onDismiss, | ||
onDismiss = _ref2$onDismiss === void 0 ? k : _ref2$onDismiss, | ||
onMouseDown = _ref2.onMouseDown, | ||
initialFocusRef = _ref2.initialFocusRef, | ||
onClick = _ref2.onClick, | ||
onKeyDown = _ref2.onKeyDown, | ||
props = _objectWithoutPropertiesLoose(_ref2, ["isOpen", "onDismiss", "onMouseDown", "initialFocusRef", "onClick", "onKeyDown"]); | ||
if (props["aria-label"] && props["aria-labelledby"]) { | ||
return new Error("You provided both `aria-label` and `aria-labelledby` props to a <" + compName + ">. If the a label for this component is visible on the screen, that label's component should be given a unique ID prop, and that ID should be passed as the `aria-labelledby` prop into <" + compName + ">. If the label cannot be determined programmatically from the content of the element, an alternative label should be provided as the `aria-label` prop, which will be used as an `aria-label` on the HTML tag." + details); | ||
} | ||
return _react["default"].createElement(_componentComponent["default"], { | ||
didMount: checkDialogStyles | ||
}, isOpen ? _react["default"].createElement(_portal["default"], { | ||
"data-reach-dialog-wrapper": true | ||
}, _react["default"].createElement(_componentComponent["default"], { | ||
refs: { | ||
overlayNode: null, | ||
mouseDownTarget: null | ||
}, | ||
didMount: function didMount(_ref3) { | ||
var refs = _ref3.refs; | ||
portalDidMount(refs); | ||
}, | ||
willUnmount: contentWillUnmount | ||
}, function (_ref4) { | ||
var refs = _ref4.refs; | ||
return _react["default"].createElement(_reactFocusLock["default"], { | ||
returnFocus: true, | ||
onActivation: function onActivation() { | ||
if (initialFocusRef) { | ||
initialFocusRef.current.focus(); | ||
} | ||
} | ||
}, _react["default"].createElement(_reactRemoveScroll.RemoveScroll, null, _react["default"].createElement("div", _extends({ | ||
"data-reach-dialog-overlay": true, | ||
onClick: (0, _utils.wrapEvent)(onClick, function (event) { | ||
if (refs.mouseDownTarget === event.target) { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
}), | ||
onMouseDown: (0, _utils.wrapEvent)(onMouseDown, function (event) { | ||
refs.mouseDownTarget = event.target; | ||
}), | ||
onKeyDown: (0, _utils.wrapEvent)(onKeyDown, function (event) { | ||
if (event.key === "Escape") { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
}), | ||
ref: function ref(node) { | ||
refs.overlayNode = node; | ||
(0, _utils.assignRef)(forwardedRef, node); | ||
} | ||
}, props)))); | ||
})) : null); | ||
}); | ||
for (var _len2 = arguments.length, rest = new Array(_len2 > 3 ? _len2 - 3 : 0), _key2 = 3; _key2 < _len2; _key2++) { | ||
rest[_key2 - 3] = arguments[_key2]; | ||
} | ||
exports.DialogOverlay = DialogOverlay; | ||
DialogOverlay.propTypes = { | ||
initialFocusRef: function initialFocusRef() {} | ||
}; | ||
var stopPropagation = function stopPropagation(event) { | ||
return event.stopPropagation(); | ||
}; | ||
var DialogContent = _react["default"].forwardRef(function (_ref5, forwardedRef) { | ||
var onClick = _ref5.onClick, | ||
onKeyDown = _ref5.onKeyDown, | ||
props = _objectWithoutPropertiesLoose(_ref5, ["onClick", "onKeyDown"]); | ||
return _react["default"].createElement("div", _extends({ | ||
"aria-modal": "true", | ||
"data-reach-dialog-content": true, | ||
tabIndex: "-1", | ||
onClick: (0, _utils.wrapEvent)(onClick, stopPropagation), | ||
ref: function ref(node) { | ||
(0, _utils.assignRef)(forwardedRef, node); | ||
} | ||
}, props)); | ||
}); | ||
exports.DialogContent = DialogContent; | ||
var Dialog = function Dialog(_ref6) { | ||
var isOpen = _ref6.isOpen, | ||
_ref6$onDismiss = _ref6.onDismiss, | ||
onDismiss = _ref6$onDismiss === void 0 ? k : _ref6$onDismiss, | ||
initialFocusRef = _ref6.initialFocusRef, | ||
props = _objectWithoutPropertiesLoose(_ref6, ["isOpen", "onDismiss", "initialFocusRef"]); | ||
return _react["default"].createElement(DialogOverlay, { | ||
isOpen: isOpen, | ||
onDismiss: onDismiss, | ||
initialFocusRef: initialFocusRef | ||
}, _react["default"].createElement(DialogContent, props)); | ||
}; | ||
exports.Dialog = Dialog; | ||
Dialog.propTypes = { | ||
isOpen: _propTypes.bool, | ||
onDismiss: _propTypes.func | ||
}; | ||
return _propTypes.string.apply(void 0, [name, props, compName].concat(rest)); | ||
} |
{ | ||
"name": "@reach/dialog", | ||
"version": "0.4.0-beta.0", | ||
"version": "0.4.0", | ||
"description": "Accessible React Modal Dialog.", | ||
"author": "Ryan Florence <@ryanflorence>", | ||
"author": "React Training <hello@reacttraining.com>", | ||
"license": "MIT", | ||
@@ -10,12 +10,10 @@ "main": "index.js", | ||
"scripts": { | ||
"build": "node ../../shared/build-package", | ||
"lint": "eslint . --max-warnings=0" | ||
"build": "node ../../shared/build-package" | ||
}, | ||
"dependencies": { | ||
"@reach/component-component": "^0.4.0-beta.0", | ||
"@reach/portal": "^0.4.0-beta.0", | ||
"@reach/utils": "^0.4.0-beta.0", | ||
"@reach/portal": "^0.4.0", | ||
"@reach/utils": "^0.4.0", | ||
"prop-types": "^15.7.2", | ||
"react-focus-lock": "^2.1.0", | ||
"react-remove-scroll": "^2.0.4" | ||
"react-focus-lock": "^2.2.0", | ||
"react-remove-scroll": "^2.1.1" | ||
}, | ||
@@ -27,5 +25,6 @@ "peerDependencies": { | ||
"devDependencies": { | ||
"@reach/menu-button": "^0.4.0-beta.0", | ||
"react": "^16.8.0", | ||
"react-test-renderer": "^16.8.0" | ||
"@reach/menu-button": "^0.4.0", | ||
"react": "^16.10.2", | ||
"react-spring": "^8.0.27", | ||
"react-test-renderer": "^16.10.2" | ||
}, | ||
@@ -37,4 +36,6 @@ "files": [ | ||
"index.js", | ||
"index.d.ts", | ||
"styles.css" | ||
] | ||
], | ||
"gitHead": "0f3d2c7e530cd80e1d1bd338182ac89a3ba23999" | ||
} |
308
src/index.js
import React from "react"; | ||
import Component from "@reach/component-component"; | ||
import Portal from "@reach/portal"; | ||
@@ -7,8 +6,159 @@ import { checkStyles, wrapEvent, assignRef } from "@reach/utils"; | ||
import { RemoveScroll } from "react-remove-scroll"; | ||
import { func, bool } from "prop-types"; | ||
import { string, func, bool } from "prop-types"; | ||
let createAriaHider = dialogNode => { | ||
const noop = () => {}; | ||
//////////////////////////////////////////////////////////////////////////////// | ||
export const DialogOverlay = React.forwardRef(function DialogOverlay( | ||
{ isOpen = true, ...props }, | ||
forwardedRef | ||
) { | ||
const ownRef = React.useRef(null); | ||
const ref = forwardedRef || ownRef; | ||
React.useEffect(() => { | ||
checkStyles("dialog"); | ||
}, []); | ||
return isOpen ? ( | ||
<Portal data-reach-dialog-wrapper> | ||
<DialogInner ref={ref} {...props} /> | ||
</Portal> | ||
) : null; | ||
}); | ||
if (__DEV__) { | ||
DialogOverlay.propTypes = { | ||
initialFocusRef: () => {} | ||
}; | ||
DialogOverlay.displayName = "DialogOverlay"; | ||
} | ||
//////////////////////////////////////////////////////////////////////////////// | ||
const DialogInner = React.forwardRef(function DialogInner( | ||
{ | ||
initialFocusRef, | ||
onClick, | ||
onDismiss = noop, | ||
onMouseDown, | ||
onKeyDown, | ||
...props | ||
}, | ||
forwardedRef | ||
) { | ||
const mouseDownTarget = React.useRef(null); | ||
const overlayNode = React.useRef(null); | ||
const ref = useForkedRef(overlayNode, forwardedRef); | ||
React.useEffect(() => createAriaHider(forwardedRef.current), [forwardedRef]); | ||
return ( | ||
<FocusLock | ||
autoFocus | ||
returnFocus | ||
onActivation={() => { | ||
if (initialFocusRef && initialFocusRef.current) { | ||
initialFocusRef.current.focus(); | ||
} | ||
}} | ||
> | ||
<RemoveScroll> | ||
<div | ||
data-reach-dialog-overlay | ||
onClick={wrapEvent(onClick, event => { | ||
if (mouseDownTarget.current === event.target) { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
})} | ||
onMouseDown={wrapEvent(onMouseDown, event => { | ||
mouseDownTarget.current = event.target; | ||
})} | ||
onKeyDown={wrapEvent(onKeyDown, event => { | ||
if (event.key === "Escape") { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
})} | ||
ref={ref} | ||
{...props} | ||
/> | ||
</RemoveScroll> | ||
</FocusLock> | ||
); | ||
}); | ||
if (__DEV__) { | ||
DialogOverlay.displayName = "DialogOverlay"; | ||
} | ||
//////////////////////////////////////////////////////////////////////////////// | ||
export const DialogContent = React.forwardRef(function DialogContent( | ||
{ onClick, onKeyDown, ...props }, | ||
forwardedRef | ||
) { | ||
return ( | ||
<div | ||
role="dialog" | ||
aria-modal="true" | ||
data-reach-dialog-content | ||
tabIndex="-1" | ||
onClick={wrapEvent(onClick, event => { | ||
event.stopPropagation(); | ||
})} | ||
ref={forwardedRef} | ||
{...props} | ||
/> | ||
); | ||
}); | ||
if (__DEV__) { | ||
DialogContent.propTypes = { | ||
"aria-label": ariaLabelType, | ||
"aria-labelledby": ariaLabelType | ||
}; | ||
DialogContent.displayName = "DialogContent"; | ||
} | ||
//////////////////////////////////////////////////////////////////////////////// | ||
export const Dialog = React.forwardRef(function Dialog( | ||
{ isOpen, onDismiss = noop, initialFocusRef, ...props }, | ||
forwardedRef | ||
) { | ||
const ownRef = React.useRef(null); | ||
const ref = forwardedRef || ownRef; | ||
return ( | ||
<DialogOverlay | ||
isOpen={isOpen} | ||
onDismiss={onDismiss} | ||
initialFocusRef={initialFocusRef} | ||
> | ||
<DialogContent ref={ref} {...props} /> | ||
</DialogOverlay> | ||
); | ||
}); | ||
if (__DEV__) { | ||
Dialog.propTypes = { | ||
isOpen: bool, | ||
onDismiss: func, | ||
"aria-label": ariaLabelType, | ||
"aria-labelledby": ariaLabelType | ||
}; | ||
Dialog.displayName = "Dialog"; | ||
} | ||
//////////////////////////////////////////////////////////////////////////////// | ||
function createAriaHider(dialogNode) { | ||
let originalValues = []; | ||
let rootNodes = []; | ||
if (!dialogNode) { | ||
if (__DEV__) { | ||
console.warn( | ||
"A ref has not yet been attached to a dialog node when attempting to call `createAriaHider`." | ||
); | ||
} | ||
return noop; | ||
} | ||
Array.prototype.forEach.call(document.querySelectorAll("body > *"), node => { | ||
@@ -39,123 +189,39 @@ const portalNode = dialogNode.parentNode.parentNode.parentNode; | ||
}; | ||
}; | ||
} | ||
let k = () => {}; | ||
// TODO: Remove and import from @reach/utils once it's been added to the package | ||
function useForkedRef(...refs) { | ||
return React.useMemo(() => { | ||
if (refs.every(ref => ref == null)) { | ||
return null; | ||
} | ||
return node => { | ||
refs.forEach(ref => { | ||
assignRef(ref, node); | ||
}); | ||
}; | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, refs); | ||
} | ||
let checkDialogStyles = () => checkStyles("dialog"); | ||
let portalDidMount = refs => { | ||
refs.disposeAriaHider = createAriaHider(refs.overlayNode); | ||
}; | ||
let contentWillUnmount = ({ refs }) => { | ||
refs.disposeAriaHider(); | ||
}; | ||
// eslint-disable-next-line no-unused-vars | ||
let FocusContext = React.createContext(); | ||
let DialogOverlay = React.forwardRef( | ||
( | ||
{ | ||
isOpen = true, | ||
onDismiss = k, | ||
onMouseDown, | ||
initialFocusRef, | ||
onClick, | ||
onKeyDown, | ||
...props | ||
}, | ||
forwardedRef | ||
) => { | ||
return ( | ||
<Component didMount={checkDialogStyles}> | ||
{isOpen ? ( | ||
<Portal data-reach-dialog-wrapper> | ||
<Component | ||
refs={{ overlayNode: null, mouseDownTarget: null }} | ||
didMount={({ refs }) => { | ||
portalDidMount(refs); | ||
}} | ||
willUnmount={contentWillUnmount} | ||
> | ||
{({ refs }) => ( | ||
<FocusLock | ||
returnFocus | ||
onActivation={() => { | ||
if (initialFocusRef) { | ||
initialFocusRef.current.focus(); | ||
} | ||
}} | ||
> | ||
<RemoveScroll> | ||
<div | ||
data-reach-dialog-overlay | ||
onClick={wrapEvent(onClick, event => { | ||
if (refs.mouseDownTarget === event.target) { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
})} | ||
onMouseDown={wrapEvent(onMouseDown, event => { | ||
refs.mouseDownTarget = event.target; | ||
})} | ||
onKeyDown={wrapEvent(onKeyDown, event => { | ||
if (event.key === "Escape") { | ||
event.stopPropagation(); | ||
onDismiss(event); | ||
} | ||
})} | ||
ref={node => { | ||
refs.overlayNode = node; | ||
assignRef(forwardedRef, node); | ||
}} | ||
{...props} | ||
/> | ||
</RemoveScroll> | ||
</FocusLock> | ||
)} | ||
</Component> | ||
</Portal> | ||
) : null} | ||
</Component> | ||
function ariaLabelType(props, name, compName, ...rest) { | ||
const details = | ||
"\nSee https://www.w3.org/TR/wai-aria/#aria-label for details."; | ||
if (!props["aria-label"] && !props["aria-labelledby"]) { | ||
return new Error( | ||
`A <${compName}> must have either an \`aria-label\` or \`aria-labelledby\` prop. | ||
${details}` | ||
); | ||
} | ||
); | ||
DialogOverlay.propTypes = { | ||
initialFocusRef: () => {} | ||
}; | ||
let stopPropagation = event => event.stopPropagation(); | ||
let DialogContent = React.forwardRef( | ||
({ onClick, onKeyDown, ...props }, forwardedRef) => ( | ||
<div | ||
aria-modal="true" | ||
data-reach-dialog-content | ||
tabIndex="-1" | ||
onClick={wrapEvent(onClick, stopPropagation)} | ||
ref={node => { | ||
assignRef(forwardedRef, node); | ||
}} | ||
{...props} | ||
/> | ||
) | ||
); | ||
let Dialog = ({ isOpen, onDismiss = k, initialFocusRef, ...props }) => ( | ||
<DialogOverlay | ||
isOpen={isOpen} | ||
onDismiss={onDismiss} | ||
initialFocusRef={initialFocusRef} | ||
> | ||
<DialogContent {...props} /> | ||
</DialogOverlay> | ||
); | ||
Dialog.propTypes = { | ||
isOpen: bool, | ||
onDismiss: func | ||
}; | ||
export { DialogOverlay, DialogContent, Dialog }; | ||
if (props["aria-label"] && props["aria-labelledby"]) { | ||
return new Error( | ||
"You provided both `aria-label` and `aria-labelledby` props to a <" + | ||
compName + | ||
">. If the a label for this component is visible on the screen, that label's component should be given a unique ID prop, and that ID should be passed as the `aria-labelledby` prop into <" + | ||
compName + | ||
">. If the label cannot be determined programmatically from the content of the element, an alternative label should be provided as the `aria-label` prop, which will be used as an `aria-label` on the HTML tag." + | ||
details | ||
); | ||
} | ||
return string(name, props, compName, ...rest); | ||
} |
@@ -9,3 +9,3 @@ import React from "react"; | ||
<div lang="gr"> | ||
<Dialog isOpen={false}> | ||
<Dialog isOpen={false} aria-label="cool dialog"> | ||
<div lang="en" /> | ||
@@ -12,0 +12,0 @@ </Dialog> |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
27038
7
9
648
4
11
1
Updated@reach/portal@^0.4.0
Updated@reach/utils@^0.4.0
Updatedreact-focus-lock@^2.2.0
Updatedreact-remove-scroll@^2.1.1