UNPKG

@enact/sandstone

Version:

Large-screen/TV support library for Enact, containing a variety of UI components.

869 lines (862 loc) 43.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "ContextualPopup", { enumerable: true, get: function get() { return _ContextualPopup.ContextualPopup; } }); exports["default"] = exports.ContextualPopupDecorator = void 0; var _ApiDecorator = _interopRequireDefault(require("@enact/core/internal/ApiDecorator")); var _dispatcher = require("@enact/core/dispatcher"); var _handle = require("@enact/core/handle"); var _hoc = _interopRequireDefault(require("@enact/core/hoc")); var _propTypes = _interopRequireDefault(require("@enact/core/internal/prop-types")); var _WithRef = require("@enact/core/internal/WithRef"); var _util = require("@enact/core/util"); var _I18nDecorator = require("@enact/i18n/I18nDecorator"); var _spotlight = _interopRequireWildcard(require("@enact/spotlight")); var _SpotlightContainerDecorator = _interopRequireDefault(require("@enact/spotlight/SpotlightContainerDecorator")); var _FloatingLayer = _interopRequireDefault(require("@enact/ui/FloatingLayer")); var _resolution = _interopRequireDefault(require("@enact/ui/resolution")); var _compose = _interopRequireDefault(require("ramda/src/compose")); var _propTypes2 = _interopRequireDefault(require("prop-types")); var _react = require("react"); var _ContextualPopup = require("./ContextualPopup"); var _HolePunchScrim = _interopRequireDefault(require("./HolePunchScrim")); var _ContextualPopupDecoratorModule = _interopRequireDefault(require("./ContextualPopupDecorator.module.css")); var _jsxRuntime = require("react/jsx-runtime"); var _excluded = ["data-webos-voice-exclusive", "popupComponent", "popupClassName", "noAutoDismiss", "open", "offset", "popupProps", "skin", "spotlightRestrict"]; function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } /* global MutationObserver ResizeObserver */ /** * A higher-order component to add a Sandstone styled popup to a component. * * @module sandstone/ContextualPopupDecorator * @exports ContextualPopup * @exports ContextualPopupDecorator */ var PositionToDirection = { above: 'up', below: 'down', left: 'left', right: 'right' }; /** * Default config for {@link sandstone/ContextualPopupDecorator.ContextualPopupDecorator} * * @type {Object} * @hocconfig * @memberof sandstone/ContextualPopupDecorator.ContextualPopupDecorator */ var defaultConfig = { /** * `ContextualPopup` without the arrow. * * @type {Boolean} * @default false * @memberof sandstone/ContextualPopupDecorator.ContextualPopupDecorator.defaultConfig * @public */ noArrow: false, /** * Disables passing the `skin` prop to the wrapped component. * * @see {@link sandstone/Skinnable.Skinnable.skin} * @type {Boolean} * @default false * @memberof sandstone/ContextualPopupDecorator.ContextualPopupDecorator.defaultConfig * @public */ noSkin: false, /** * The prop in which to pass the value of `open` state of ContextualPopupDecorator to the * wrapped component. * * @type {String} * @default 'selected' * @memberof sandstone/ContextualPopupDecorator.ContextualPopupDecorator.defaultConfig * @public */ openProp: 'selected' }; var ContextualPopupContainer = (0, _SpotlightContainerDecorator["default"])({ enterTo: 'default-element', preserveId: true }, _ContextualPopup.ContextualPopup); var Decorator = (0, _hoc["default"])(defaultConfig, function (config, Wrapped) { var _class; var noArrow = config.noArrow, noSkin = config.noSkin, openProp = config.openProp; var WrappedWithRef = (0, _WithRef.WithRef)(Wrapped); return _class = /*#__PURE__*/function (_Component) { _inherits(_class, _Component); var _super = _createSuper(_class); function _class(props) { var _this; _classCallCheck(this, _class); _this = _super.call(this, props); _this.generateId = function () { return Math.random().toString(36).substr(2, 8); }; _this.getContainerAdjustedPosition = function () { var position = _this.adjustedDirection; var arr = _this.adjustedDirection.split(' '); var direction = null; var anchor = null; if (arr.length === 2) { var _arr = _slicedToArray(arr, 2); direction = _arr[0]; anchor = _arr[1]; } else { direction = position; } return { anchor: anchor, direction: direction }; }; /** * Position the popup in relation to the activator. * * Position is based on the dimensions of the popup and its activator. If the popup does not * fit in the specified direction, it will automatically flip to the opposite direction. * * @method * @memberof sandstone/ContextualPopupDecorator.ContextualPopupDecorator.prototype * @public * @returns {undefined} */ _this.positionContextualPopup = function () { var _this$clientSiblingRe; if (_this.containerNode && (_this$clientSiblingRe = _this.clientSiblingRef) !== null && _this$clientSiblingRe !== void 0 && _this$clientSiblingRe.current) { var containerNode = _this.containerNode.getBoundingClientRect(); var _this$clientSiblingRe2 = _this.clientSiblingRef.current.getBoundingClientRect(), top = _this$clientSiblingRe2.top, left = _this$clientSiblingRe2.left, bottom = _this$clientSiblingRe2.bottom, right = _this$clientSiblingRe2.right, width = _this$clientSiblingRe2.width, height = _this$clientSiblingRe2.height; var clientNode = { top: top, left: left, bottom: bottom, right: right, width: width, height: height }; clientNode.left = _this.props.rtl ? window.innerWidth - right : left; clientNode.right = _this.props.rtl ? window.innerWidth - left : right; _this.calcOverflow(containerNode, clientNode); _this.adjustDirection(); var arrowPosition = _this.getArrowPosition(containerNode, clientNode), containerPosition = _this.getContainerPosition(containerNode, clientNode); if (_this.state.direction !== _this.adjustedDirection || _this.state.arrowPosition.left !== arrowPosition.left || _this.state.arrowPosition.top !== arrowPosition.top || _this.state.containerPosition.left !== containerPosition.left || _this.state.containerPosition.right !== containerPosition.right || _this.state.containerPosition.top !== containerPosition.top) { _this.setState({ direction: _this.adjustedDirection, arrowPosition: arrowPosition, containerPosition: containerPosition }); } } }; _this.getContainerNode = function (node) { _this.containerNode = node; if (_this.resizeObserver) { if (node) { var _node$parentElement; // It is not easy to trigger changed position of activator, // so we chose to observe the `div` element's size that has the real size below the root of floatLayer. // This implementation is dependent on the current structure of FloatingLayer, // so if the structure have changed, below code needs to be changed accordingly. _this.resizeObserver.observe(node === null || node === void 0 || (_node$parentElement = node.parentElement) === null || _node$parentElement === void 0 ? void 0 : _node$parentElement.parentElement); } else { _this.resizeObserver.disconnect(); } } if (_this.mutationObserver) { if (node) { _this.mutationObserver.observe(document.body, { attributes: false, childList: true, subtree: true }); } else { _this.mutationObserver.disconnect(); } } }; _this.handle = _handle.handle.bind(_assertThisInitialized(_this)); _this.handleKeyUp = _this.handle((0, _handle.forProp)('open', true), (0, _handle.forKey)('enter'), function () { return _spotlight["default"].getCurrent() === _this.state.activator; }, _handle.stop, (0, _handle.forwardCustom)('onClose')); _this.handleOpen = function (ev) { (0, _handle.forward)('onOpen', ev, _this.props); _this.positionContextualPopup(); var current = _spotlight["default"].getCurrent(); _this.updateLeaveFor(current); _this.setState({ activator: current }); _this.spotPopupContent(); }; _this.handleClose = function () { _this.updateLeaveFor(null); _this.setState({ activator: null }); }; _this.handleDismiss = function () { (0, _handle.forwardCustom)('onClose')(null, _this.props); }; // handle key event from outside (i.e. the activator) to the popup container _this.handleKeyDown = function (ev) { var _this$state = _this.state, activator = _this$state.activator, containerId = _this$state.containerId; var spotlightRestrict = _this.props.spotlightRestrict; var current = _spotlight["default"].getCurrent(); var direction = (0, _spotlight.getDirection)(ev.keyCode); if (!direction) return; var hasSpottables = _spotlight["default"].getSpottableDescendants(containerId).length > 0; var spotlessSpotlightModal = spotlightRestrict === 'self-only' && !hasSpottables; var shouldSpotPopup = current === activator && direction === PositionToDirection[_this.adjustedDirection.split(' ')[0]] && hasSpottables; if (shouldSpotPopup || spotlessSpotlightModal) { _this.handleDirectionalKey(ev); // we guard against attempting a focus change by verifying the case where a // spotlightModal popup contains no spottable components if (!spotlessSpotlightModal && shouldSpotPopup) { _this.spotPopupContent(); } } }; // handle key event from contextual popup and closes the popup _this.handleContainerKeyDown = function (ev) { // Note: Container will be only rendered if `open`ed, therefore no need to check for `open` var direction = (0, _spotlight.getDirection)(ev.keyCode); if (!direction) return; _this.handleDirectionalKey(ev); // if focus moves outside the popup's container, issue the `onClose` event if (_spotlight["default"].move(direction) && !_this.containerNode.contains(_spotlight["default"].getCurrent())) { (0, _handle.forwardCustom)('onClose')(null, _this.props); } }; _this.spotActivator = function (activator) { if (!_spotlight["default"].getPointerMode() && activator && activator === _spotlight["default"].getCurrent()) { activator.blur(); } if (!_spotlight["default"].focus(activator)) { _spotlight["default"].focus(); } }; _this.spotPopupContent = function () { var spotlightRestrict = _this.props.spotlightRestrict; var containerId = _this.state.containerId; var spottableDescendants = _spotlight["default"].getSpottableDescendants(containerId); if (spotlightRestrict === 'self-only' && spottableDescendants.length && _spotlight["default"].getCurrent()) { _spotlight["default"].getCurrent().blur(); } if (!_spotlight["default"].focus(containerId)) { _spotlight["default"].setActiveContainer(containerId); } }; _this.state = { arrowPosition: { top: 0, left: 0 }, containerPosition: { top: 0, left: 0, right: 0 }, containerId: _spotlight["default"].add(_this.props.popupSpotlightId), activator: null }; _this.resizeObserver = null; _this.overflow = {}; _this.adjustedDirection = _this.props.direction; _this.id = _this.generateId(); _this.clientSiblingRef = /*#__PURE__*/(0, _react.createRef)(null); _this.findClientSiblingRef = /*#__PURE__*/(0, _react.createRef)(null); _this.MARGIN = _resolution["default"].scale(noArrow ? 0 : 12); _this.ARROW_WIDTH = noArrow ? 0 : _resolution["default"].scale(60); // svg arrow width. used for arrow positioning _this.ARROW_OFFSET = noArrow ? 0 : _resolution["default"].scale(36); // actual distance of the svg arrow displayed to offset overlaps with the container. Offset is when `noArrow` is false. _this.KEEPOUT = _resolution["default"].scale(24); // keep out distance on the edge of the screen if (props.setApiProvider) { props.setApiProvider(_assertThisInitialized(_this)); } return _this; } _createClass(_class, [{ key: "componentDidMount", value: function componentDidMount() { var _this2 = this; if (this.props.open) { (0, _dispatcher.on)('keydown', this.handleKeyDown); (0, _dispatcher.on)('keyup', this.handleKeyUp); } if (typeof ResizeObserver === 'function') { this.resizeObserver = new ResizeObserver(function () { _this2.positionContextualPopup(); }); } if (typeof MutationObserver === 'function') { this.mutationObserver = new MutationObserver(function () { _this2.positionContextualPopup(); }); } } }, { key: "getSnapshotBeforeUpdate", value: function getSnapshotBeforeUpdate(prevProps, prevState) { var snapshot = { containerWidth: this.getContainerNodeWidth(), clientSiblingWidth: this.getClientSiblingNodeWidth() }; if (prevProps.open && !this.props.open) { var current = _spotlight["default"].getCurrent(); snapshot.shouldSpotActivator = // isn't set !current || // is on the activator and we want to re-spot it so a11y read out can occur current === prevState.activator || // is within the popup this.containerNode.contains(current); } return snapshot; } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps, prevState, snapshot) { if (snapshot.clientSiblingWidth !== this.getClientSiblingNodeWidth()) { this.clientSiblingRef.current = this.findClientSiblingRef.current(); } if (prevProps.direction !== this.props.direction || snapshot.containerWidth !== this.getContainerNodeWidth() || prevProps.open && this.props.open) { this.adjustedDirection = this.props.direction; // NOTE: `setState` is called and will cause re-render this.positionContextualPopup(); } if (this.props.open && !prevProps.open) { (0, _dispatcher.on)('keydown', this.handleKeyDown); (0, _dispatcher.on)('keyup', this.handleKeyUp); } else if (!this.props.open && prevProps.open) { (0, _dispatcher.off)('keydown', this.handleKeyDown); (0, _dispatcher.off)('keyup', this.handleKeyUp); if (snapshot && snapshot.shouldSpotActivator) { this.spotActivator(prevState.activator); } } } }, { key: "componentWillUnmount", value: function componentWillUnmount() { if (this.props.open) { (0, _dispatcher.off)('keydown', this.handleKeyDown); (0, _dispatcher.off)('keyup', this.handleKeyUp); } _spotlight["default"].remove(this.state.containerId); if (this.resizeObserver) { this.resizeObserver.disconnect(); this.resizeObserver = null; } if (this.mutationObserver) { this.mutationObserver.disconnect(); this.mutationObserver = null; } } }, { key: "getContainerNodeWidth", value: function getContainerNodeWidth() { return this.containerNode && this.containerNode.getBoundingClientRect().width || 0; } }, { key: "getClientSiblingNodeWidth", value: function getClientSiblingNodeWidth() { return this.clientSiblingRef.current && this.clientSiblingRef.current.getBoundingClientRect().width || 0; } }, { key: "updateLeaveFor", value: function updateLeaveFor(activator) { _spotlight["default"].set(this.state.containerId, { leaveFor: { up: activator, down: activator, left: activator, right: activator } }); } }, { key: "getContainerPosition", value: function getContainerPosition(containerNode, clientNode) { var position = this.centerContainerPosition(containerNode, clientNode); var _this$getContainerAdj = this.getContainerAdjustedPosition(), direction = _this$getContainerAdj.direction; switch (direction) { case 'above': position.top = clientNode.top - this.ARROW_OFFSET - containerNode.height - this.MARGIN; break; case 'below': position.top = clientNode.bottom + this.ARROW_OFFSET + this.MARGIN; break; case 'right': position.left = this.props.rtl ? clientNode.left - containerNode.width - this.ARROW_OFFSET - this.MARGIN : clientNode.right + this.ARROW_OFFSET + this.MARGIN; break; case 'left': position.left = this.props.rtl ? clientNode.right + this.ARROW_OFFSET + this.MARGIN : clientNode.left - containerNode.width - this.ARROW_OFFSET - this.MARGIN; break; } return this.adjustRTL(position); } }, { key: "centerContainerPosition", value: function centerContainerPosition(containerNode, clientNode) { var pos = {}; var _this$getContainerAdj2 = this.getContainerAdjustedPosition(), anchor = _this$getContainerAdj2.anchor, direction = _this$getContainerAdj2.direction; if (direction === 'above' || direction === 'below') { if (this.overflow.isOverLeft) { // anchor to the left of the screen pos.left = this.KEEPOUT; } else if (this.overflow.isOverRight) { // anchor to the right of the screen pos.left = window.innerWidth - containerNode.width - this.KEEPOUT; } else if (anchor) { if (anchor === 'center') { // center horizontally pos.left = clientNode.left + (clientNode.width - containerNode.width) / 2; } else if (anchor === 'left') { // anchor to the left side of the activator pos.left = clientNode.left; } else { // anchor to the right side of the activator pos.left = clientNode.right - containerNode.width; } } else { // anchor to the left side of the activator, matching its width pos.left = clientNode.left; pos.width = clientNode.width; } } else if (direction === 'left' || direction === 'right') { if (this.overflow.isOverTop) { // anchor to the top of the screen pos.top = this.KEEPOUT; } else if (this.overflow.isOverBottom) { // anchor to the bottom of the screen pos.top = window.innerHeight - containerNode.height - this.KEEPOUT; } else if (anchor === 'middle') { // center vertically pos.top = clientNode.top - (containerNode.height - clientNode.height) / 2; } else if (anchor === 'top') { // anchor to the top of the activator pos.top = clientNode.top; } else { // anchor to the bottom of the activator pos.top = clientNode.bottom - containerNode.height; } } return pos; } }, { key: "getArrowPosition", value: function getArrowPosition(containerNode, clientNode) { var position = {}; var _this$getContainerAdj3 = this.getContainerAdjustedPosition(), anchor = _this$getContainerAdj3.anchor, direction = _this$getContainerAdj3.direction; if (direction === 'above' || direction === 'below') { if (this.overflow.isOverRight && !this.overflow.isOverLeft) { position.left = window.innerWidth - (containerNode.width + this.ARROW_WIDTH) / 2 - this.KEEPOUT; } else if (!this.overflow.isOverRight && this.overflow.isOverLeft) { position.left = (containerNode.width - this.ARROW_WIDTH) / 2 + this.KEEPOUT; } else if (anchor === 'left') { position.left = clientNode.left + (containerNode.width - this.ARROW_WIDTH) / 2; } else if (anchor === 'right') { position.left = clientNode.right - containerNode.width + (containerNode.width - this.ARROW_WIDTH) / 2; } else { position.left = clientNode.left + (clientNode.width - this.ARROW_WIDTH) / 2; } } else if (this.overflow.isOverBottom && !this.overflow.isOverTop) { position.top = window.innerHeight - (containerNode.height + this.ARROW_WIDTH) / 2 - this.KEEPOUT; } else if (!this.overflow.isOverBottom && this.overflow.isOverTop) { position.top = (containerNode.height - this.ARROW_WIDTH) / 2 + this.KEEPOUT; } else if (anchor === 'top') { position.top = clientNode.top + (containerNode.height - this.ARROW_WIDTH) / 2; } else if (anchor === 'bottom') { position.top = clientNode.bottom - containerNode.height + (containerNode.height - this.ARROW_WIDTH) / 2; } else { position.top = clientNode.top + (clientNode.height - this.ARROW_WIDTH) / 2; } switch (direction) { case 'above': position.top = clientNode.top - this.ARROW_WIDTH - this.MARGIN; break; case 'below': position.top = clientNode.bottom + this.MARGIN; break; case 'left': position.left = this.props.rtl ? clientNode.left + clientNode.width + this.MARGIN : clientNode.left - this.ARROW_WIDTH - this.MARGIN; break; case 'right': position.left = this.props.rtl ? clientNode.left - this.ARROW_WIDTH - this.MARGIN : clientNode.left + clientNode.width + this.MARGIN; break; default: return {}; } return this.adjustRTL(position); } }, { key: "calcOverflow", value: function calcOverflow(container, client) { var containerHeight, containerWidth; var _this$getContainerAdj4 = this.getContainerAdjustedPosition(), anchor = _this$getContainerAdj4.anchor, direction = _this$getContainerAdj4.direction; if (direction === 'above' || direction === 'below') { containerHeight = container.height; containerWidth = (container.width - client.width) / 2; } else { containerHeight = (container.height - client.height) / 2; containerWidth = container.width; } this.overflow = { isOverTop: anchor === 'top' && (direction === 'left' || direction === 'right') ? !(client.top > this.KEEPOUT) : client.top - containerHeight - this.ARROW_OFFSET - this.MARGIN - this.KEEPOUT < 0, isOverBottom: anchor === 'bottom' && (direction === 'left' || direction === 'right') ? client.bottom + this.KEEPOUT > window.innerHeight : client.bottom + containerHeight + this.ARROW_OFFSET + this.MARGIN + this.KEEPOUT > window.innerHeight, isOverLeft: anchor === 'left' && (direction === 'above' || direction === 'below') ? !(client.left > this.KEEPOUT) : client.left - containerWidth - this.ARROW_OFFSET - this.MARGIN - this.KEEPOUT < 0, isOverRight: anchor === 'right' && (direction === 'above' || direction === 'below') ? client.right + this.KEEPOUT > window.innerWidth : client.right + containerWidth + this.ARROW_OFFSET + this.MARGIN + this.KEEPOUT > window.innerWidth }; } }, { key: "adjustDirection", value: function adjustDirection() { var _this$getContainerAdj5 = this.getContainerAdjustedPosition(), anchor = _this$getContainerAdj5.anchor, direction = _this$getContainerAdj5.direction; if (this.overflow.isOverTop && !this.overflow.isOverBottom && direction === 'above') { this.adjustedDirection = anchor ? "below ".concat(anchor) : 'below'; } else if (this.overflow.isOverBottom && !this.overflow.isOverTop && direction === 'below') { this.adjustedDirection = anchor ? "above ".concat(anchor) : 'above'; } else if (this.overflow.isOverLeft && !this.overflow.isOverRight && direction === 'left' && !this.props.rtl) { this.adjustedDirection = anchor ? "right ".concat(anchor) : 'right'; } else if (this.overflow.isOverRight && !this.overflow.isOverLeft && direction === 'right' && !this.props.rtl) { this.adjustedDirection = anchor ? "left ".concat(anchor) : 'left'; } } }, { key: "adjustRTL", value: function adjustRTL(position) { var pos = position; if (this.props.rtl) { var tmpLeft = pos.left; pos.left = pos.right; pos.right = tmpLeft; } return pos; } }, { key: "handleDirectionalKey", value: function handleDirectionalKey(ev) { // prevent default page scrolling ev.preventDefault(); // stop propagation to prevent default spotlight behavior ev.stopPropagation(); // set the pointer mode to false on keydown _spotlight["default"].setPointerMode(false); } }, { key: "render", value: function render() { var _this$clientSiblingRe3; var _this$props = this.props, voiceExclusive = _this$props['data-webos-voice-exclusive'], PopupComponent = _this$props.popupComponent, popupClassName = _this$props.popupClassName, noAutoDismiss = _this$props.noAutoDismiss, open = _this$props.open, offset = _this$props.offset, popupProps = _this$props.popupProps, skin = _this$props.skin, spotlightRestrict = _this$props.spotlightRestrict, rest = _objectWithoutProperties(_this$props, _excluded); var idFloatLayer = "".concat(this.id, "_floatLayer"); var scrimType = rest.scrimType; delete rest.scrimType; // 'holepunch' scrimType is specific to this component, not supported by floating layer // so it must be swapped-out for one that FloatingLayer does support. var holepunchScrim = scrimType === 'holepunch'; if (spotlightRestrict === 'self-only' && scrimType === 'none' || holepunchScrim) { scrimType = 'transparent'; } var popupPropsRef = Object.assign({}, popupProps); var ariaProps = (0, _util.extractAriaProps)(popupPropsRef); if (!noSkin) { rest.skin = skin; } var holeBounds; if ((_this$clientSiblingRe3 = this.clientSiblingRef) !== null && _this$clientSiblingRe3 !== void 0 && _this$clientSiblingRe3.current && holepunchScrim) { holeBounds = this.clientSiblingRef.current.getBoundingClientRect(); } delete rest.direction; delete rest.onClose; delete rest.onOpen; delete rest.popupSpotlightId; delete rest.rtl; delete rest.setApiProvider; if (openProp) rest[openProp] = open; return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", { "aria-owns": idFloatLayer, className: _ContextualPopupDecoratorModule["default"].contextualPopupDecorator, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_FloatingLayer["default"], { id: idFloatLayer, noAutoDismiss: noAutoDismiss, onClose: this.handleClose, onDismiss: this.handleDismiss, onOpen: this.handleOpen, open: open, scrimType: scrimType, children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_react.Fragment, { children: [holepunchScrim ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_HolePunchScrim["default"], { holeBounds: holeBounds }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(ContextualPopupContainer, _objectSpread(_objectSpread({}, ariaProps), {}, { className: popupClassName, onKeyDown: this.handleContainerKeyDown, direction: this.state.direction, arrowPosition: this.state.arrowPosition, containerPosition: this.state.containerPosition, containerRef: this.getContainerNode, "data-webos-voice-exclusive": voiceExclusive, offset: noArrow ? offset : 'none', showArrow: !noArrow, skin: skin, spotlightId: this.state.containerId, spotlightRestrict: spotlightRestrict, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(PopupComponent, _objectSpread({}, popupPropsRef)) }))] }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(WrappedWithRef, _objectSpread(_objectSpread({}, rest), {}, { outermostRef: this.clientSiblingRef, findOutermostRef: this.findClientSiblingRef, referrerName: "ContextualPopup" }))] }); } }]); return _class; }(_react.Component), _class.displayName = 'ContextualPopupDecorator', _class.propTypes = /** @lends sandstone/ContextualPopupDecorator.ContextualPopupDecorator.prototype */{ /** * The component rendered within the * {@link sandstone/ContextualPopupDecorator.ContextualPopup|ContextualPopup}. * * @type {Component} * @required * @public */ popupComponent: _propTypes["default"].component.isRequired, /** * Limits the range of voice control to the popup. * * @memberof sandstone/ContextualPopupDecorator.ContextualPopupDecorator.prototype * @type {Boolean} * @default true * @public */ 'data-webos-voice-exclusive': _propTypes2["default"].bool, /** * Direction of popup with respect to the wrapped component. * * @type {('above'|'above center'|'above left'|'above right'|'below'|'below center'|'below left'|'below right'|'left middle'|'left top'|'left bottom'|'right middle'|'right top'|'right bottom')} * @default 'below center' * @public */ direction: _propTypes2["default"].oneOf(['above', 'above center', 'above left', 'above right', 'below', 'below center', 'below left', 'below right', 'left middle', 'left top', 'left bottom', 'right middle', 'right top', 'right bottom']), /** * Disables closing the popup when the user presses the cancel/back (e.g. `ESC`) key or taps outside the * popup. * * @type {Boolean} * @default false * @public */ noAutoDismiss: _propTypes2["default"].bool, /** * Offset from the activator to apply to the position of the popup. * * Only applies when `noArrow` is `true`. * * @type {('none'|'overlap'|'small')} * @default 'small' * @public */ offset: _propTypes2["default"].oneOf(['none', 'overlap', 'small']), /** * Called when the user has attempted to close the popup. * * This may occur either when the close button is clicked or when spotlight focus * moves outside the boundary of the popup. Setting `spotlightRestrict` to `'self-only'` * will prevent Spotlight focus from leaving the popup. * * @type {Function} * @public */ onClose: _propTypes2["default"].func, /** * Called when the popup is opened. * * @type {Function} * @public */ onOpen: _propTypes2["default"].func, /** * Displays the contextual popup. * * @type {Boolean} * @default false * @public */ open: _propTypes2["default"].bool, /** * CSS class name to pass to the * {@link sandstone/ContextualPopupDecorator.ContextualPopup|ContextualPopup}. * * This is commonly used to set width and height of the popup. * * @type {String} * @public */ popupClassName: _propTypes2["default"].string, /** * An object containing properties to be passed to popup component. * * @type {Object} * @public */ popupProps: _propTypes2["default"].object, /** * The container ID to use with Spotlight. * * The spotlight container for the popup isn't created until it is open. To configure * the container using `Spotlight.set()`, handle the `onOpen` event which is fired after * the popup has been created and opened. * * @type {String} * @public */ popupSpotlightId: _propTypes2["default"].string, /** * Indicates the content's text direction is right-to-left. * * @type {Boolean} * @private */ rtl: _propTypes2["default"].bool, /** * Set the type of scrim to use * * @type {('holepunch'|'translucent'|'transparent'|'none')} * @default 'none' * @private */ scrimType: _propTypes2["default"].oneOf(['holepunch', 'translucent', 'transparent', 'none']), /** * Registers the ContextualPopupDecorator component with an * {@link core/internal/ApiDecorator.ApiDecorator|ApiDecorator}. * * @type {Function} * @private */ setApiProvider: _propTypes2["default"].func, /** * The current skin for this component. * * When `noSkin` is set on the config object, `skin` will only be applied to the * {@link sandstone/ContextualPopupDecorator.ContextualPopup|ContextualPopup} and not * to the popup's activator component. * * @see {@link sandstone/Skinnable.Skinnable.skin} * @type {String} * @public */ skin: _propTypes2["default"].string, /** * Restricts or prioritizes spotlight navigation. * * Allowed values are: * * `'none'` - Spotlight can move freely within and beyond the popup * * `'self-first'` - Spotlight should prefer components within the popup over * components beyond the popup, or * * `'self-only'` - Spotlight can only be set within the popup * * @type {('none'|'self-first'|'self-only')} * @default 'self-first' * @public */ spotlightRestrict: _propTypes2["default"].oneOf(['none', 'self-first', 'self-only']) }, _class.defaultProps = { 'data-webos-voice-exclusive': true, direction: 'below center', noAutoDismiss: false, offset: 'small', open: false, scrimType: 'none', spotlightRestrict: 'self-first' }, _class; }); /** * Adds support for positioning a * {@link sandstone/ContextualPopupDecorator.ContextualPopup|ContextualPopup} relative to the * wrapped component. * * `ContextualPopupDecorator` may be used to show additional settings or actions rendered within a * small floating popup. * * Usage: * ``` * const ButtonWithPopup = ContextualPopupDecorator(Button); * <ButtonWithPopup * direction="above center" * open={this.state.open} * popupComponent={CustomPopupComponent} * > * Open Popup * </ButtonWithPopup> * ``` * * @hoc * @memberof sandstone/ContextualPopupDecorator * @public */ var ContextualPopupDecorator = exports.ContextualPopupDecorator = (0, _compose["default"])((0, _ApiDecorator["default"])({ api: ['positionContextualPopup'] }), (0, _I18nDecorator.I18nContextDecorator)({ rtlProp: 'rtl' }), Decorator); var _default = exports["default"] = ContextualPopupDecorator;