This commit is contained in:
167
js/ui/calendarPopover.js
Normal file
167
js/ui/calendarPopover.js
Normal file
@@ -0,0 +1,167 @@
|
||||
const DEFAULT_POPOVER_CLASS = 'absolute left-0 right-0 top-full mt-2 z-[50] transition-all duration-200 pointer-events-none';
|
||||
const DEFAULT_POPOVER_STYLE = 'opacity:0; transform:translateY(-6px) scale(0.98);';
|
||||
const DEFAULT_PANEL_CLASS = 'rounded-[1.35rem] py-3';
|
||||
const DEFAULT_PANEL_STYLE = 'background:rgb(var(--sunken-rgb)); border:1px solid rgb(var(--border-input-rgb)); box-shadow:var(--shadow-shell);';
|
||||
|
||||
const DEFAULT_OPEN_TRIGGER_STYLE = {
|
||||
background: 'rgb(var(--sunken-rgb))',
|
||||
borderColor: 'rgb(var(--border-input-rgb))',
|
||||
};
|
||||
|
||||
const DEFAULT_CLOSED_TRIGGER_STYLE = {
|
||||
background: 'rgb(var(--card-rgb))',
|
||||
borderColor: 'rgb(var(--border-card-rgb))',
|
||||
};
|
||||
|
||||
function byId(idOrElement) {
|
||||
if (!idOrElement) return null;
|
||||
if (typeof idOrElement === 'string') return document.getElementById(idOrElement);
|
||||
return idOrElement;
|
||||
}
|
||||
|
||||
function setStyles(el, styles = {}, important = false) {
|
||||
if (!el) return;
|
||||
Object.entries(styles).forEach(([name, value]) => {
|
||||
if (value == null) return;
|
||||
if (important) {
|
||||
const cssName = name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
||||
el.style.setProperty(cssName, value, 'important');
|
||||
}
|
||||
else el.style[name] = value;
|
||||
});
|
||||
}
|
||||
|
||||
export function createCalendarPopoverHTML({
|
||||
id,
|
||||
calendarHTML,
|
||||
popoverClass = DEFAULT_POPOVER_CLASS,
|
||||
popoverStyle = DEFAULT_POPOVER_STYLE,
|
||||
panelClass = DEFAULT_PANEL_CLASS,
|
||||
panelStyle = DEFAULT_PANEL_STYLE,
|
||||
wrapInPanel = true,
|
||||
}) {
|
||||
const body = wrapInPanel
|
||||
? `<div class="${panelClass}" style="${panelStyle}">${calendarHTML}</div>`
|
||||
: calendarHTML;
|
||||
return `
|
||||
<div id="${id}" class="${popoverClass}" style="${popoverStyle}">
|
||||
${body}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
export function syncCalendarPopoverVisibility({
|
||||
popup,
|
||||
isOpen,
|
||||
chevron,
|
||||
trigger,
|
||||
openTriggerStyle = DEFAULT_OPEN_TRIGGER_STYLE,
|
||||
closedTriggerStyle = DEFAULT_CLOSED_TRIGGER_STYLE,
|
||||
triggerImportant = false,
|
||||
}) {
|
||||
const popupEl = byId(popup);
|
||||
if (popupEl) {
|
||||
popupEl.style.opacity = isOpen ? '1' : '0';
|
||||
popupEl.style.transform = isOpen ? 'translateY(0) scale(1)' : 'translateY(-6px) scale(0.98)';
|
||||
popupEl.style.pointerEvents = isOpen ? 'auto' : 'none';
|
||||
}
|
||||
|
||||
const chevronEl = byId(chevron);
|
||||
if (chevronEl) chevronEl.style.transform = isOpen ? 'rotate(180deg)' : 'rotate(0deg)';
|
||||
|
||||
setStyles(
|
||||
byId(trigger),
|
||||
isOpen ? openTriggerStyle : closedTriggerStyle,
|
||||
triggerImportant,
|
||||
);
|
||||
}
|
||||
|
||||
export function stabilizeSwipeCalendarLayout({
|
||||
calendar,
|
||||
viewport,
|
||||
hideViewport = false,
|
||||
maxAttempts = 8,
|
||||
}) {
|
||||
const viewportEl = byId(viewport);
|
||||
if (hideViewport && viewportEl) {
|
||||
viewportEl.style.opacity = '0';
|
||||
viewportEl.style.visibility = 'hidden';
|
||||
viewportEl.style.transition = 'opacity 120ms ease';
|
||||
}
|
||||
|
||||
calendar?.render?.();
|
||||
|
||||
const ensureStableLayout = (attempt = 0) => {
|
||||
const width = viewportEl ? (viewportEl.clientWidth || viewportEl.getBoundingClientRect().width) : 0;
|
||||
if (viewportEl && width < 8 && attempt < maxAttempts) {
|
||||
requestAnimationFrame(() => ensureStableLayout(attempt + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
calendar?.reapplyLayout?.();
|
||||
calendar?.resetTrackPosition?.();
|
||||
requestAnimationFrame(() => {
|
||||
calendar?.reapplyLayout?.();
|
||||
calendar?.resetTrackPosition?.();
|
||||
if (hideViewport && viewportEl) {
|
||||
viewportEl.style.visibility = 'visible';
|
||||
viewportEl.style.opacity = '1';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
requestAnimationFrame(() => ensureStableLayout());
|
||||
}
|
||||
|
||||
export function createCalendarPopoverController({
|
||||
popupId,
|
||||
viewportId,
|
||||
triggerId,
|
||||
chevronId,
|
||||
getCalendar,
|
||||
openTriggerStyle = DEFAULT_OPEN_TRIGGER_STYLE,
|
||||
closedTriggerStyle = DEFAULT_CLOSED_TRIGGER_STYLE,
|
||||
triggerImportant = false,
|
||||
hideViewportDuringLayout = false,
|
||||
}) {
|
||||
const calendar = () => (typeof getCalendar === 'function' ? getCalendar() : null);
|
||||
|
||||
const sync = (isOpen) => {
|
||||
syncCalendarPopoverVisibility({
|
||||
popup: popupId,
|
||||
isOpen,
|
||||
chevron: chevronId,
|
||||
trigger: triggerId,
|
||||
openTriggerStyle,
|
||||
closedTriggerStyle,
|
||||
triggerImportant,
|
||||
});
|
||||
};
|
||||
|
||||
const stabilize = () => {
|
||||
stabilizeSwipeCalendarLayout({
|
||||
calendar: calendar(),
|
||||
viewport: viewportId,
|
||||
hideViewport: hideViewportDuringLayout,
|
||||
});
|
||||
};
|
||||
|
||||
const close = ({ clearPendingRange = false } = {}) => {
|
||||
sync(false);
|
||||
const instance = calendar();
|
||||
if (clearPendingRange) instance?.clearPendingRange?.();
|
||||
instance?.resetTrackPosition?.();
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
sync(true);
|
||||
stabilize();
|
||||
};
|
||||
|
||||
return {
|
||||
sync,
|
||||
open,
|
||||
close,
|
||||
stabilize,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user