@enact/sandstone
Version:
Large-screen/TV support library for Enact, containing a variety of UI components.
262 lines (259 loc) • 12.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = exports.ContextualMenuDecorator = void 0;
var _handle = require("@enact/core/handle");
var _hoc = _interopRequireDefault(require("@enact/core/hoc"));
var _kind = _interopRequireDefault(require("@enact/core/kind"));
var _Repeater = _interopRequireDefault(require("@enact/ui/Repeater"));
var _Toggleable = _interopRequireDefault(require("@enact/ui/Toggleable"));
var _compose = _interopRequireDefault(require("ramda/src/compose"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _ContextualPopupDecorator = _interopRequireDefault(require("../ContextualPopupDecorator"));
var _Item = _interopRequireDefault(require("../Item"));
var _Scroller = _interopRequireDefault(require("../Scroller"));
var _Skinnable = _interopRequireDefault(require("../Skinnable"));
var _ContextualMenuDecoratorModule = _interopRequireDefault(require("./ContextualMenuDecorator.module.css"));
var _jsxRuntime = require("react/jsx-runtime");
var _excluded = ["className"],
_excluded2 = ["onOpen", "popupProps"];
/**
* A decorator for adding contextual menus to components.
*
* @module sandstone/ContextualMenuDecorator
* @exports ContextualMenuDecorator
*/
// The maximum number of visible items. More than this number invokes a scroller.
// When updating this value, you must also set the max-items LESS variable.
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 _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 _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; }
var MAX_VISIBLE_MENU_ITEMS = 5;
/**
* Default config for {@link sandstone/ContextualMenuDecorator.ContextualMenuDecorator}
*
* @type {Object}
* @hocconfig
* @memberof sandstone/ContextualMenuDecorator.ContextualMenuDecorator
*/
var defaultConfig = {
/**
* Disables passing the `skin` prop to the wrapped component.
*
* @see {@link sandstone/Skinnable.Skinnable.skin}
* @type {Boolean}
* @default false
* @memberof sandstone/ContextualMenuDecorator.ContextualMenuDecorator.defaultConfig
* @public
*/
noSkin: false,
/**
* The prop in which to pass the value of `open` state of ContextualMenuDecorator to the
* wrapped component.
*
* @type {String}
* @default 'selected'
* @memberof sandstone/ContextualMenuDecorator.ContextualMenuDecorator.defaultConfig
* @public
*/
openProp: 'selected'
};
var ScrollingRepeater = function ScrollingRepeater(_ref) {
var className = _ref.className,
rest = _objectWithoutProperties(_ref, _excluded);
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Scroller["default"], {
className: className,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Repeater["default"], _objectSpread({}, rest))
});
};
var ContextualMenuDecoratorBase = (0, _hoc["default"])(defaultConfig, function (config, Wrapped) {
// we might not need Skinnable at all here. If we want to skin the popup and it's defined as a
// private component in this module, we can wrap it with skinnable and style it as needed there.
var Component = (0, _Skinnable["default"])((0, _ContextualPopupDecorator["default"])(_objectSpread(_objectSpread({}, config), {}, {
noArrow: true
}), Wrapped));
return (0, _kind["default"])({
name: 'ContextualMenuDecorator',
propTypes: /** @lends sandstone/ContextualMenuDecorator.ContextualMenuDecorator.prototype */{
/**
* 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 right'
* @public
*/
direction: _propTypes["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']),
/**
* The items to be displayed in the `ContextualMenuDecorator` when `open`.
*
* Takes either an array of strings or an array of objects. When strings, the values will be
* used in the generated components as the readable text. When objects, the properties will
* be passed onto an `Item` component and `children` as well as a unique `key` property are
* required.
*
* @type {String[]|Array.<{key: (Number|String), children: (String|Component)}>}
* @public
*/
menuItems: _propTypes["default"].oneOfType([_propTypes["default"].arrayOf(_propTypes["default"].string), _propTypes["default"].arrayOf(_propTypes["default"].shape({
children: _propTypes["default"].string.isRequired,
key: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].number]).isRequired
}))]),
/**
* Offset from the activator to apply to the position of the popup.
*
* @type {('none'|'overlap'|'small')}
* @default 'overlap'
* @public
*/
offset: _propTypes["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: _propTypes["default"].func,
/**
* Called when the popup is opened.
*
* @type {Function}
* @public
*/
onOpen: _propTypes["default"].func,
/**
* 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: _propTypes["default"].string,
/**
* An object containing properties to be passed to popup component.
*
* @type {Object}
* @public
*/
popupProps: _propTypes["default"].object,
/**
* Width of the Popup component.
*
* When `'auto'` the popup will match the activator's width when `direction` is
* `'below'` or `'above'` or a width specified in `popupClassName`.
*
* @type {('auto'|'large'|'small')}
* @default 'auto'
* @private
*/
popupWidth: _propTypes["default"].oneOf(['auto', 'large', 'small']),
/**
* Set the type of scrim to use
*
* @type {('holepunch'|'translucent'|'transparent'|'none')}
* @default 'holepunch'
* @private
*/
scrimType: _propTypes["default"].oneOf(['holepunch', 'translucent', 'transparent', 'none']),
/**
* 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-only'
* @public
*/
spotlightRestrict: _propTypes["default"].oneOf(['none', 'self-first', 'self-only'])
},
defaultProps: {
direction: 'below right',
offset: 'overlap',
popupWidth: 'auto',
scrimType: 'holepunch',
spotlightRestrict: 'self-only'
},
handlers: {
onOpen: (0, _handle.handle)((0, _handle.forward)('onClick'), (0, _handle.forProp)('open', false), (0, _handle.forward)('onOpen'))
},
styles: {
css: _ContextualMenuDecoratorModule["default"]
},
computed: {
// expect we'll be able to drop this when we add the private popupComponent
// implementation with the Repeater for the items since the popup class could be set
// on the component by itself
popupClassName: function popupClassName(_ref2) {
var popupWidth = _ref2.popupWidth,
_popupClassName = _ref2.popupClassName,
styler = _ref2.styler;
var sizeClass = popupWidth !== 'auto' && popupWidth;
return styler.join('popup', 'container', _popupClassName, sizeClass);
},
popupComponent: function popupComponent(_ref3) {
var menuItems = _ref3.menuItems;
return menuItems && menuItems.length > MAX_VISIBLE_MENU_ITEMS ? ScrollingRepeater : _Repeater["default"];
},
popupProps: function popupProps(_ref4) {
var menuItems = _ref4.menuItems,
_popupProps = _ref4.popupProps;
return _objectSpread({
'aria-live': null,
children: menuItems,
childComponent: _Item["default"],
className: _ContextualMenuDecoratorModule["default"].innerContainer,
itemProps: {
className: _ContextualMenuDecoratorModule["default"].item,
size: 'small'
},
component: 'div',
role: null
}, _popupProps);
}
},
render: function render(_ref5) {
var onOpen = _ref5.onOpen,
popupProps = _ref5.popupProps,
rest = _objectWithoutProperties(_ref5, _excluded2);
delete rest.menuItems;
delete rest.onOpen;
delete rest.popupWidth;
return /*#__PURE__*/(0, _jsxRuntime.jsx)(Component, _objectSpread(_objectSpread({}, rest), {}, {
onClick: onOpen,
popupProps: popupProps
}));
}
});
});
/**
* Wraps a component to display a contextual popup menu.
*
* @hoc
* @memberof sandstone/ContextualMenuDecorator
* @mixes ui/Toggleable.Toggleable
* @mixes sandstone/Skinnable.Skinnable
* @mixes sandstone/ContextualPopupDecorator.ContextualPopupDecorator
* @public
*/
var ContextualMenuDecorator = exports.ContextualMenuDecorator = (0, _compose["default"])((0, _Toggleable["default"])({
activate: 'onOpen',
deactivate: 'onClose',
prop: 'open',
toggle: null
}), ContextualMenuDecoratorBase);
var _default = exports["default"] = ContextualMenuDecorator;