UNPKG

@amaui/ui-react

Version:
529 lines (528 loc) 29.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = __importDefault(require("react")); const utils_1 = require("@amaui/utils"); const style_react_1 = require("@amaui/style-react"); const Portal_1 = __importDefault(require("../Portal")); const valuesDefault = { x: 0, y: 0, switch: false, init: true }; const Append = (props_) => { var _a, _b; const theme = (0, style_react_1.useAmauiTheme)(); const props = react_1.default.useMemo(() => { var _a, _b, _c, _d, _e, _f, _g, _h; return (Object.assign(Object.assign(Object.assign({}, (_d = (_c = (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.ui) === null || _a === void 0 ? void 0 : _a.elements) === null || _b === void 0 ? void 0 : _b.all) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d.default), (_h = (_g = (_f = (_e = theme === null || theme === void 0 ? void 0 : theme.ui) === null || _e === void 0 ? void 0 : _e.elements) === null || _f === void 0 ? void 0 : _f.amauiAppend) === null || _g === void 0 ? void 0 : _g.props) === null || _h === void 0 ? void 0 : _h.default), props_)); }, [props_]); const Portal = react_1.default.useMemo(() => { var _a; return ((_a = theme === null || theme === void 0 ? void 0 : theme.elements) === null || _a === void 0 ? void 0 : _a.Portal) || Portal_1.default; }, [theme]); 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, style: style_, update, element, parent: parentElement, additional, children } = props; const [init, setInit] = react_1.default.useState(false); const [values, setValues] = react_1.default.useState(valuesDefault); const refs = { root: react_1.default.useRef(undefined), element: react_1.default.useRef(undefined), values: react_1.default.useRef(values), alignment: react_1.default.useRef(undefined), position: react_1.default.useRef(undefined), portal: react_1.default.useRef(undefined), props: react_1.default.useRef(undefined), anchor: react_1.default.useRef(undefined), additional: react_1.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 ? 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 = react_1.default.useCallback((event) => { // Only if it's parent's scroll event // if (event.target.contains(refs.root.current) && anchor === undefined) make(); make(); }, [anchor]); const observerMethod = react_1.default.useCallback((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(); } } }, []); const observerResizeMethod = react_1.default.useCallback(() => { if (refs.anchor.current === undefined) make(); }, []); react_1.default.useEffect(() => { var _a, _b; const rootWindow = (0, utils_1.isEnvironment)('browser') ? (((_b = (_a = refs.root.current) === null || _a === void 0 ? void 0 : _a.ownerDocument) === null || _b === void 0 ? void 0 : _b.defaultView) || window) : undefined; make(); // Scroll rootWindow.addEventListener('scroll', onScroll, true); // Init setInit(true); return () => { rootWindow.removeEventListener('scroll', onScroll); }; }, []); // Anchor react_1.default.useEffect(() => { if (init) { if (open) make(); else { if (refs.props.current.clearOnClose) setValues(valuesDefault); } } }, [open]); // Anchor react_1.default.useEffect(() => { if (init) { if ((anchor === null || anchor === void 0 ? void 0 : anchor.x) && (anchor === null || anchor === void 0 ? void 0 : anchor.y)) make(); } }, [anchor]); // Anchor react_1.default.useEffect(() => { make(); }, [anchorElement]); // Anchor element react_1.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_1.default.useEffect(() => { // Resize const observer = new MutationObserver(observerMethod); try { if (refs.element.current) { observer.observe(refs.element.current, { attributes: true, childList: true, subtree: true }); } } catch (error) { } return () => { if (refs.element.current) { observer.disconnect(); } }; }, [anchor, refs.element.current]); // Update react_1.default.useEffect(() => { if (init) make(); }, [update]); // Update react_1.default.useEffect(() => { if (init) { if ((0, utils_1.is)('function', refs.props.current.onUpdate)) refs.props.current.onUpdate(values); } }, [values]); const getBoundingRect = react_1.default.useCallback((elementHTML) => new Promise(async (resolve, reject) => { if (!(elementHTML === null || elementHTML === void 0 ? void 0 : elementHTML.getBoundingClientRect)) return; let tries = 5; while (tries) { const valueRect = elementHTML.getBoundingClientRect(); if ((valueRect === null || valueRect === void 0 ? void 0 : valueRect.height) && (valueRect === null || valueRect === void 0 ? void 0 : valueRect.width)) return resolve(valueRect); tries--; await (0, utils_1.wait)(40); } }), []); const getValues = async () => { var _a; if (!((refs.root.current || refs.anchor.current) && refs.element.current)) return; const wrapperRect = await getBoundingRect((_a = (refs.root.current || refs.element.current)) === null || _a === void 0 ? void 0 : _a.parentElement); if (!wrapperRect) return; const resolve = () => { 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(); 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 _a, _b, _c; const valueMeasurements = valueMeasurements_ !== undefined ? valueMeasurements_ : await getValues(); if (!valueMeasurements || (valueMeasurements.rect.element.width === 0 && valueMeasurements.rect.element.height === 0)) return; const rootDocument = (0, utils_1.isEnvironment)('browser') ? (((_a = refs.root.current) === null || _a === void 0 ? void 0 : _a.ownerDocument) || window.document) : undefined; const wrapperRect = (overflow || switch_) && (refs.root.current || refs.element.current).parentElement.getBoundingClientRect(); const scrollableParents = (0, utils_1.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 } = valueMeasurements; let { rectOffset } = valueMeasurements; // We need both root and element refs // to make our values for it const values_ = { x: 0, y: 0 }; const rootX = portal ? rect.root.x : rectOffset.root.x; const rootY = portal ? rect.root.y : rectOffset.root.y; const rootBottom = portal ? rect.root.bottom : rectOffset.root.y + rect.root.height; const rootRight = portal ? rect.root.right : rectOffset.root.x + rect.root.width; const parent_ = (_c = (parentElement !== undefined ? parentElement : portal ? rootDocument.body : (_b = refs.root.current) === null || _b === void 0 ? void 0 : _b.parentElement)) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect(); // Top, Bottom if (['top', 'bottom'].includes(position)) { if (alignment === 'start') values_.x = rootX; if (!alignment || alignment === 'center') values_.x = rootX + ((rect.root.width - rect.element.width) / 2); if (alignment === 'end') values_.x = rootX + rect.root.width - rect.element.width; if (position === 'top') { values_.y = rootBottom - (parent_.height || 0) - offset[1] - rect.root.height; if (inset) values_.y = rootBottom - (parent_.height || 0) - rect.root.height + rect.element.height + offset[1]; } else { values_.y = rootY + offset[1] + rect.root.height; if (inset) values_.y = rootY + rect.root.height - rect.element.height - offset[1]; } } // Left if (['left', 'right'].includes(position)) { if (alignment === 'start') values_.y = rootY; if (!alignment || alignment === 'center') values_.y = rootY + ((rect.root.height - rect.element.height) / 2); if (alignment === 'end') values_.y = rootY + rect.root.height - rect.element.height; if (position === 'left') { values_.x = rootRight - (parent_.width || 0) - offset[0] - rect.root.width; if (inset) values_.x = rootRight - (parent_.width || 0) - rect.root.width + rect.element.width + offset[0]; } else { values_.x = rootX + offset[0] + rect.root.width; if (inset) values_.x = rootX + rect.root.width - rect.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 = rect; const top = (portal ? rootDocument.documentElement.scrollTop : 0); const left = (portal ? rootDocument.documentElement.scrollLeft : 0); const rootY_ = !portal ? wrapperRect.y + rectOffset.root.y : rect.root.y; const valueY = !portal ? wrapperRect.y + values_.y : values_.y; const rootX_ = !portal ? wrapperRect.x + rectOffset.root.x : rect.root.x; const valueX = !portal ? wrapperRect.x + values_.x : values_.x; const wrapperRectY = !portal ? wrapperRect.y : 0; const wrapperRectX = !portal ? wrapperRect.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.root.y); const valueScrollParentY = valueY - scrollParentRect.y; // top if ((valueY - top <= 0 + padding[1]) || (valueScrollParentY - top <= 0 + padding[1])) { if ((rootY_ + rect.root.height) > 0 || unfollow) { const mathValues = [ values_.y, top, scrollParentY, scrollParentRect.y - wrapperRectY + top, 0 ]; if (!portal) mathValues.push(rectOffset.root.y - rootY_); values_.y = Math.max(...mathValues); // padding const padding_ = values_.y > (rectOffset.root.y + rect.root.height + top) && unfollow ? paddingUnfollow : padding; const scrollRoot = scrollParentRect.y - wrapperRectY >= 0; values_.y += (0, utils_1.clamp)(Math.abs(values_.y - (scrollRoot ? scrollParentRect.y : 0) + wrapperRectY - padding_[1]), 0, padding_[1]); if (!unfollow) values_.y = (0, utils_1.clamp)(values_.y, Number.MIN_SAFE_INTEGER, rectOffset.root.y + rect.root.height + top); } else values_.y = rectOffset.root.y + rect.root.height + top; valuesY.push(values_.y); result = Math.max(...valuesY); } // bottom if ((valueY + rect.element.height - top >= window.innerHeight - padding[1]) || (values_.y + rect.element.height - top >= scrollParentRect.y + scrollParentRect.height - wrapperRectY - padding[1])) { if (((rect.root.y < window.innerHeight) || (rectOffset.root.y < scrollParentRect.y + scrollParentRect.height - wrapperRectY)) || unfollow) { const mathValues = [ values_.y, window.innerHeight - wrapperRectY - rect.element.height + top, scrollParentRect.y + scrollParentRect.height - wrapperRectY - rect.element.height + top ]; values_.y = Math.abs(Math.min(...mathValues)); // padding const padding_ = values_.y < (rectOffset.root.y - rect.element.height + top) && unfollow ? paddingUnfollow : padding; const scrollRoot = scrollParentRect.y - wrapperRectY >= 0; values_.y -= (0, utils_1.clamp)(Math.abs(values_.y - ((scrollRoot ? scrollParentRect.y + scrollParentRect.height : window.innerHeight) - wrapperRectY - rect.element.height - padding_[1])), 0, padding_[1]); if (!unfollow) values_.y = (0, utils_1.clamp)(values_.y, rectOffset.root.y - rect.element.height + top, Number.MAX_SAFE_INTEGER); } else values_.y = rectOffset.root.y - rect.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 = values_.x; scrollableParents.forEach((parent) => { const scrollParentRect = parent.getBoundingClientRect(); const scrollParentX = scrollParentRect.x - Math.abs(rect.root.x); const valueScrollParentX = valueX - scrollParentRect.x; // left if ((valueX - left <= 0 + padding[0]) || (valueScrollParentX - left <= 0 + padding[0])) { if ((rootX_ + rect.root.width) > 0 || unfollow) { const mathValues = [ values_.x, left, scrollParentX, scrollParentRect.x - wrapperRectX + left, 0 ]; if (!portal) mathValues.push(rectOffset.root.x - rootX_); values_.x = Math.max(...mathValues); // padding const padding_ = (values_.x > rectOffset.root.x + rect.root.width + left) && unfollow ? paddingUnfollow : padding; const scrollRoot = scrollParentRect.x - wrapperRectX >= 0; values_.x += (0, utils_1.clamp)(Math.abs(values_.x - (scrollRoot ? scrollParentRect.x : 0) + wrapperRectX - padding_[0]), 0, padding_[0]); if (!unfollow) values_.x = (0, utils_1.clamp)(values_.x, Number.MIN_SAFE_INTEGER, rectOffset.root.x + rect.root.width + left); } else values_.x = rectOffset.root.x + rect.root.width + left; valuesX.push(values_.x); result = Math.max(...valuesX); } // right if ((valueX + rect.element.width - left >= window.innerWidth - padding[0]) || (values_.x + rect.element.width - left >= scrollParentRect.x + scrollParentRect.width - wrapperRectX - padding[0])) { if (((rect.root.x < window.innerWidth) || (rectOffset.root.x < scrollParentRect.x + scrollParentRect.width - wrapperRectX)) || unfollow) { const mathValues = [ values_.x, window.innerWidth - wrapperRectX - rect.element.width + left, scrollParentRect.x + scrollParentRect.width - wrapperRectX - rect.element.width + left ]; values_.x = Math.abs(Math.min(...mathValues)); // padding const padding_ = (values_.x < rectOffset.root.x - rect.element.width + left) && unfollow ? paddingUnfollow : padding; const scrollRoot = scrollParentRect.x - wrapperRectX >= 0; values_.x -= (0, utils_1.clamp)(Math.abs(values_.x - ((scrollRoot ? scrollParentRect.x + scrollParentRect.width : window.innerWidth) - wrapperRectX - rect.element.width - padding_[1])), 0, padding_[1]); if (!unfollow) values_.x = (0, utils_1.clamp)(values_.x, rectOffset.root.x - rect.element.width + left, Number.MAX_SAFE_INTEGER); } else values_.x = rectOffset.root.x - rect.element.width + left; valuesX.push(values_.x); result = Math.min(...valuesX); } // Reset values_.x = valuesX[0]; }); values_.x = result; } } // Switch if (switch_ && !value.switch) { let newPosition = position; const rectValue = { element: {} }; if (position_ === 'top') rectValue.element.y = rect.root.y - offset[1] - rect.element.height; if (position_ === 'bottom') rectValue.element.y = rect.root.y + rect.root.height + offset[1]; if (position_ === 'left') rectValue.element.x = rect.root.x - offset[0] - rect.element.width; if (position_ === 'right') rectValue.element.x = rect.root.x + rect.root.width + offset[0]; const update_ = scrollableParents.some(parent => { const rectParent = parent.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.element.height + (['top', 'bottom'].includes(position_) ? padding[0] : 0) <= window.innerHeight && rectValue.element.y + rect.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.element.width + (['left', 'right'].includes(position_) ? padding[1] : 0) <= window.innerWidth && rectValue.element.x + rect.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 = Object.assign({ position: value.position, alignment: value.alignment, switch: switched, init: false }, values_); if ((0, utils_1.is)('function', refs.additional.current)) { refs.values.current = Object.assign(Object.assign({}, refs.values.current), additional(rect, rectOffset)); } // 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_1.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 = Object.assign(Object.assign(Object.assign(Object.assign({}, (_a = element === null || element === void 0 ? void 0 : element.props) === null || _a === void 0 ? void 0 : _a.style), style), style_), values === null || values === void 0 ? void 0 : values.style); const PortalComponent = portal ? Portal : react_1.default.Fragment; const PortalComponentProps = {}; const rootDocumentElement = (0, utils_1.isEnvironment)('browser') ? (((_b = refs.root.current) === null || _b === void 0 ? void 0 : _b.ownerDocument) || window.document) : undefined; if (portal && (0, utils_1.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 ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [children && react_1.default.cloneElement(children, { ref: (item) => { if (children.ref) { if ((0, utils_1.is)('function', children.ref)) children.ref(item); else children.ref.current = item; } refs.root.current = item; } }), open && (children || anchorElement || anchor) && ((0, jsx_runtime_1.jsx)(PortalComponent, Object.assign({}, PortalComponentProps, { children: (0, utils_1.is)('function', element) ? element({ ref: refs.element, values, style }) : react_1.default.cloneElement(element, { ref: (item) => { if (element === null || element === void 0 ? void 0 : element.ref) { if ((0, utils_1.is)('function', element.ref)) element.ref(item); else element.ref.current = item; } refs.element.current = item; }, style }) })))] })); }; Append.displayName = 'amaui-Append'; exports.default = Append;