UNPKG

@enact/sandstone

Version:

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

523 lines (517 loc) 23 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "Tab", { enumerable: true, get: function get() { return _Tab["default"]; } }); exports["default"] = exports.TabLayoutDecorator = exports.TabLayoutContext = exports.TabLayoutBase = exports.TabLayout = void 0; var _handle = require("@enact/core/handle"); var _keymap = require("@enact/core/keymap"); var _kind = _interopRequireDefault(require("@enact/core/kind")); var _util = require("@enact/core/util"); var _I18nDecorator = require("@enact/i18n/I18nDecorator"); var _spotlight = _interopRequireWildcard(require("@enact/spotlight")); var _target = require("@enact/spotlight/src/target"); var _SpotlightContainerDecorator = _interopRequireDefault(require("@enact/spotlight/SpotlightContainerDecorator")); var _Changeable = require("@enact/ui/Changeable"); var _Layout = require("@enact/ui/Layout"); var _resolution = require("@enact/ui/resolution"); var _Toggleable = _interopRequireDefault(require("@enact/ui/Toggleable")); var _Touchable = _interopRequireDefault(require("@enact/ui/Touchable")); var _ViewManager = _interopRequireDefault(require("@enact/ui/ViewManager")); var _propTypes = _interopRequireDefault(require("prop-types")); var _compose = _interopRequireDefault(require("ramda/src/compose")); var _react = require("react"); var _ThemeDecorator = require("../ThemeDecorator"); var _RefocusDecorator = _interopRequireWildcard(require("./RefocusDecorator")); var _TabGroup = _interopRequireDefault(require("./TabGroup")); var _Tab = _interopRequireDefault(require("./Tab")); var _TabLayoutModule = _interopRequireDefault(require("./TabLayout.module.css")); var _PopupTabLayoutModule = _interopRequireDefault(require("../PopupTabLayout/PopupTabLayout.module.css")); var _jsxRuntime = require("react/jsx-runtime"); var _excluded = ["children", "collapsed", "css", "data-spotlight-id", "dimensions", "handleClick", "handleEnter", "handleFlick", "handleFocus", "handleTabsTransitionEnd", "index", "onCollapse", "onSelect", "orientation", "tabOrientation", "tabSize", "tabs", "type"]; 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 _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 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); } /** * Provides a Sandstone-themed TabLayout. * * @module sandstone/TabLayout * @exports TabLayout * @exports TabLayoutBase * @exports TabLayoutContext * @exports TabLayoutDecorator * @exports Tab */ var TabLayoutContext = exports.TabLayoutContext = /*#__PURE__*/(0, _react.createContext)(null); var TouchableCell = (0, _Touchable["default"])(_Layout.Cell); var isTouchMode = function isTouchMode() { return (0, _ThemeDecorator.getLastInputType)() === 'touch'; }; /** * Tabbed Layout component. * * Example: * * ```jsx * <TabLayout> * <Tab title="Tab One"> * <Item>Hello</Item> * </Tab> * <Tab title="Tab Two"> * <Item>Goodbye</Item> * </Tab> * </TabLayout> * ``` * * @class TabLayout * @memberof sandstone/TabLayout * @ui * @public */ var TabLayoutBase = exports.TabLayoutBase = (0, _kind["default"])({ name: 'TabLayout', propTypes: /** @lends sandstone/TabLayout.TabLayout.prototype */{ /** * Sets where this component should attach its tabs and animations. * * "left" and "right" represent true screen left and screen right, while "start" represents * screen left in LTR and screen right in RTL. "end" is the reverse: screen right for LTR * and screen left for RTL. * * @type {('left'|'right'|'start'|'end')} * @default 'start' * @private */ anchorTo: _propTypes["default"].oneOf(['left', 'right', 'start', 'end']), /** * Collection of {@link sandstone/TabLayout.Tab|Tabs} to render. * * @type {Node} * @public */ children: _propTypes["default"].node, /** * Collapses the vertical tab list into icons only. * * Only applies to `orientation="vertical"`. If the tabs do not include icons, a single * collapsed icon will be shown. * * @type {Boolean} * @public */ collapsed: _propTypes["default"].bool, /** * Customizes the component by mapping the supplied collection of CSS class names to the * corresponding internal elements and states of this component. * * The following classes are supported: * * @type {Object} * @public */ css: _propTypes["default"].object, 'data-spotlight-id': _propTypes["default"].string, /** * Specify dimensions for the layout areas. * * All 4 combinations must me supplied: each of the elements, tabs and content in both * collapsed and expanded state. * * @type {{tabs: {collapsed: Number, normal: Number}, content: {expanded: number, normal: number}}} * @default { * tabs: { * collapsed: 228, * normal: 882 * }, * content: { * expanded: null, * normal: null * } * } * @private */ dimensions: _propTypes["default"].shape({ content: _propTypes["default"].shape({ expanded: _propTypes["default"].number, normal: _propTypes["default"].number }).isRequired, tabs: _propTypes["default"].shape({ collapsed: _propTypes["default"].number, normal: _propTypes["default"].number }).isRequired }), /** * The currently selected tab. * * @type {Number} * @default 0 * @public */ index: _propTypes["default"].number, /** * Called when the tabs are collapsed. * * @type {Function} * @public */ onCollapse: _propTypes["default"].func, /** * Called when the tabs are expanded. * * @type {Function} * @public */ onExpand: _propTypes["default"].func, /** * Called when a tab is selected * * @type {Function} * @public */ onSelect: _propTypes["default"].func, /** * Called when the tab collapse or expand animation completes. * * Event payload includes: * * `type` - Always set to "onTabAnimationEnd" * * `collapsed` - `true` when the tabs are collapsed * * @type {Function} * @public */ onTabAnimationEnd: _propTypes["default"].func, /** * Orientation of the tabs. * * @type {('horizontal'|'vertical')} * @default 'vertical' * @public */ orientation: _propTypes["default"].oneOf(['horizontal', 'vertical']), /** * Indicates the content's text direction is right-to-left. * * @type {Boolean} * @private */ rtl: _propTypes["default"].bool, /** * Assign a custom size to horizontal tabs. * * Tabs in the horizontal orientation automatically stretch to fill the available width. * Leave this prop blank to use the default auto-sizing behavior. * Tabs may also be set to a finite width using this property. This accepts numeric pixel * values. Be mindful of the value you provide as values that are too wide will run off the * edge of the screen. * * Only applies to `orientation="horizontal"` at this time. * * @type {Number} * @public */ tabSize: _propTypes["default"].number, /** * Type of TabLayout. * * @type {('normal'|'popup')} * @default 'normal' * @private */ type: _propTypes["default"].oneOf(['normal', 'popup']) }, defaultProps: { anchorTo: 'start', dimensions: { tabs: { collapsed: 216, normal: 882 }, content: { expanded: null, normal: null } }, index: 0, orientation: 'vertical', type: 'normal' }, styles: { css: _TabLayoutModule["default"], className: 'tabLayout', publicClassNames: ['bg', 'button', 'client', 'collapsed', 'content', 'selected', 'tab', 'tabGroup', 'tabLayout', 'tabs', 'tabsExpanded', 'vertical'] }, handlers: { onKeyDown: function onKeyDown(ev, props) { var keyCode = ev.keyCode, target = ev.target; var collapsed = props.collapsed, orientation = props.orientation, spotlightId = props['data-spotlight-id']; var direction = (0, _spotlight.getDirection)(keyCode); if ((0, _handle.forwardWithPrevent)('onKeyDown', ev, props) && direction && collapsed && orientation === 'vertical' && document.querySelector("[data-spotlight-id='".concat(spotlightId, "']")).contains(target) && target.tagName !== 'INPUT') { _spotlight["default"].setPointerMode(false); ev.preventDefault(); _spotlight["default"].set(spotlightId, { navigableFilter: null }); var nextTarget = (0, _target.getTargetByDirectionFromElement)(direction, target); var isNextTargetInTabs = nextTarget && document.querySelector(".".concat(_TabLayoutModule["default"].tabs)).contains(nextTarget); _spotlight["default"].set(spotlightId, { navigableFilter: (0, _RefocusDecorator.getNavigableFilter)(spotlightId, collapsed) }); if (!isNextTargetInTabs && _spotlight["default"].move(direction)) { ev.stopPropagation(); } else if (isNextTargetInTabs && document.querySelector("[data-spotlight-id='".concat(spotlightId, "'] .").concat(_TabLayoutModule["default"].content)).contains(target)) { (0, _handle.forward)('onExpand', ev, props); } } else if ((0, _keymap.is)('enter')(keyCode) && !collapsed && document.querySelector("[data-spotlight-id='".concat(spotlightId, "-tabs-expanded']")).contains(target) && target.tagName !== 'INPUT') { ev.stopPropagation(); } }, onKeyUp: function onKeyUp(ev, props) { var keyCode = ev.keyCode, target = ev.target; var anchorTo = props.anchorTo, collapsed = props.collapsed, orientation = props.orientation, spotlightId = props['data-spotlight-id'], rtl = props.rtl, type = props.type; var popupPanelRef = document.querySelector("[data-spotlight-id='".concat(spotlightId, "'] .").concat(_PopupTabLayoutModule["default"].panel)); var tabLayoutContentRef = document.querySelector("[data-spotlight-id='".concat(spotlightId, "'] .").concat(_TabLayoutModule["default"].content)); if ((0, _handle.forwardWithPrevent)('onKeyUp', ev, props) && (0, _keymap.is)('cancel')(keyCode)) { if (type === 'popup' && popupPanelRef !== null && popupPanelRef !== void 0 && popupPanelRef.contains(target) && (popupPanelRef === null || popupPanelRef === void 0 ? void 0 : popupPanelRef.dataset.index) === '0' || type === 'normal' && !_spotlight["default"].getPointerMode() && tabLayoutContentRef !== null && tabLayoutContentRef !== void 0 && tabLayoutContentRef.contains(target)) { if (collapsed) { (0, _handle.forward)('onExpand', ev, props); } _spotlight["default"].focus("[data-spotlight-id='".concat(spotlightId, "-tabs-expanded']")); ev.stopPropagation(); } } else if ((0, _keymap.is)('enter')(keyCode) && !collapsed && document.querySelector("[data-spotlight-id='".concat(spotlightId, "-tabs-expanded']")).contains(target) && target.tagName !== 'INPUT') { _spotlight["default"].setPointerMode(false); var moveTo; if (orientation === 'vertical') { if (anchorTo === 'left') { moveTo = 'right'; } else if (anchorTo === 'right') { moveTo = 'left'; } else if (anchorTo === 'start') { if (rtl) { moveTo = 'left'; } else { moveTo = 'right'; } } else if (anchorTo === 'end') { if (!rtl) { moveTo = 'left'; } else { moveTo = 'right'; } } } else { moveTo = 'down'; } _spotlight["default"].move(moveTo); } }, onSelect: (0, _handle.handle)((0, _handle.forwardCustom)('onSelect', function (_ref) { var selected = _ref.selected; return { index: selected }; })), handleTabsTransitionEnd: (0, _handle.handle)((0, _handle.forward)('onTransitionEnd'), (0, _handle.forProp)('orientation', 'vertical'), // Validate the transition is from the root node function (ev) { return ev.target.classList.contains(_TabLayoutModule["default"].tabs); }, (0, _handle.forwardCustom)('onTabAnimationEnd', function (ev, _ref2) { var collapsed = _ref2.collapsed; return { collapsed: Boolean(collapsed) }; })), handleFlick: function handleFlick(_ref3, _ref4) { var direction = _ref3.direction, velocityX = _ref3.velocityX; var collapsed = _ref4.collapsed, onCollapse = _ref4.onCollapse, onExpand = _ref4.onExpand; // See the global class 'spotlight-input-touch' to check the input type is touch if (isTouchMode() && direction === 'horizontal') { if (!collapsed && velocityX < 0) { onCollapse(); } else if (collapsed && velocityX > 0) { onExpand(); } } }, handleClick: (0, _handle.handle)(isTouchMode, (0, _handle.forward)('onExpand')), handleFocus: (0, _handle.handle)((0, _handle.not)(isTouchMode), (0, _handle.forward)('onExpand')), handleEnter: function handleEnter(ev, props) { var index = ev.index, previousIndex = ev.previousIndex; if (index > previousIndex) { (0, _handle.forward)('onCollapse', ev, props); } } }, computed: { children: function children(_ref5) { var _children = _ref5.children; return (0, _util.mapAndFilterChildren)(_children, function (child) { return /*#__PURE__*/(0, _jsxRuntime.jsx)(_react.Fragment, { children: child.props.children }); }); }, className: function className(_ref6) { var collapsed = _ref6.collapsed, anchorTo = _ref6.anchorTo, orientation = _ref6.orientation, styler = _ref6.styler; return styler.append({ collapsed: orientation === 'vertical' && collapsed }, "anchor".concat((0, _util.cap)(anchorTo)), orientation); }, style: function style(_ref7) { var dimensions = _ref7.dimensions, orientation = _ref7.orientation, _style = _ref7.style; return _objectSpread(_objectSpread({}, _style), {}, { '--tablayout-expand-collapse-diff': orientation === 'vertical' ? (0, _resolution.scaleToRem)(dimensions.tabs.normal - dimensions.tabs.collapsed) : 0 }); }, tabOrientation: function tabOrientation(_ref8) { var orientation = _ref8.orientation; return orientation === 'vertical' ? 'horizontal' : 'vertical'; }, tabs: function tabs(_ref9) { var children = _ref9.children; var tabs = (0, _util.mapAndFilterChildren)(children, function (child) { return Object.keys(child.props).filter(function (prop) { return prop !== 'children' && prop !== 'id'; }).reduce(function (obj, key) { return _objectSpread(_objectSpread({}, obj), {}, _defineProperty({}, key, child.props[key])); }, {}); }); return tabs; } }, render: function render(_ref10) { var children = _ref10.children, collapsed = _ref10.collapsed, css = _ref10.css, spotlightId = _ref10['data-spotlight-id'], dimensions = _ref10.dimensions, handleClick = _ref10.handleClick, handleEnter = _ref10.handleEnter, handleFlick = _ref10.handleFlick, handleFocus = _ref10.handleFocus, handleTabsTransitionEnd = _ref10.handleTabsTransitionEnd, index = _ref10.index, onCollapse = _ref10.onCollapse, onSelect = _ref10.onSelect, orientation = _ref10.orientation, tabOrientation = _ref10.tabOrientation, tabSize = _ref10.tabSize, tabs = _ref10.tabs, type = _ref10.type, rest = _objectWithoutProperties(_ref10, _excluded); delete rest.anchorTo; delete rest.onExpand; delete rest.onTabAnimationEnd; delete rest.rtl; var contentSize = collapsed ? dimensions.content.expanded : dimensions.content.normal; var isVertical = orientation === 'vertical'; var ContentCell = isVertical ? TouchableCell : _Layout.Cell; var contentCellProps = isVertical ? { onFlick: handleFlick } : null; // Props that are shared between both of the rendered TabGroup components var tabGroupProps = { css: css, onClick: collapsed ? handleClick : null, onFocus: collapsed ? handleFocus : null, onFocusTab: onSelect, onSelect: onSelect, orientation: orientation, selectedIndex: index, tabs: tabs }; // In vertical orientation, render two sets of tabs, one just icons, one with icons and text. return /*#__PURE__*/(0, _jsxRuntime.jsx)(TabLayoutContext.Provider, { value: handleEnter, children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_Layout.Layout, _objectSpread(_objectSpread({}, rest), {}, { orientation: tabOrientation, "data-spotlight-id": spotlightId, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Layout.Cell, { className: css.tabs, shrink: true, onTransitionEnd: handleTabsTransitionEnd, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_TabGroup["default"], _objectSpread(_objectSpread({}, tabGroupProps), {}, { collapsed: isVertical, spotlightId: (0, _RefocusDecorator.getTabsSpotlightId)(spotlightId, isVertical), tabSize: !isVertical ? tabSize : null, spotlightDisabled: !collapsed && isVertical })) }), isVertical ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Layout.Cell, { className: css.tabs + ' ' + css.tabsExpanded, size: dimensions.tabs.normal, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_TabGroup["default"], _objectSpread(_objectSpread({}, tabGroupProps), {}, { spotlightId: (0, _RefocusDecorator.getTabsSpotlightId)(spotlightId, false), spotlightDisabled: collapsed })) }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(ContentCell, _objectSpread(_objectSpread({ size: isVertical ? contentSize : null, className: css.content, component: _ViewManager["default"], index: index, noAnimation: true, onFocus: type === 'normal' && !collapsed ? onCollapse : null, orientation: orientation }, contentCellProps), {}, { children: children }))] })) }); } }); var TabLayoutDecorator = exports.TabLayoutDecorator = (0, _compose["default"])((0, _Toggleable["default"])({ prop: 'collapsed', activate: 'onCollapse', deactivate: 'onExpand' }), (0, _Changeable.Changeable)({ prop: 'index', change: 'onSelect' }), _RefocusDecorator["default"], (0, _SpotlightContainerDecorator["default"])({ // using last-focused so we return to the last focused if it exists but fall through to // default element if no focus has ocurred yet (e.g. on mount) enterTo: 'last-focused', // favor the content when collapsed and the tabs otherwise defaultElement: [".".concat(_TabLayoutModule["default"].horizontal, " .").concat(_TabLayoutModule["default"].tabs, " *"), ".".concat(_TabLayoutModule["default"].collapsed, " .").concat(_TabLayoutModule["default"].content, " *"), ".".concat(_TabLayoutModule["default"].tabsExpanded, " *")] }), (0, _I18nDecorator.I18nContextDecorator)({ rtlProp: 'rtl' })); // Currently not documenting the base output since it's not exported var TabLayout = exports.TabLayout = TabLayoutDecorator(TabLayoutBase); /** * A shortcut to access {@link sandstone/TabLayout.Tab} * * @name Tab * @type {sandstone/TabLayout.Tab} * @static * @memberof sandstone/TabLayout.TabLayout */ TabLayout.Tab = _Tab["default"]; var _default = exports["default"] = TabLayout;