UNPKG

@onesy/ui-react

Version:
509 lines (479 loc) 25.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireDefault(require("react")); var _utils = require("@onesy/utils"); var _styleReact = require("@onesy/style-react"); var _Portal = _interopRequireDefault(require("../Portal")); var _jsxRuntime = require("react/jsx-runtime"); 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; } const valuesDefault = { x: 0, y: 0, switch: false, init: true }; const Append = props_ => { var _theme$ui, _theme$ui2, _theme$elements, _props, _refs$root$current4; const theme = (0, _styleReact.useOnesyTheme)(); const props = _objectSpread(_objectSpread(_objectSpread({}, theme === null || theme === void 0 || (_theme$ui = theme.ui) === null || _theme$ui === void 0 || (_theme$ui = _theme$ui.elements) === null || _theme$ui === void 0 || (_theme$ui = _theme$ui.all) === null || _theme$ui === void 0 || (_theme$ui = _theme$ui.props) === null || _theme$ui === void 0 ? void 0 : _theme$ui.default), theme === null || theme === void 0 || (_theme$ui2 = theme.ui) === null || _theme$ui2 === void 0 || (_theme$ui2 = _theme$ui2.elements) === null || _theme$ui2 === void 0 || (_theme$ui2 = _theme$ui2.onesyAppend) === null || _theme$ui2 === void 0 || (_theme$ui2 = _theme$ui2.props) === null || _theme$ui2 === void 0 ? void 0 : _theme$ui2.default), props_); const Portal = (theme === null || theme === void 0 || (_theme$elements = theme.elements) === null || _theme$elements === void 0 ? void 0 : _theme$elements.Portal) || _Portal.default; const { open, portal = false, accelerated = true, anchor, anchorElement: anchorElement_, offset = [0, 0], padding = [0, 0], paddingUnfollow = props.padding || [0, 0], inset: inset_, position: position_ = 'bottom', alignment: alignment_ = 'end', switch: switch_ = true, overflow = true, unfollow = false, // @ts-ignore onScreen = true, style: style_, update, element, parent: parentElement, additional, children } = props; const [init, setInit] = _react.default.useState(false); const [values, setValues] = _react.default.useState(valuesDefault); const refs = { root: _react.default.useRef(undefined), element: _react.default.useRef(undefined), values: _react.default.useRef(values), alignment: _react.default.useRef(undefined), position: _react.default.useRef(undefined), portal: _react.default.useRef(undefined), props: _react.default.useRef(undefined), anchor: _react.default.useRef(undefined), additional: _react.default.useRef(undefined) }; refs.alignment.current = alignment_; if (theme.direction === 'rtl' && ['top', 'bottom'].includes(position_)) { if (alignment_ === 'start') refs.alignment.current = 'end';else if (alignment_ === 'end') refs.alignment.current = 'start'; } refs.position.current = position_; refs.portal.current = portal; refs.anchor.current = anchor; refs.additional.current = additional; const anchorElement = anchorElement_ !== null && anchorElement_ !== void 0 && anchorElement_.current ? anchorElement_ === null || anchorElement_ === void 0 ? void 0 : anchorElement_.current : anchorElement_; if (anchorElement) refs.root.current = anchorElement; refs.props.current = props; const onScroll = () => { make(); }; const observerMethod = mutations => { for (const mutation of mutations) { if (mutation.target === refs.root.current ? // Root attributes or childList ['attributes', 'childList'].includes(mutation.type) && [null, undefined, 'style'].includes(mutation.attributeName) : // or subtree's childList ['attributes', 'childList'].includes(mutation.type) && [null, undefined, 'style'].includes(mutation.attributeName)) { if (refs.anchor.current === undefined) make(); } } }; _react.default.useEffect(() => { var _refs$root$current; const rootWindow = (0, _utils.isEnvironment)('browser') ? ((_refs$root$current = refs.root.current) === null || _refs$root$current === void 0 || (_refs$root$current = _refs$root$current.ownerDocument) === null || _refs$root$current === void 0 ? void 0 : _refs$root$current.defaultView) || window : undefined; make(); // Scroll rootWindow.addEventListener('scroll', onScroll, true); // Init setInit(true); return () => { rootWindow.removeEventListener('scroll', onScroll); }; }, []); // Anchor _react.default.useEffect(() => { if (init) { if (open) make();else { if (refs.props.current.clearOnClose) setValues(valuesDefault); } } }, [open]); // Anchor _react.default.useEffect(() => { if (init) { if (anchor !== null && anchor !== void 0 && anchor.x && anchor !== null && anchor !== void 0 && anchor.y) make(); } }, [anchor]); // Anchor _react.default.useEffect(() => { make(); }, [anchorElement]); // Anchor element _react.default.useEffect(() => { // Resize const observer = new MutationObserver(observerMethod); try { if (refs.root.current) { observer.observe(refs.root.current, { attributes: true, childList: true, subtree: true }); } } catch (error) {} return () => { if (refs.root.current) { observer.disconnect(); } }; }, [anchor, refs.root.current]); // Element resize _react.default.useEffect(() => { // Resize const observer_0 = new MutationObserver(observerMethod); try { if (refs.element.current) { observer_0.observe(refs.element.current, { attributes: true, childList: true, subtree: true }); } } catch (error_0) {} return () => { if (refs.element.current) { observer_0.disconnect(); } }; }, [anchor, refs.element.current]); // Update _react.default.useEffect(() => { if (init) make(); }, [update]); // Update _react.default.useEffect(() => { if (init) { if ((0, _utils.is)('function', refs.props.current.onUpdate)) refs.props.current.onUpdate(values); } }, [values]); const getBoundingRect = elementHTML => new Promise(async (resolve, reject) => { if (!(elementHTML !== null && elementHTML !== void 0 && elementHTML.getBoundingClientRect)) return; let tries = 5; while (tries) { const valueRect = elementHTML.getBoundingClientRect(); if (valueRect !== null && valueRect !== void 0 && valueRect.height && valueRect !== null && valueRect !== void 0 && valueRect.width) return resolve(valueRect); tries--; await (0, _utils.wait)(40); } }); const getValues = async () => { var _ref; if (!((refs.root.current || refs.anchor.current) && refs.element.current)) return; const wrapperRect = await getBoundingRect((_ref = refs.root.current || refs.element.current) === null || _ref === void 0 ? void 0 : _ref.parentElement); if (!wrapperRect) return; const resolve_0 = () => { if (!anchor) return; if (!portal) { anchor.x = anchor.x - wrapperRect.x; anchor.y = anchor.y - wrapperRect.y; } return anchor; }; // Anchor relative to parent values const anchor_ = resolve_0(); const rect = { root: anchor_ || (await getBoundingRect(refs.root.current)), element: await getBoundingRect(refs.element.current) }; const rectOffset = { root: { x: refs.root.current ? refs.root.current.offsetLeft : anchor_ === null || anchor_ === void 0 ? void 0 : anchor_.x, y: refs.root.current ? refs.root.current.offsetTop : anchor_ === null || anchor_ === void 0 ? void 0 : anchor_.y, width: refs.root.current ? refs.root.current.offsetLeft + refs.root.current.offsetWidth : (anchor_ === null || anchor_ === void 0 ? void 0 : anchor_.x) + (anchor_ === null || anchor_ === void 0 ? void 0 : anchor_.width), height: refs.root.current ? refs.root.current.offsetTop + refs.root.current.offsetHeight : (anchor_ === null || anchor_ === void 0 ? void 0 : anchor_.y) + (anchor_ === null || anchor_ === void 0 ? void 0 : anchor_.height) }, element: { x: refs.element.current.offsetLeft, y: refs.element.current.offsetTop, width: refs.element.current.offsetLeft + refs.element.current.offsetWidth, height: refs.element.current.offsetTop + refs.element.current.offsetHeight } }; return { rect, rectOffset }; }; const make = async (value = { position: refs.position.current, alignment: refs.alignment.current, inset: inset_, switch: false }, valueMeasurements_) => { var _refs$root$current2, _ref2, _refs$root$current3; const valueMeasurements = valueMeasurements_ !== undefined ? valueMeasurements_ : await getValues(); if (!valueMeasurements || valueMeasurements.rect.element.width === 0 && valueMeasurements.rect.element.height === 0) return; const rootDocument = (0, _utils.isEnvironment)('browser') ? ((_refs$root$current2 = refs.root.current) === null || _refs$root$current2 === void 0 ? void 0 : _refs$root$current2.ownerDocument) || window.document : undefined; const wrapperRect_0 = (overflow || switch_) && (refs.root.current || refs.element.current).parentElement.getBoundingClientRect(); const scrollableParents = (0, _utils.element)(refs.root.current).parents().filter(item => { if (!(item instanceof Element)) return; const overflow_ = window.getComputedStyle(item).overflow; return (overflow_.includes('auto') || overflow_.includes('hidden')) && (!refs.portal.current || item.clientHeight !== item.scrollHeight || item.clientHeight > window.innerHeight || item.clientWidth > window.innerWidth); }); // If no parents, ie. anchor // add rootDocument.body as an only value if (!scrollableParents.length) scrollableParents.push(rootDocument.body); const { position, alignment, inset, switch: switched } = value; const { rect: rect_0 } = valueMeasurements; let { rectOffset: rectOffset_0 } = valueMeasurements; // We need both root and element refs // to make our values for it const values_ = { x: 0, y: 0 }; const rootX = portal ? rect_0.root.x : rectOffset_0.root.x; const rootY = portal ? rect_0.root.y : rectOffset_0.root.y; const rootBottom = portal ? rect_0.root.bottom : rectOffset_0.root.y + rect_0.root.height; const rootRight = portal ? rect_0.root.right : rectOffset_0.root.x + rect_0.root.width; const parent_ = (_ref2 = parentElement !== undefined ? parentElement : portal ? rootDocument.body : (_refs$root$current3 = refs.root.current) === null || _refs$root$current3 === void 0 ? void 0 : _refs$root$current3.parentElement) === null || _ref2 === void 0 ? void 0 : _ref2.getBoundingClientRect(); // Top, Bottom if (['top', 'bottom'].includes(position)) { if (alignment === 'start') values_.x = rootX; if (!alignment || alignment === 'center') values_.x = rootX + (rect_0.root.width - rect_0.element.width) / 2; if (alignment === 'end') values_.x = rootX + rect_0.root.width - rect_0.element.width; if (position === 'top') { values_.y = rootBottom - (parent_.height || 0) - offset[1] - rect_0.root.height; if (inset) values_.y = rootBottom - (parent_.height || 0) - rect_0.root.height + rect_0.element.height + offset[1]; } else { values_.y = rootY + offset[1] + rect_0.root.height; if (inset) values_.y = rootY + rect_0.root.height - rect_0.element.height - offset[1]; } } // Left if (['left', 'right'].includes(position)) { if (alignment === 'start') values_.y = rootY; if (!alignment || alignment === 'center') values_.y = rootY + (rect_0.root.height - rect_0.element.height) / 2; if (alignment === 'end') values_.y = rootY + rect_0.root.height - rect_0.element.height; if (position === 'left') { values_.x = rootRight - (parent_.width || 0) - offset[0] - rect_0.root.width; if (inset) values_.x = rootRight - (parent_.width || 0) - rect_0.root.width + rect_0.element.width + offset[0]; } else { values_.x = rootX + offset[0] + rect_0.root.width; if (inset) values_.x = rootX + rect_0.root.width - rect_0.element.width - offset[0]; } } // Absolute position if (portal) { values_.y += rootDocument.documentElement.scrollTop; values_.x += rootDocument.documentElement.scrollLeft; } // Overflow if (overflow) { // If x or y is out of bounds of the parent // or window push them to 0 value // only if that value doesn't unfollow them from the element // or unfollow them if unfollow is true if (portal) rectOffset_0 = rect_0; const top = portal ? rootDocument.documentElement.scrollTop : 0; const left = portal ? rootDocument.documentElement.scrollLeft : 0; const rootY_ = !portal ? wrapperRect_0.y + rectOffset_0.root.y : rect_0.root.y; const valueY = !portal ? wrapperRect_0.y + values_.y : values_.y; const rootX_ = !portal ? wrapperRect_0.x + rectOffset_0.root.x : rect_0.root.x; const valueX = !portal ? wrapperRect_0.x + values_.x : values_.x; const wrapperRectY = !portal ? wrapperRect_0.y : 0; const wrapperRectX = !portal ? wrapperRect_0.x : 0; if (['left', 'right'].includes(position)) { // All parents that are scrollable const valuesY = [values_.y]; let result = values_.y; scrollableParents.forEach(parent => { const scrollParentRect = parent.getBoundingClientRect(); const scrollParentY = scrollParentRect.y - Math.abs(rect_0.root.y); const valueScrollParentY = valueY - scrollParentRect.y; // top if (valueY - top <= 0 + padding[1] || valueScrollParentY - top <= 0 + padding[1]) { if (rootY_ + rect_0.root.height > 0 || unfollow) { const mathValues = [values_.y, top, scrollParentY, scrollParentRect.y - wrapperRectY + top, 0]; if (!portal) mathValues.push(rectOffset_0.root.y - rootY_); values_.y = Math.max(...mathValues); // padding const padding_ = values_.y > rectOffset_0.root.y + rect_0.root.height + top && unfollow ? paddingUnfollow : padding; const scrollRoot = scrollParentRect.y - wrapperRectY >= 0; values_.y += (0, _utils.clamp)(Math.abs(values_.y - (scrollRoot ? scrollParentRect.y : 0) + wrapperRectY - padding_[1]), 0, padding_[1]); if (!unfollow) values_.y = (0, _utils.clamp)(values_.y, Number.MIN_SAFE_INTEGER, rectOffset_0.root.y + rect_0.root.height + top); } else values_.y = rectOffset_0.root.y + rect_0.root.height + top; valuesY.push(values_.y); result = Math.max(...valuesY); } // bottom if (valueY + rect_0.element.height - top >= window.innerHeight - padding[1] || values_.y + rect_0.element.height - top >= scrollParentRect.y + scrollParentRect.height - wrapperRectY - padding[1]) { if (rect_0.root.y < window.innerHeight || rectOffset_0.root.y < scrollParentRect.y + scrollParentRect.height - wrapperRectY || unfollow) { const mathValues_0 = [values_.y, window.innerHeight - wrapperRectY - rect_0.element.height + top, scrollParentRect.y + scrollParentRect.height - wrapperRectY - rect_0.element.height + top]; values_.y = Math.abs(Math.min(...mathValues_0)); // padding const padding__0 = values_.y < rectOffset_0.root.y - rect_0.element.height + top && unfollow ? paddingUnfollow : padding; const scrollRoot_0 = scrollParentRect.y - wrapperRectY >= 0; values_.y -= (0, _utils.clamp)(Math.abs(values_.y - ((scrollRoot_0 ? scrollParentRect.y + scrollParentRect.height : window.innerHeight) - wrapperRectY - rect_0.element.height - padding__0[1])), 0, padding__0[1]); if (!unfollow) values_.y = (0, _utils.clamp)(values_.y, rectOffset_0.root.y - rect_0.element.height + top, Number.MAX_SAFE_INTEGER); } else values_.y = rectOffset_0.root.y - rect_0.element.height + top; valuesY.push(values_.y); result = Math.min(...valuesY); } // Reset values_.y = valuesY[0]; }); values_.y = result; } if (['top', 'bottom'].includes(position)) { // All parents that are scrollable const valuesX = [values_.x]; let result_0 = values_.x; scrollableParents.forEach(parent_0 => { const scrollParentRect_0 = parent_0.getBoundingClientRect(); const scrollParentX = scrollParentRect_0.x - Math.abs(rect_0.root.x); const valueScrollParentX = valueX - scrollParentRect_0.x; // left if (valueX - left <= 0 + padding[0] || valueScrollParentX - left <= 0 + padding[0]) { if (rootX_ + rect_0.root.width > 0 || unfollow) { const mathValues_1 = [values_.x, left, scrollParentX, scrollParentRect_0.x - wrapperRectX + left, 0]; if (!portal) mathValues_1.push(rectOffset_0.root.x - rootX_); values_.x = Math.max(...mathValues_1); // padding const padding__1 = values_.x > rectOffset_0.root.x + rect_0.root.width + left && unfollow ? paddingUnfollow : padding; const scrollRoot_1 = scrollParentRect_0.x - wrapperRectX >= 0; values_.x += (0, _utils.clamp)(Math.abs(values_.x - (scrollRoot_1 ? scrollParentRect_0.x : 0) + wrapperRectX - padding__1[0]), 0, padding__1[0]); if (!unfollow) values_.x = (0, _utils.clamp)(values_.x, Number.MIN_SAFE_INTEGER, rectOffset_0.root.x + rect_0.root.width + left); } else values_.x = rectOffset_0.root.x + rect_0.root.width + left; valuesX.push(values_.x); result_0 = Math.max(...valuesX); } // right if (valueX + rect_0.element.width - left >= window.innerWidth - padding[0] || values_.x + rect_0.element.width - left >= scrollParentRect_0.x + scrollParentRect_0.width - wrapperRectX - padding[0]) { if (rect_0.root.x < window.innerWidth || rectOffset_0.root.x < scrollParentRect_0.x + scrollParentRect_0.width - wrapperRectX || unfollow) { const mathValues_2 = [values_.x, window.innerWidth - wrapperRectX - rect_0.element.width + left, scrollParentRect_0.x + scrollParentRect_0.width - wrapperRectX - rect_0.element.width + left]; values_.x = Math.abs(Math.min(...mathValues_2)); // padding const padding__2 = values_.x < rectOffset_0.root.x - rect_0.element.width + left && unfollow ? paddingUnfollow : padding; const scrollRoot_2 = scrollParentRect_0.x - wrapperRectX >= 0; values_.x -= (0, _utils.clamp)(Math.abs(values_.x - ((scrollRoot_2 ? scrollParentRect_0.x + scrollParentRect_0.width : window.innerWidth) - wrapperRectX - rect_0.element.width - padding__2[1])), 0, padding__2[1]); if (!unfollow) values_.x = (0, _utils.clamp)(values_.x, rectOffset_0.root.x - rect_0.element.width + left, Number.MAX_SAFE_INTEGER); } else values_.x = rectOffset_0.root.x - rect_0.element.width + left; valuesX.push(values_.x); result_0 = Math.min(...valuesX); } // Reset values_.x = valuesX[0]; }); values_.x = result_0; } } // Switch if (switch_ && !value.switch) { let newPosition = position; const rectValue = { element: {} }; if (position_ === 'top') rectValue.element.y = rect_0.root.y - offset[1] - rect_0.element.height; if (position_ === 'bottom') rectValue.element.y = rect_0.root.y + rect_0.root.height + offset[1]; if (position_ === 'left') rectValue.element.x = rect_0.root.x - offset[0] - rect_0.element.width; if (position_ === 'right') rectValue.element.x = rect_0.root.x + rect_0.root.width + offset[0]; const update_ = scrollableParents.some(parent_1 => { const rectParent = parent_1.getBoundingClientRect(); if (position_ === 'top') return !(rectValue.element.y - (['top', 'bottom'].includes(position_) ? padding[0] : 0) >= 0 && rectValue.element.y - (['top', 'bottom'].includes(position_) ? padding[0] : 0) >= rectParent.y); if (position_ === 'bottom') return !(rectValue.element.y + rect_0.element.height + (['top', 'bottom'].includes(position_) ? padding[0] : 0) <= window.innerHeight && rectValue.element.y + rect_0.element.height + (['top', 'bottom'].includes(position_) ? padding[0] : 0) <= rectParent.y + rectParent.height); if (position_ === 'left') return !(rectValue.element.x - (['left', 'right'].includes(position_) ? padding[1] : 0) >= 0 && rectValue.element.x - (['left', 'right'].includes(position_) ? padding[1] : 0) >= rectParent.x); if (position_ === 'right') return !(rectValue.element.x + rect_0.element.width + (['left', 'right'].includes(position_) ? padding[1] : 0) <= window.innerWidth && rectValue.element.x + rect_0.element.width + (['left', 'right'].includes(position_) ? padding[1] : 0) <= rectParent.x + rectParent.width); }); if (update_) { if (position_ === 'top') newPosition = 'bottom'; if (position_ === 'left') newPosition = 'right'; if (position_ === 'right') newPosition = 'left'; if (position_ === 'bottom') newPosition = 'top'; return make({ position: newPosition, alignment: alignment_, inset: inset_, switch: true }); } } refs.values.current = _objectSpread({ position: value.position, alignment: value.alignment, switch: switched, init: false }, values_); if ((0, _utils.is)('function', refs.additional.current)) { refs.values.current = _objectSpread(_objectSpread({}, refs.values.current), additional(rect_0, rectOffset_0)); } if (portal) { const paddingX = padding[1] || 0; refs.values.current.x = (0, _utils.clamp)(refs.values.current.x, paddingX, window.innerWidth - rect_0.element.width - paddingX); } // Update setValues(refs.values.current); }; let style = {}; style.position = 'absolute'; if (values.position === 'top') style.inset = 'auto auto 0px 0px';else if (values.position === 'left') style.inset = '0px 0px auto auto';else style.inset = '0px auto auto 0px'; if (accelerated) { const ppiHigh = (0, _utils.isEnvironment)('browser') && window.devicePixelRatio > 1; if (ppiHigh) style.transform = `translate3d(${values.x}px, ${values.y}px, 0px)`;else style.transform = `translate(${values.x}px, ${values.y}px)`; } else { style.top = values.y; style.left = values.x; } style = _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, element === null || element === void 0 || (_props = element.props) === null || _props === void 0 ? void 0 : _props.style), style), style_), values === null || values === void 0 ? void 0 : values.style); const PortalComponent = portal ? Portal : _react.default.Fragment; const PortalComponentProps = {}; const rootDocumentElement = (0, _utils.isEnvironment)('browser') ? ((_refs$root$current4 = refs.root.current) === null || _refs$root$current4 === void 0 ? void 0 : _refs$root$current4.ownerDocument) || window.document : undefined; if (portal && (0, _utils.isEnvironment)('browser')) { PortalComponentProps.element = rootDocumentElement.body; // relative rootDocumentElement.body.style.position = rootDocumentElement.body.style.position || 'relative'; rootDocumentElement.body.style.margin = rootDocumentElement.body.style.margin || '0'; } return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_react.default.Fragment, { children: [children && /*#__PURE__*/_react.default.cloneElement(children, { ref: item_0 => { if (children.ref) { if ((0, _utils.is)('function', children.ref)) children.ref(item_0);else children.ref.current = item_0; } refs.root.current = item_0; } }), open && (children || anchorElement || anchor) && /*#__PURE__*/(0, _jsxRuntime.jsx)(PortalComponent, _objectSpread(_objectSpread({}, PortalComponentProps), {}, { children: (0, _utils.is)('function', element) ? element({ ref: refs.element, values, style }) : /*#__PURE__*/_react.default.cloneElement(element, { ref: item_1 => { if (element !== null && element !== void 0 && element.ref) { if ((0, _utils.is)('function', element.ref)) element.ref(item_1);else element.ref.current = item_1; } refs.element.current = item_1; }, style }) }))] }); }; Append.displayName = 'onesy-Append'; var _default = exports.default = Append;