curls
Version:
💪 Responsive, expressive UI primitives for React written with Style Hooks and Emotion
457 lines (402 loc) • 12.4 kB
JavaScript
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,
}
}