UNPKG

@enact/sandstone

Version:

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

364 lines (363 loc) 17.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isSelectedValid = exports["default"] = exports.DropdownListBase = exports.DropdownList = void 0; var _kind = _interopRequireDefault(require("@enact/core/kind")); 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 _spotlight = _interopRequireDefault(require("@enact/spotlight")); var _IdProvider = _interopRequireDefault(require("@enact/ui/internal/IdProvider")); var _resolution = _interopRequireDefault(require("@enact/ui/resolution")); var _propTypes2 = _interopRequireDefault(require("prop-types")); var _compose = _interopRequireDefault(require("ramda/src/compose")); var _react = require("react"); var _$L = _interopRequireDefault(require("../internal/$L")); var _Icon = _interopRequireDefault(require("../Icon")); var _Item = _interopRequireDefault(require("../Item")); var _Skinnable = _interopRequireDefault(require("../Skinnable")); var _VirtualList = _interopRequireDefault(require("../VirtualList")); var _DropdownModule = _interopRequireDefault(require("./Dropdown.module.css")); var _util = require("../internal/util"); var _jsxRuntime = require("react/jsx-runtime"); var _excluded = ["index"], _excluded2 = ["key"], _excluded3 = ["dataSize", "id", "itemSize", "scrollTo", "width"]; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 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 _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); } 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 isSelectedValid = exports.isSelectedValid = function isSelectedValid(_ref) { var children = _ref.children, selected = _ref.selected; return Array.isArray(children) && children[selected] != null; }; var getKey = function getKey(_ref2) { var children = _ref2.children, selected = _ref2.selected; if (isSelectedValid({ children: children, selected: selected })) { return children[selected].key; } }; var indexFromKey = function indexFromKey(children, key) { var index = -1; if (children) { index = children.findIndex(function (child) { return child.key === key; }); } return index; }; var DropdownListBase = exports.DropdownListBase = (0, _kind["default"])({ name: 'DropdownListBase', propTypes: { /* * The selections for Dropdown * * @type {String[]|Array.<{key: (Number|String), children: (String|Component)}>} */ children: _propTypes2["default"].oneOfType([_propTypes2["default"].arrayOf(_propTypes2["default"].string), _propTypes2["default"].arrayOf(_propTypes2["default"].shape({ children: _propTypes["default"].renderable.isRequired, key: _propTypes2["default"].oneOfType([_propTypes2["default"].string, _propTypes2["default"].number]).isRequired }))]), /** * The `id` of DropdownList referred to when setting aria-labelledby * * @type {String} * @private */ id: _propTypes2["default"].string, /* * Called when an item is selected. * * @type {Function} */ onSelect: _propTypes2["default"].func, /* * Callback function that will receive the scroller's scrollTo() method * * @type {Function} */ scrollTo: _propTypes2["default"].func, /* * Index of the selected item. * * @type {Number} */ selected: _propTypes2["default"].number, /* * State of possible skin variants. * * Used to scale the `itemSize` of the `VirtualList` based on large-text mode * * @type {Object} */ skinVariants: _propTypes2["default"].object, /* * The width of DropdownList. * * @type {('huge'|'x-large'|'large'|'medium'|'small'|'tiny')|number} */ width: _propTypes2["default"].oneOfType([_propTypes2["default"].oneOf(['tiny', 'small', 'medium', 'large', 'x-large', 'huge']), _propTypes2["default"].number]) }, styles: { css: _DropdownModule["default"], className: 'dropdownList' }, handlers: { itemRenderer: function itemRenderer(_ref3, props) { var index = _ref3.index, rest = _objectWithoutProperties(_ref3, _excluded); var children = props.children, selected = props.selected; var isSelected = index === selected; var slotAfter = isSelected ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon["default"], { children: "check" }) : null; var child = children[index]; if (typeof child === 'string') { child = { children: child }; } var data = child.children; var _child = _objectSpread({}, child), key = _child.key, restChild = _objectWithoutProperties(_child, _excluded2); return /*#__PURE__*/(0, _react.createElement)(_Item["default"], _objectSpread(_objectSpread(_objectSpread({}, rest), restChild), {}, { key: key, slotAfter: slotAfter, "data-selected": isSelected // eslint-disable-next-line react/jsx-no-bind , onClick: function onClick() { return (0, _handle.forward)('onSelect', { data: data, selected: index }, props); }, size: "small" })); } }, computed: { className: function className(_ref4) { var width = _ref4.width, styler = _ref4.styler; return styler.append(typeof width === 'string' ? width : null); }, dataSize: function dataSize(_ref5) { var children = _ref5.children; return children ? children.length : 0; }, // Note: Retaining this in case we need to support different item sizes for large text mode: // itemSize: ({skinVariants}) => ri.scale(skinVariants && skinVariants.largeText ? 126 : 126) itemSize: function itemSize() { return 126; } }, render: function render(_ref6) { var dataSize = _ref6.dataSize, id = _ref6.id, itemSize = _ref6.itemSize, scrollTo = _ref6.scrollTo, width = _ref6.width, rest = _objectWithoutProperties(_ref6, _excluded3); delete rest.children; delete rest.onSelect; delete rest.selected; delete rest.skinVariants; delete rest.width; return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", { role: "region", "aria-labelledby": "".concat(id, "_dropdownlist"), children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", { id: "".concat(id, "_dropdownlist"), "aria-label": (0, _$L["default"])('Dropdown list opened') }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList["default"], _objectSpread(_objectSpread({}, rest), {}, { cbScrollTo: scrollTo, dataSize: dataSize, itemSize: _resolution["default"].scale(itemSize), style: { height: _resolution["default"].scaleToRem(itemSize * dataSize + 36), width: typeof width === 'number' ? _resolution["default"].scaleToRem(width) : null } }))] }); } }); var ReadyState = { // Initial state. Scrolling and focusing pending INIT: 0, // Scroll requested SCROLLED: 1, // Focus completed or not required DONE: 2 }; var DropdownListSpotlightDecorator = (0, _hoc["default"])(function (config, Wrapped) { var _class; 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.setScrollTo = function (scrollTo) { _this.scrollTo = scrollTo; }; _this.scrollIntoView = function () { var selected = _this.props.selected; if (_this.state.prevFocused == null && !isSelectedValid(_this.props)) { selected = 0; } else if (_this.state.prevFocused != null) { selected = _this.state.prevFocused; } _this.scrollTo({ animate: false, focus: true, index: selected, offset: _resolution["default"].scale(126 * 2), // @sand-item-small-height * 2 (TODO: large text mode not supported!) stickTo: 'start' // offset from the top of the dropdown }); _this.setState({ ready: ReadyState.SCROLLED }); }; _this.handleFocus = function (ev) { var _this$clientSiblingRe; var current = ev.target; var dropdownListNode = (_this$clientSiblingRe = _this.clientSiblingRef) === null || _this$clientSiblingRe === void 0 ? void 0 : _this$clientSiblingRe.current; if (_this.state.ready === ReadyState.DONE && !_spotlight["default"].getPointerMode() && current.dataset['index'] != null && dropdownListNode.contains(current)) { var focusedIndex = Number(current.dataset['index']); var lastFocusedKey = getKey({ children: _this.props.children, selected: focusedIndex }); _this.lastFocusedKey = lastFocusedKey; } if (_this.props.onFocus) { _this.props.onFocus(ev); } }; _this.clientSiblingRef = /*#__PURE__*/(0, _react.createRef)(null); _this.state = { prevChildren: props.children, prevFocused: null, prevSelected: _this.props.selected, prevSelectedKey: getKey(props), ready: isSelectedValid(props) ? ReadyState.INIT : ReadyState.DONE }; return _this; } _createClass(_class, [{ key: "componentDidMount", value: function componentDidMount() { if (this.props.handleSpotlightPause) { this.props.handleSpotlightPause(false); } } }, { key: "componentDidUpdate", value: function componentDidUpdate() { if (this.state.ready === ReadyState.INIT) { this.scrollIntoView(); } else if (this.state.ready === ReadyState.SCROLLED) { this.focusSelected(); } else { var key = getKey(this.props); var keysDiffer = key && this.state.prevSelectedKey && key !== this.state.prevSelectedKey; if (keysDiffer || (!key || !this.state.prevSelectedKey) && this.state.prevSelected !== this.props.selected || !(0, _util.compareChildren)(this.state.prevChildren, this.props.children)) { this.resetFocus(keysDiffer); } } } }, { key: "resetFocus", value: function resetFocus(keysDiffer) { var adjustedFocusIndex; if (!keysDiffer && this.lastFocusedKey) { var targetIndex = indexFromKey(this.props.children, this.lastFocusedKey); if (targetIndex >= 0) { adjustedFocusIndex = targetIndex; } } this.setState({ prevChildren: this.props.children, prevFocused: adjustedFocusIndex, prevSelected: this.props.selected, prevSelectedKey: getKey(this.props), ready: ReadyState.INIT }); } }, { key: "focusSelected", value: function focusSelected() { this.setState({ ready: ReadyState.DONE }); } }, { key: "render", value: function render() { var props = _objectSpread({}, this.props); delete props.handleSpotlightPause; return /*#__PURE__*/(0, _jsxRuntime.jsx)(WrappedWithRef, _objectSpread(_objectSpread({}, props), {}, { onFocus: this.handleFocus, outermostRef: this.clientSiblingRef, referrerName: "DropdownList", scrollTo: this.setScrollTo })); } }]); return _class; }(_react.Component), _class.displayName = 'DropdownListSpotlightDecorator', _class.propTypes = { /* * Passed by DropdownBase to resume Spotlight * * @type {Function} */ handleSpotlightPause: _propTypes2["default"].func, /* * Called when an item receives focus. * * @type {Function} */ onFocus: _propTypes2["default"].func, /* * Index of the selected item. * * @type {Number} */ selected: _propTypes2["default"].number }, _class; }); var DropdownListDecorator = (0, _compose["default"])(DropdownListSpotlightDecorator, (0, _IdProvider["default"])({ generateProp: null, prefix: 'dl_' }), (0, _Skinnable["default"])({ variantsProp: 'skinVariants' })); var DropdownList = exports.DropdownList = DropdownListDecorator(DropdownListBase); var _default = exports["default"] = DropdownList;