lisn.js
Version:
Simply handle user gestures and actions. Includes widgets.
164 lines (157 loc) • 6.94 kB
JavaScript
/**
* @module Utils
*/
import * as MC from "../globals/minification-constants.js";
import * as MH from "../globals/minification-helpers.js";
import { addClasses, addClassesNow, setDataNow, setStylePropNow } from "./css-alter.js";
import { moveElement, tryWrapContent } from "./dom-alter.js";
import { waitForElement } from "./dom-events.js";
import { waitForMutateTime } from "./dom-optimize.js";
import { camelToKebabCase, objToStrKey } from "./text.js";
import { isScrollable, tryGetMainContentElement, fetchMainContentElement } from "./scroll.js";
import { newXWeakMap } from "../modules/x-map.js";
/**
* @category Overlays
* @interface
*/
/**
* Returns an existing overlay for this specification. If the overlay was just
* created it may not yet be attached to the DOM.
*
* @category Overlays
*/
export const getOverlay = userOptions => {
var _overlays$get$get, _overlays$get;
const options = tryGetOverlayOptions(userOptions);
if (!options) {
return null;
}
return (_overlays$get$get = (_overlays$get = overlays.get(options._parent)) === null || _overlays$get === void 0 ? void 0 : _overlays$get.get(options._overlayKey)) !== null && _overlays$get$get !== void 0 ? _overlays$get$get : null;
};
/**
* Creates a new overlay, and inserts it into the DOM as soon as
* {@link waitForMutateTime} resolves, or returns an already existing matching
* overlay.
*
* **Note** that if {@link OverlayOptions.id} is set, a new overlay will
* _always_ be created.
*
* @category Overlays
*/
export const createOverlay = async userOptions => {
const options = await fetchOverlayOptions(userOptions);
const canReuse = !options._id;
if (canReuse) {
var _overlays$get2;
const existingOverlay = (_overlays$get2 = overlays.get(options._parent)) === null || _overlays$get2 === void 0 ? void 0 : _overlays$get2.get(options._overlayKey);
if (existingOverlay) {
if (!MH.parentOf(existingOverlay)) {
// not yet inserted into the DOM, so wait until it is
await waitForMutateTime();
}
return existingOverlay;
}
}
// Create a new one
const overlay = createOnlyOverlay(options);
if (canReuse) {
// Save it now before awating, so that concurrent requests to create the
// same one use it
overlays.sGet(options._parent).set(options._overlayKey, overlay);
} else {
overlay.id = options._id;
}
const isPercentageHOffset = MH.includes((options._style.left || "") + (options._style.right || ""), "%");
const isPercentageVOffset = MH.includes((options._style.top || "") + (options._style.bottom || ""), "%");
let needsContentWrapping = false;
let parentEl = options._parent;
if (isPercentageHOffset || isPercentageVOffset) {
needsContentWrapping = isPercentageHOffset && isScrollable(parentEl, {
axis: "x"
}) || isPercentageVOffset && isScrollable(parentEl, {
axis: "y"
});
}
if (needsContentWrapping) {
// TODO Is it possible to unwrap the children when no longer needing this
// overlay? Probably not worth the effort. ViewWatcher doesn't remove old
// olverlays anyway.
parentEl = await tryWrapContent(parentEl, {
_classNames: [MC.PREFIX_WRAPPER, PREFIX_WRAPPER],
_required: true,
_requiredBy: "percentage offset view trigger with scrolling root"
});
}
if (options._style.position === MC.S_ABSOLUTE) {
// Ensure parent has non-static positioning
addClasses(parentEl, MH.prefixName("overlay-container"));
}
await moveElement(overlay, {
to: parentEl
});
return overlay;
};
// ----------------------------------------
const PREFIX_WRAPPER = MH.prefixName("overlay-wrapper");
const overlays = newXWeakMap(() => MH.newMap());
const tryGetOverlayOptions = userOptions => {
var _userOptions$data, _userOptions$id;
const style = getCssProperties(userOptions === null || userOptions === void 0 ? void 0 : userOptions.style);
const data = (_userOptions$data = userOptions === null || userOptions === void 0 ? void 0 : userOptions.data) !== null && _userOptions$data !== void 0 ? _userOptions$data : {};
const parentEl = tryGetParent(userOptions === null || userOptions === void 0 ? void 0 : userOptions.parent, style.position);
if (!parentEl) {
return null;
}
return {
_parent: parentEl,
_id: (_userOptions$id = userOptions === null || userOptions === void 0 ? void 0 : userOptions.id) !== null && _userOptions$id !== void 0 ? _userOptions$id : "",
_style: style,
_data: data,
_overlayKey: getOverlayKey(style, data)
};
};
const fetchOverlayOptions = async userOptions => {
var _userOptions$data2, _userOptions$id2;
const style = getCssProperties(userOptions === null || userOptions === void 0 ? void 0 : userOptions.style);
const data = (_userOptions$data2 = userOptions === null || userOptions === void 0 ? void 0 : userOptions.data) !== null && _userOptions$data2 !== void 0 ? _userOptions$data2 : {};
const parentEl = await fetchParent(userOptions === null || userOptions === void 0 ? void 0 : userOptions.parent, style.position);
return {
_parent: parentEl,
_id: (_userOptions$id2 = userOptions === null || userOptions === void 0 ? void 0 : userOptions.id) !== null && _userOptions$id2 !== void 0 ? _userOptions$id2 : "",
_style: style,
_data: data,
_overlayKey: getOverlayKey(style, data)
};
};
const getOverlayKey = (style, data) => objToStrKey(style) + "|" + objToStrKey(data);
const getCssProperties = style => {
const finalCssProperties = MH.merge(style, {
position: (style === null || style === void 0 ? void 0 : style.position) || MC.S_ABSOLUTE
} // default
);
if (finalCssProperties.position === MC.S_ABSOLUTE || finalCssProperties.position === MC.S_FIXED) {
if (MH.isEmpty(finalCssProperties.top) && MH.isEmpty(finalCssProperties.bottom)) {
finalCssProperties.top = "0px";
}
if (MH.isEmpty(finalCssProperties.left) && MH.isEmpty(finalCssProperties.right)) {
finalCssProperties.left = "0px";
}
}
return finalCssProperties;
};
const tryGetParent = (userSuppliedParent, position) => userSuppliedParent !== null && userSuppliedParent !== void 0 ? userSuppliedParent : position === MC.S_FIXED ? MH.getBody() : tryGetMainContentElement();
const fetchParent = async (userSuppliedParent, position) => userSuppliedParent !== null && userSuppliedParent !== void 0 ? userSuppliedParent : position === MC.S_FIXED ? await waitForElement(MH.getBody) : await fetchMainContentElement();
const createOnlyOverlay = options => {
const overlay = MH.createElement("div");
addClassesNow(overlay, MH.prefixName("overlay"));
const data = options._data;
for (const attr of MH.keysOf(data)) {
setDataNow(overlay, camelToKebabCase(attr), data[attr]);
}
const style = options._style;
for (const prop of MH.keysOf(style)) {
setStylePropNow(overlay, prop, style[prop]);
}
return overlay;
};
//# sourceMappingURL=overlays.js.map