chore: update templ and templui
This commit is contained in:
parent
b5d195baea
commit
61eaa268ab
89 changed files with 25776 additions and 8231 deletions
447
assets/js/popover.js
Normal file
447
assets/js/popover.js
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
import "./floating_ui_core.js";
|
||||
import "./floating_ui_dom.js";
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const floatingCleanups = new WeakMap();
|
||||
const hoverTimeouts = new WeakMap();
|
||||
const arrowBaseClass =
|
||||
"absolute h-2.5 w-2.5 rotate-45 bg-popover border border-border";
|
||||
const exitAnimationDuration = 150;
|
||||
|
||||
function getRootById(id) {
|
||||
const root = document.getElementById(id);
|
||||
return root?.matches("[data-tui-popover-root]") ? root : null;
|
||||
}
|
||||
|
||||
function getRoots() {
|
||||
return Array.from(document.querySelectorAll("[data-tui-popover-root]"));
|
||||
}
|
||||
|
||||
function getContent(root) {
|
||||
return Array.from(root?.children || []).find((child) =>
|
||||
child.matches("[data-tui-popover-content]"),
|
||||
);
|
||||
}
|
||||
|
||||
function getTriggers(root) {
|
||||
return Array.from(root?.children || []).filter((child) =>
|
||||
child.matches("[data-tui-popover-trigger]"),
|
||||
);
|
||||
}
|
||||
|
||||
function getReferenceElement(trigger) {
|
||||
let ref = trigger;
|
||||
let maxArea = 0;
|
||||
|
||||
for (const child of trigger.children) {
|
||||
const rect = child.getBoundingClientRect?.();
|
||||
if (!rect) continue;
|
||||
|
||||
const area = rect.width * rect.height;
|
||||
if (area > maxArea) {
|
||||
maxArea = area;
|
||||
ref = child;
|
||||
}
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
function isHoverRoot(root) {
|
||||
return getTriggers(root).some(
|
||||
(trigger) => trigger.getAttribute("data-tui-popover-type") === "hover",
|
||||
);
|
||||
}
|
||||
|
||||
function isOpenRoot(root) {
|
||||
return getContent(root)?.getAttribute("data-tui-popover-open") === "true";
|
||||
}
|
||||
|
||||
function isOpen(id) {
|
||||
const root = getRootById(id);
|
||||
return root ? isOpenRoot(root) : false;
|
||||
}
|
||||
|
||||
function clearHoverTimeouts(root) {
|
||||
const timeouts = hoverTimeouts.get(root);
|
||||
if (!timeouts) return;
|
||||
clearTimeout(timeouts.enter);
|
||||
clearTimeout(timeouts.leave);
|
||||
hoverTimeouts.delete(root);
|
||||
}
|
||||
|
||||
function stopAutoUpdate(root) {
|
||||
const cleanup = floatingCleanups.get(root);
|
||||
if (!cleanup) return;
|
||||
cleanup();
|
||||
floatingCleanups.delete(root);
|
||||
}
|
||||
|
||||
function showContent(content) {
|
||||
clearTimeout(content._tuiPopoverCloseTimeout);
|
||||
content._tuiPopoverCloseTimeout = null;
|
||||
|
||||
if (!content.matches(":popover-open")) {
|
||||
try {
|
||||
content.showPopover();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
content.setAttribute("data-tui-popover-open", "true");
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function hideContent(content) {
|
||||
clearTimeout(content._tuiPopoverCloseTimeout);
|
||||
content._tuiPopoverCloseTimeout = null;
|
||||
content.setAttribute("data-tui-popover-open", "false");
|
||||
|
||||
if (!content.matches(":popover-open")) {
|
||||
return;
|
||||
}
|
||||
|
||||
content._tuiPopoverCloseTimeout = setTimeout(() => {
|
||||
content._tuiPopoverCloseTimeout = null;
|
||||
if (content.matches(":popover-open")) {
|
||||
try {
|
||||
content.hidePopover();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}, exitAnimationDuration);
|
||||
}
|
||||
|
||||
function arrowClassForPlacement(placement) {
|
||||
switch (placement) {
|
||||
case "top-start":
|
||||
case "top":
|
||||
case "top-end":
|
||||
return `${arrowBaseClass} -bottom-[5px] border-t-transparent border-l-transparent`;
|
||||
case "right-start":
|
||||
case "right":
|
||||
case "right-end":
|
||||
return `${arrowBaseClass} -left-[5px] border-r-transparent border-t-transparent`;
|
||||
case "bottom-start":
|
||||
case "bottom":
|
||||
case "bottom-end":
|
||||
return `${arrowBaseClass} -top-[5px] border-b-transparent border-r-transparent`;
|
||||
case "left-start":
|
||||
case "left":
|
||||
case "left-end":
|
||||
return `${arrowBaseClass} -right-[5px] border-l-transparent border-b-transparent`;
|
||||
default:
|
||||
return `${arrowBaseClass} -top-[5px] border-b-transparent border-r-transparent`;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePosition(root, triggerOverride = null) {
|
||||
if (!window.FloatingUIDOM) return;
|
||||
|
||||
const trigger = triggerOverride || getTriggers(root)[0];
|
||||
const content = getContent(root);
|
||||
if (!trigger || !content) return;
|
||||
|
||||
const { computePosition, offset, flip, shift, arrow } =
|
||||
window.FloatingUIDOM;
|
||||
const reference = getReferenceElement(trigger);
|
||||
const arrowEl = content.querySelector("[data-tui-popover-arrow]");
|
||||
const placement =
|
||||
content.getAttribute("data-tui-popover-placement") || "bottom";
|
||||
const offsetValue =
|
||||
parseInt(content.getAttribute("data-tui-popover-offset"), 10) ||
|
||||
(arrowEl ? 8 : 4);
|
||||
|
||||
const middleware = [
|
||||
offset(offsetValue),
|
||||
flip({ padding: 10 }),
|
||||
shift({ padding: 10 }),
|
||||
];
|
||||
|
||||
if (arrowEl) {
|
||||
middleware.push(arrow({ element: arrowEl, padding: 5 }));
|
||||
}
|
||||
|
||||
// Match the fixed-position popover layer so scroll offsets stay correct.
|
||||
computePosition(reference, content, {
|
||||
placement,
|
||||
middleware,
|
||||
strategy: "fixed",
|
||||
}).then(({ x, y, placement: finalPlacement, middlewareData }) => {
|
||||
Object.assign(content.style, {
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
});
|
||||
|
||||
if (arrowEl && middlewareData.arrow) {
|
||||
const { x: arrowX, y: arrowY } = middlewareData.arrow;
|
||||
|
||||
arrowEl.setAttribute("data-tui-popover-placement", finalPlacement);
|
||||
arrowEl.className = arrowClassForPlacement(finalPlacement);
|
||||
Object.assign(arrowEl.style, {
|
||||
left: arrowX != null ? `${arrowX}px` : "",
|
||||
top: arrowY != null ? `${arrowY}px` : "",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeRoot(root) {
|
||||
const content = getContent(root);
|
||||
if (!content) return;
|
||||
|
||||
stopAutoUpdate(root);
|
||||
clearHoverTimeouts(root);
|
||||
|
||||
getTriggers(root).forEach((trigger) => {
|
||||
trigger.setAttribute("data-tui-popover-open", "false");
|
||||
});
|
||||
|
||||
hideContent(content);
|
||||
}
|
||||
|
||||
function close(id) {
|
||||
const root = getRootById(id);
|
||||
if (root) {
|
||||
closeRoot(root);
|
||||
}
|
||||
}
|
||||
|
||||
function closeAllRoots(exceptRoot = null) {
|
||||
getRoots().forEach((root) => {
|
||||
if (root !== exceptRoot && isOpenRoot(root)) {
|
||||
closeRoot(root);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeAll(exceptId = null) {
|
||||
closeAllRoots(exceptId ? getRootById(exceptId) : null);
|
||||
}
|
||||
|
||||
function openRoot(root, triggerOverride = null) {
|
||||
const content = getContent(root);
|
||||
const trigger = triggerOverride || getTriggers(root)[0];
|
||||
if (!content || !trigger) return;
|
||||
|
||||
if (content.getAttribute("data-tui-popover-exclusive") === "true") {
|
||||
closeAllRoots(root);
|
||||
}
|
||||
|
||||
if (!showContent(content)) return;
|
||||
|
||||
getTriggers(root).forEach((item) => {
|
||||
item.setAttribute("data-tui-popover-open", "true");
|
||||
});
|
||||
|
||||
stopAutoUpdate(root);
|
||||
updatePosition(root, trigger);
|
||||
|
||||
if (window.FloatingUIDOM) {
|
||||
const cleanup = window.FloatingUIDOM.autoUpdate(
|
||||
trigger,
|
||||
content,
|
||||
() => updatePosition(root, trigger),
|
||||
{ animationFrame: true },
|
||||
);
|
||||
floatingCleanups.set(root, cleanup);
|
||||
}
|
||||
}
|
||||
|
||||
function open(id) {
|
||||
const root = getRootById(id);
|
||||
if (root) {
|
||||
openRoot(root);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRoot(root, triggerOverride = null) {
|
||||
if (isOpenRoot(root)) {
|
||||
closeRoot(root);
|
||||
return;
|
||||
}
|
||||
|
||||
openRoot(root, triggerOverride);
|
||||
}
|
||||
|
||||
function toggle(id) {
|
||||
const root = getRootById(id);
|
||||
if (root) {
|
||||
toggleRoot(root);
|
||||
}
|
||||
}
|
||||
|
||||
function clearOtherHoverRoots(activeRoot) {
|
||||
getRoots().forEach((root) => {
|
||||
if (root === activeRoot || !isHoverRoot(root)) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearHoverTimeouts(root);
|
||||
closeRoot(root);
|
||||
});
|
||||
}
|
||||
|
||||
function handleHoverEnter(root, trigger) {
|
||||
const content = getContent(root);
|
||||
if (!content || !isHoverRoot(root)) return;
|
||||
|
||||
const delay =
|
||||
parseInt(content.getAttribute("data-tui-popover-hover-delay"), 10) || 100;
|
||||
const timeouts = hoverTimeouts.get(root) || {};
|
||||
|
||||
clearOtherHoverRoots(root);
|
||||
clearTimeout(timeouts.leave);
|
||||
clearTimeout(timeouts.enter);
|
||||
timeouts.enter = setTimeout(() => openRoot(root, trigger), delay);
|
||||
hoverTimeouts.set(root, timeouts);
|
||||
}
|
||||
|
||||
function handleHoverLeave(root, movingWithinPair) {
|
||||
const content = getContent(root);
|
||||
if (!content || !isHoverRoot(root)) return;
|
||||
|
||||
const delay =
|
||||
parseInt(content.getAttribute("data-tui-popover-hover-out-delay"), 10) ||
|
||||
200;
|
||||
const timeouts = hoverTimeouts.get(root) || {};
|
||||
|
||||
clearTimeout(timeouts.enter);
|
||||
if (!movingWithinPair) {
|
||||
timeouts.leave = setTimeout(() => closeRoot(root), delay);
|
||||
hoverTimeouts.set(root, timeouts);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
const trigger = event.target.closest("[data-tui-popover-trigger]");
|
||||
const root = trigger?.closest("[data-tui-popover-root]");
|
||||
const triggerType = trigger?.getAttribute("data-tui-popover-type");
|
||||
|
||||
if (
|
||||
trigger &&
|
||||
root &&
|
||||
triggerType !== "hover" &&
|
||||
triggerType !== "manual"
|
||||
) {
|
||||
const disabledChild = trigger.querySelector(
|
||||
':disabled, [disabled], [aria-disabled="true"]',
|
||||
);
|
||||
if (disabledChild) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
toggleRoot(root, trigger);
|
||||
return;
|
||||
}
|
||||
|
||||
getRoots().forEach((currentRoot) => {
|
||||
const content = getContent(currentRoot);
|
||||
if (
|
||||
!content ||
|
||||
!content.matches(":popover-open") ||
|
||||
content.getAttribute("data-tui-popover-disable-clickaway") === "true"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clickedInsideContent = content.contains(event.target);
|
||||
const clickedTrigger = getTriggers(currentRoot).some((item) =>
|
||||
item.contains(event.target),
|
||||
);
|
||||
|
||||
if (!clickedInsideContent && !clickedTrigger) {
|
||||
closeRoot(currentRoot);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("mouseover", (event) => {
|
||||
const trigger = event.target.closest("[data-tui-popover-trigger]");
|
||||
const root = trigger?.closest("[data-tui-popover-root]");
|
||||
if (
|
||||
trigger &&
|
||||
root &&
|
||||
!trigger.contains(event.relatedTarget) &&
|
||||
trigger.getAttribute("data-tui-popover-type") === "hover"
|
||||
) {
|
||||
handleHoverEnter(root, trigger);
|
||||
}
|
||||
|
||||
const content = event.target.closest("[data-tui-popover-content]");
|
||||
const contentRoot = content?.closest("[data-tui-popover-root]");
|
||||
if (
|
||||
content &&
|
||||
contentRoot &&
|
||||
isHoverRoot(contentRoot) &&
|
||||
!content.contains(event.relatedTarget) &&
|
||||
content.matches(":popover-open")
|
||||
) {
|
||||
const timeouts = hoverTimeouts.get(contentRoot) || {};
|
||||
clearTimeout(timeouts.leave);
|
||||
hoverTimeouts.set(contentRoot, timeouts);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mouseout", (event) => {
|
||||
const trigger = event.target.closest("[data-tui-popover-trigger]");
|
||||
const root = trigger?.closest("[data-tui-popover-root]");
|
||||
if (
|
||||
trigger &&
|
||||
root &&
|
||||
!trigger.contains(event.relatedTarget) &&
|
||||
trigger.getAttribute("data-tui-popover-type") === "hover"
|
||||
) {
|
||||
const content = getContent(root);
|
||||
handleHoverLeave(root, !!content?.contains(event.relatedTarget));
|
||||
}
|
||||
|
||||
const content = event.target.closest("[data-tui-popover-content]");
|
||||
const contentRoot = content?.closest("[data-tui-popover-root]");
|
||||
if (
|
||||
content &&
|
||||
contentRoot &&
|
||||
isHoverRoot(contentRoot) &&
|
||||
!content.contains(event.relatedTarget) &&
|
||||
content.matches(":popover-open")
|
||||
) {
|
||||
const movingToTrigger = getTriggers(contentRoot).some((item) =>
|
||||
item.contains(event.relatedTarget),
|
||||
);
|
||||
handleHoverLeave(contentRoot, movingToTrigger);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key !== "Escape") return;
|
||||
|
||||
getRoots().forEach((root) => {
|
||||
const content = getContent(root);
|
||||
if (
|
||||
content &&
|
||||
content.matches(":popover-open") &&
|
||||
content.getAttribute("data-tui-popover-disable-esc") !== "true"
|
||||
) {
|
||||
closeRoot(root);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
window.tui = window.tui || {};
|
||||
window.tui.popover = {
|
||||
open,
|
||||
close,
|
||||
closeAll,
|
||||
toggle,
|
||||
isOpen,
|
||||
};
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue