@amaui/ui-react
Version:
UI for React
529 lines (528 loc) • 29.2 kB
JavaScript
"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;