UNPKG

curls

Version:

💪 Responsive, expressive UI primitives for React written with Style Hooks and Emotion

457 lines (402 loc) • 12.4 kB
'use strict' exports.__esModule = true exports.Popover = exports.PopoverMe = exports.PopoverBox = exports.usePopoverBox = exports.PopoverConsumer = exports.usePopoverContext = exports.PopoverContext = void 0 var _core = require('@emotion/core') var _react = _interopRequireWildcard(require('react')) var _core2 = require('@style-hooks/core') var _throttled = _interopRequireDefault( require('@react-hook/window-size/throttled') ) var _passiveLayoutEffect = _interopRequireDefault( require('@react-hook/passive-layout-effect') ) var _mergedRef = _interopRequireDefault(require('@react-hook/merged-ref')) var _switch = _interopRequireDefault(require('@react-hook/switch')) var _windowScroll = _interopRequireDefault(require('@react-hook/window-scroll')) var _array = _interopRequireDefault(require('empty/array')) var _Box = require('../Box') var _Fade = require('../Fade') var _useBreakpointValueParser = _interopRequireDefault( require('../useBreakpointValueParser') ) var _utils = require('../utils') var _utils2 = require('./utils') var _object = _interopRequireDefault(require('empty/object')) function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj } else { var newObj = {} if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {} if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc) } else { newObj[key] = obj[key] } } } } newObj.default = obj return newObj } } 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 _extends() { _extends = Object.assign || function(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key] } } } return target } return _extends.apply(this, arguments) } const PopoverContext = _react.default.createContext({}), {Consumer: PopoverConsumer} = PopoverContext, usePopoverContext = () => (0, _react.useContext)(PopoverContext) exports.PopoverConsumer = PopoverConsumer exports.usePopoverContext = usePopoverContext exports.PopoverContext = PopoverContext const defaultStyles = { name: '1o2bxov', styles: 'display:flex;position:fixed;z-index:1001;', }, withoutPop = { ref: 0, triggerRef: 0, style: 0, } const defaultTransition = ({ isOpen, /*, placement*/ }) => (0, _Fade.useFade)({ visible: isOpen, from: 0, to: 1, }) function _ref(match) { return match.matches } const usePopoverBox = props => { const popover = usePopoverContext() props = (0, _core2.useStyles)( 'popover', _object.default, (0, _utils.pushCss)(props, [defaultStyles, popover.css]) ) props.id = props.id || popover.id props.tabIndex = props.tabIndex || '0' const {css} = (props.transition || defaultTransition)({ isOpen: popover.isOpen, placement: popover.placement, }) delete props.transition props.css = props.css ? [defaultStyles, css].concat(props.css) : [defaultStyles, css] props.style = props.style ? _extends({}, popover.style, props.style) : popover.style return props }, PopoverBox = _react.default.forwardRef((props_, ref) => { const _useBox = (0, _Box.useBox)(usePopoverBox(props_)), {placement = 'bottom', portal, children} = _useBox, props = _objectWithoutProperties(_useBox, [ 'placement', 'portal', 'children', ]) const matches = (0, _useBreakpointValueParser.default)(placement) const popover = usePopoverContext() // handles repositioning the popover // Yes this is correct, it's useEffect, not useLayoutEffect // Just move on. ;(0, _react.useEffect)(() => { if (typeof placement === 'function') { popover.reposition(placement) } else if (matches) { popover.reposition(matches.filter(_ref).pop().value) } }, [placement, matches]) // handles closing the popover when the ESC key is pressed function _ref2() { return popover.ref.current.focus() } function _ref3(event) { return parseInt(event.keyCode) === 27 && popover.close() } ;(0, _passiveLayoutEffect.default)(() => { if (popover.isOpen) { setTimeout(_ref2, 100) const callback = _ref3 popover.ref.current.addEventListener('keyup', callback) return () => popover.ref.current.removeEventListener('keyup', callback) } }, [popover.isOpen]) props.ref = (0, _mergedRef.default)(popover.ref, ref) props.children = typeof children === 'function' ? children((0, _utils.objectWithoutProps)(popover, withoutPop)) : children return (0, _utils.portalize)( (0, _core2.createElement)('div', props), portal ) }) exports.PopoverBox = PopoverBox exports.usePopoverBox = usePopoverBox let ID = 0 const PopoverContainer = _react.default.memo( ({ open, close, toggle, isOpen, containPolicy, windowSize, scrollY, children, }) => { const triggerRef = (0, _react.useRef)(null), popoverRef = (0, _react.useRef)(null), id = (0, _react.useRef)(`curls.popover.${ID++}`).current, [{style, requestedPlacement, placement}, setState] = (0, _react.useState)( { style: {}, placement: null, requestedPlacement: null, } ), reposition = (0, _react.useCallback)( nextPlacement => { setState( (0, _utils2.setPlacementStyle)( nextPlacement, triggerRef.current, popoverRef.current, containPolicy ) ) }, [containPolicy] ), childContext = (0, _react.useMemo)( () => ({ isOpen, open, close, toggle, id, style, ref: popoverRef, placement, reposition, triggerRef, }), [isOpen, open, close, toggle, placement, reposition, style] ) ;(0, _react.useEffect)(() => { isOpen && reposition(requestedPlacement) }, [isOpen, reposition, scrollY, windowSize[0], windowSize[1]]) return (0, _core.jsx)(PopoverContext.Provider, { value: childContext, children: typeof children === 'function' ? children(childContext) : children, }) }, ( prev, next // bails out if the popover is closed and was closed ) => // and the children didn't change (next.isOpen === false && prev.isOpen === false && prev.children === next.children) || // bails out if all else is equal (prev.children === next.children && prev.isOpen === next.isOpen && prev.windowSize[0] === next.windowSize[0] && prev.windowSize[1] === next.windowSize[1] && prev.scrollY === next.scrollY && prev.containPolicy === next.containPolicy) ) const PopoverMe = props => { const {children, on, tabIndex} = props const matches = (0, _useBreakpointValueParser.default)(on), {isOpen, open, close, toggle, id} = usePopoverContext(), elementRef = (0, _react.useRef)(null), ref = (0, _mergedRef.default)(usePopoverContext().triggerRef, elementRef), seen = (0, _react.useRef)(false) // returns the focus to the trigger when the popover box closes if focus is // not an event that triggers opening the popover ;(0, _passiveLayoutEffect.default)(() => { if (isOpen === false) { if (seen.current === true) { let isTriggeredByFocus = false for (let match of matches) if (match.matches === true && match.value === 'focus') { isTriggeredByFocus = true break } if (!isTriggeredByFocus) elementRef.current.focus() } seen.current = true } }, [isOpen]) // handles trigger events function _ref4(e) { e.stopPropagation() toggle() } function _ref5(args) { return elementRef.current.removeEventListener(...args) } ;(0, _passiveLayoutEffect.default)(() => { if (elementRef.current && Array.isArray(matches)) { const listeners = [] const addListener = (...args) => { listeners.push(args) elementRef.current.addEventListener(...args) } for (let match of matches) { if (match.matches === true) { switch (match.value) { case 'hover': addListener('mouseenter', open) addListener('mouseleave', close) break case 'focus': addListener('focus', open) // addListener('blur', close) break case 'click': addListener('click', _ref4) break } } } return () => { listeners.forEach(_ref5) } } }, [elementRef.current, matches, open, close, toggle]) return _react.default.cloneElement(children, { tabIndex: children.props.tabIndex || (typeof tabIndex === 'string' ? tabIndex : undefined), 'aria-controls': props['aria-controls'] || id, 'aria-haspopup': 'true', 'aria-expanded': String(isOpen), ref, }) } exports.PopoverMe = PopoverMe const ScrollPositioner = props => _react.default.createElement( PopoverContainer, _extends( { scrollY: (0, _windowScroll.default)( props.repositionOnScroll === true ? 30 : props.repositionOnScroll ), }, props ) ) const ResizePositioner = props => { props = _extends({}, props) props.windowSize = (0, _throttled.default)(1280, 720, { fps: props.repositionOnResize === true ? 30 : props.repositionOnResize, }) return _react.default.createElement( props.repositionOnScroll ? ScrollPositioner : PopoverContainer, props ) } const Popover = ({ open, initialOpen, repositionOnResize = 0, repositionOnScroll = 0, containPolicy = 'flip', children, }) => { let [isOpen, toggle] = (0, _switch.default)(initialOpen) isOpen = open === void 0 || open === null ? isOpen : open return _react.default.createElement( repositionOnResize ? ResizePositioner : repositionOnScroll ? ScrollPositioner : PopoverContainer, { children, open: toggle.on, close: toggle.off, toggle, isOpen, containPolicy, windowSize: _array.default, repositionOnResize, repositionOnScroll, } ) } exports.Popover = Popover if (process.env.NODE_ENV !== 'production') { const PropTypes = require('prop-types') Popover.displayName = 'Popover' PopoverBox.displayName = 'PopoverBox' Popover.propTypes = { open: PropTypes.bool, initialOpen: PropTypes.bool, repositionOnResize: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]), repositionOnScroll: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]), containPolicy: PropTypes.oneOfType([ PropTypes.func, PropTypes.string, PropTypes.bool, ]), } PopoverBox.propTypes = { placement: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), transition: PropTypes.func, } }