UNPKG

@atlaskit/select

Version:

Select allows users to make a single selection or multiple selections from a list of options.

369 lines (355 loc) 17 kB
/* popup-select-top-layer.tsx generated by @compiled/babel-plugin v3.0.2 */ "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.PopupSelectTopLayer = void 0; require("./popup-select-top-layer.compiled.css"); var _react = _interopRequireWildcard(require("react")); var React = _react; var _runtime = require("@compiled/react/runtime"); var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _bindEventListener = require("bind-event-listener"); var _styles = require("@atlaskit/react-select/styles"); var _index = require("@atlaskit/top-layer/placement-map/index"); var _popover = require("@atlaskit/top-layer/popover/popover"); var _popoverSurface = require("@atlaskit/top-layer/popover-surface"); var _useAnchoredPopover = require("@atlaskit/top-layer/use-anchored-popover"); var _usePopoverId = require("@atlaskit/top-layer/use-popover-id"); var _select = _interopRequireDefault(require("../select")); var _components = require("./components"); var _dummyControl = require("./dummy-control"); var _excluded = ["closeMenuOnSelect", "shouldCloseMenuOnTab", "footer", "searchThreshold", "maxMenuWidth", "minMenuWidth", "maxMenuHeight", "target", "label", "placeholder", "testId", "isOpen", "defaultIsOpen", "spacing", "options", "isSearchable", "components", "styles", "onChange", "onKeyDown", "onOpen", "onClose", "onMenuOpen", "onMenuClose", "popperProps", "shouldPreventEscapePropagation"]; function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } 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) { (0, _defineProperty2.default)(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; } var menuDialogStyles = null; /** * Top-layer implementation of PopupSelect. * * Replaces the legacy PopupSelect rendering pipeline * (react-popper + createPortal + react-focus-lock + @atlaskit/layering + z-index) * with the native Popover API + CSS Anchor Positioning via @atlaskit/top-layer. * * Gated behind the `platform-dst-top-layer` feature flag. */ function PopupSelectTopLayerInner(_ref, forwardedRef) { var _popperProps$placemen, _getLabel; var _ref$closeMenuOnSelec = _ref.closeMenuOnSelect, closeMenuOnSelect = _ref$closeMenuOnSelec === void 0 ? true : _ref$closeMenuOnSelec, _ref$shouldCloseMenuO = _ref.shouldCloseMenuOnTab, shouldCloseMenuOnTab = _ref$shouldCloseMenuO === void 0 ? true : _ref$shouldCloseMenuO, footer = _ref.footer, _ref$searchThreshold = _ref.searchThreshold, searchThreshold = _ref$searchThreshold === void 0 ? 5 : _ref$searchThreshold, _ref$maxMenuWidth = _ref.maxMenuWidth, maxMenuWidth = _ref$maxMenuWidth === void 0 ? 440 : _ref$maxMenuWidth, _ref$minMenuWidth = _ref.minMenuWidth, minMenuWidth = _ref$minMenuWidth === void 0 ? 220 : _ref$minMenuWidth, _ref$maxMenuHeight = _ref.maxMenuHeight, maxMenuHeight = _ref$maxMenuHeight === void 0 ? 300 : _ref$maxMenuHeight, target = _ref.target, label = _ref.label, placeholder = _ref.placeholder, testId = _ref.testId, controlledIsOpen = _ref.isOpen, defaultIsOpen = _ref.defaultIsOpen, _spacing = _ref.spacing, _ref$options = _ref.options, options = _ref$options === void 0 ? [] : _ref$options, _ref$isSearchable = _ref.isSearchable, isSearchable = _ref$isSearchable === void 0 ? true : _ref$isSearchable, consumerComponents = _ref.components, consumerStyles = _ref.styles, onChange = _ref.onChange, onKeyDown = _ref.onKeyDown, onOpen = _ref.onOpen, onClose = _ref.onClose, onMenuOpen = _ref.onMenuOpen, onMenuClose = _ref.onMenuClose, popperProps = _ref.popperProps, _shouldPreventEscapePropagation = _ref.shouldPreventEscapePropagation, selectProps = (0, _objectWithoutProperties2.default)(_ref, _excluded); // State. var isControlled = controlledIsOpen !== undefined; var _useState = (0, _react.useState)(isControlled ? Boolean(controlledIsOpen) : Boolean(defaultIsOpen)), _useState2 = (0, _slicedToArray2.default)(_useState, 2), internalIsOpen = _useState2[0], setInternalIsOpen = _useState2[1]; var isOpen = isControlled ? Boolean(controlledIsOpen) : internalIsOpen; var selectRef = (0, _react.useRef)(null); var triggerRef = (0, _react.useRef)(null); var menuRef = (0, _react.useRef)(null); var popoverRef = (0, _react.useRef)(null); var popoverId = (0, _usePopoverId.usePopoverId)(); // Placement. var legacyPlacement = (_popperProps$placemen = popperProps === null || popperProps === void 0 ? void 0 : popperProps.placement) !== null && _popperProps$placemen !== void 0 ? _popperProps$placemen : 'bottom-start'; // `popup-select` historically defaulted to an `[along, away] = [0, 8]` popper offset // modifier; consumers can override it via `popperProps.modifiers`. Honour both here so // the top-layer rendering matches the legacy popper output. var legacyOffset = (0, _react.useMemo)(function () { var _modifiers$find; var modifiers = popperProps === null || popperProps === void 0 ? void 0 : popperProps.modifiers; var value = modifiers === null || modifiers === void 0 || (_modifiers$find = modifiers.find(function (modifier) { return (modifier === null || modifier === void 0 ? void 0 : modifier.name) === 'offset'; })) === null || _modifiers$find === void 0 || (_modifiers$find = _modifiers$find.options) === null || _modifiers$find === void 0 ? void 0 : _modifiers$find.offset; if (Array.isArray(value) && value.length === 2) { return [Number(value[0]) || 0, Number(value[1]) || 0]; } return [0, 8]; }, [popperProps === null || popperProps === void 0 ? void 0 : popperProps.modifiers]); var topLayerPlacement = (0, _react.useMemo)(function () { return (0, _index.fromLegacyPlacement)({ legacy: legacyPlacement, offset: legacyOffset }); }, [legacyPlacement, legacyOffset]); (0, _useAnchoredPopover.useAnchoredPopover)({ anchorRef: triggerRef, popoverRef: popoverRef, placement: topLayerPlacement, isOpen: isOpen }); // Merged components. var mergedComponents = (0, _react.useMemo)(function () { return _objectSpread(_objectSpread({}, _components.defaultComponents), consumerComponents); }, [consumerComponents]); // Default styles. var defaultStyles = (0, _react.useMemo)(function () { return { groupHeading: function groupHeading(provided) { return _objectSpread(_objectSpread({}, provided), {}, { color: "var(--ds-text-subtlest, #6B6E76)" }); } }; }, []); // Utils. var getItemCount = (0, _react.useCallback)(function () { return options.reduce(function (count, groupOrOption) { var group = groupOrOption; if (group.options) { return count + group.options.length; } return count + 1; }, 0); }, [options]); var showSearchControl = isSearchable && getItemCount() > searchThreshold; var getMaxHeight = (0, _react.useCallback)(function () { var _selectRef$current$se; if (!selectRef.current) { return maxMenuHeight; } var controlRef = (_selectRef$current$se = selectRef.current.select) === null || _selectRef$current$se === void 0 ? void 0 : _selectRef$current$se.controlRef; var offsetHeight = showSearchControl && controlRef ? controlRef.offsetHeight : 0; return maxMenuHeight - offsetHeight; }, [maxMenuHeight, showSearchControl]); var getLabel = (0, _react.useCallback)(function () { if (label) { return label; } if (typeof placeholder === 'string') { return placeholder; } return undefined; }, [label, placeholder]); // Open / Close. var open = (0, _react.useCallback)(function () { if (!isControlled) { setInternalIsOpen(true); } onOpen === null || onOpen === void 0 || onOpen(); onMenuOpen === null || onMenuOpen === void 0 || onMenuOpen(); }, [isControlled, onOpen, onMenuOpen]); var close = (0, _react.useCallback)(function () { if (!isControlled) { setInternalIsOpen(false); } onClose === null || onClose === void 0 || onClose(); onMenuClose === null || onMenuClose === void 0 || onMenuClose(); }, [isControlled, onClose, onMenuClose]); /** * For backwards compatibility */ (0, _react.useImperativeHandle)(forwardedRef, function () { return { open: open, close: close, get selectRef() { return selectRef.current; }, get menuRef() { return menuRef.current; }, get targetRef() { return triggerRef.current; } }; }, [close, open]); // Sync controlled isOpen. (0, _react.useEffect)(function () { if (!isControlled) { return; } if (controlledIsOpen) { onOpen === null || onOpen === void 0 || onOpen(); onMenuOpen === null || onMenuOpen === void 0 || onMenuOpen(); } else { onClose === null || onClose === void 0 || onClose(); onMenuClose === null || onMenuClose === void 0 || onMenuClose(); } // Only react to changes in the controlled value // eslint-disable-next-line react-hooks/exhaustive-deps }, [controlledIsOpen]); // Open the react-select menu when isOpen becomes true. (0, _react.useEffect)(function () { if (isOpen && selectRef.current) { var _selectRef$current$se2; (_selectRef$current$se2 = selectRef.current.select) === null || _selectRef$current$se2 === void 0 || _selectRef$current$se2.openMenu('first'); } }, [isOpen]); // Event Handlers. var handleTargetKeyDown = (0, _react.useCallback)(function (event) { if (event.key === 'ArrowDown') { open(); } }, [open]); // Open the menu when the trigger is clicked. Mirrors the legacy // `PopupSelect` global-click listener: consumers are not required to // spread an `onClick`, so we walk up from the event target to the // trigger ref instead. (0, _react.useEffect)(function () { if (typeof window === 'undefined') { return; } return (0, _bindEventListener.bind)(window, { type: 'click', listener: function listener(event) { var target = event.target; if (!(target instanceof Element)) { return; } var trigger = triggerRef.current; if (!trigger || !trigger.contains(target)) { return; } if (isOpen) { close(); return; } open(); }, options: { capture: true } }); }, [isOpen, open, close]); // Focus restoration on close is handled automatically by Popover // based on the content role (role="dialog" -> auto-restore). var handleOnClose = (0, _react.useCallback)(function (_ref2) { var _reason = _ref2.reason; if (isControlled) { // For controlled mode, notify consumer but do not change internal state onClose === null || onClose === void 0 || onClose(); onMenuClose === null || onMenuClose === void 0 || onMenuClose(); return; } close(); }, [close, isControlled, onClose, onMenuClose]); var handleSelectChange = (0, _react.useCallback)(function (value, actionMeta) { if (closeMenuOnSelect && actionMeta.action !== 'clear') { var _triggerRef$current; close(); // Return focus to trigger after selection (_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 || _triggerRef$current.focus(); } onChange === null || onChange === void 0 || onChange(value, actionMeta); }, [closeMenuOnSelect, close, onChange]); var handleKeyDown = (0, _react.useCallback)(function (event) { var tabEvent = event.key === 'Tab'; if (shouldCloseMenuOnTab && tabEvent) { var _triggerRef$current2; close(); (_triggerRef$current2 = triggerRef.current) === null || _triggerRef$current2 === void 0 || _triggerRef$current2.focus(); } onKeyDown === null || onKeyDown === void 0 || onKeyDown(event); }, [shouldCloseMenuOnTab, close, onKeyDown]); // Select components. // Type assertion needed because defaultComponents uses concrete OptionType/boolean // but Select expects the generic Option/IsMulti parameters. The components are // structurally compatible regardless of the generic parameters. var selectComponentsMerged = (0, _react.useMemo)(function () { return _objectSpread(_objectSpread({}, mergedComponents), {}, { Control: showSearchControl ? mergedComponents.Control : _dummyControl.DummyControl }); }, [mergedComponents, showSearchControl]); var getSelectRef = (0, _react.useCallback)(function (ref) { selectRef.current = ref; }, []); var triggerProps = { ref: function ref(node) { triggerRef.current = node; }, // No `onClick` on `triggerProps`: the trigger click is observed via // the capture-phase global `click` listener in the effect above, // matching the legacy `PopupSelect` contract for consumers that do // not spread `triggerProps` onto their target. onKeyDown: handleTargetKeyDown, // Maintain existing aria attribute format for backwards compatibility. // Should technically be 'dialog' instead of 'true', but preserving // legacy behavior. See go/DSP-22283 'aria-haspopup': 'true', 'aria-expanded': isOpen, // Always emit aria-controls referencing the controlled element. // Matches the behavior of `getAriaForTrigger` (see // `@atlaskit/top-layer/get-aria-for-trigger`) and aligns with the // WAI-ARIA spec recommendation. 'aria-controls': popoverId, isOpen: isOpen }; return /*#__PURE__*/React.createElement(_react.Fragment, null, target === null || target === void 0 ? void 0 : target(triggerProps), /*#__PURE__*/React.createElement(_popover.Popover, { ref: popoverRef, id: popoverId, role: "dialog", label: (_getLabel = getLabel()) !== null && _getLabel !== void 0 ? _getLabel : 'Popup select', isOpen: isOpen, onClose: handleOnClose, shouldAnimate: true, placement: topLayerPlacement, testId: testId && "".concat(testId, "--content") }, /*#__PURE__*/React.createElement(_popoverSurface.PopoverSurface, null, /*#__PURE__*/React.createElement("div", { ref: menuRef, // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop style: { maxWidth: maxMenuWidth, minWidth: minMenuWidth }, "data-testid": testId && "".concat(testId, "--menu"), className: (0, _runtime.ax)(["_2rko1mok _bfhk1bhr _16qs130s"]) }, /*#__PURE__*/React.createElement(_select.default, (0, _extends2.default)({ label: getLabel(), backspaceRemovesValue: false, controlShouldRenderValue: false, isClearable: false, tabSelectsValue: false, placeholder: placeholder, ref: getSelectRef // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props -- preserving legacy PopupSelect API during migration }, selectProps, { menuRenderMode: "inline", options: options, isSearchable: showSearchControl, styles: (0, _styles.mergeStyles)(defaultStyles, consumerStyles || {}), maxMenuHeight: getMaxHeight(), components: selectComponentsMerged, onChange: handleSelectChange, onKeyDown: handleKeyDown, testId: testId })), footer)))); } var PopupSelectTopLayer = exports.PopupSelectTopLayer = /*#__PURE__*/(0, _react.forwardRef)(PopupSelectTopLayerInner);