271 lines
8.9 KiB
JavaScript
271 lines
8.9 KiB
JavaScript
const STYLE_ID = 'calendar-popover-liquid-styles';
|
|
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 = 'calendar-liquid-panel rounded-[1.35rem] py-3';
|
|
const DEFAULT_PANEL_STYLE = 'background-image:none !important;';
|
|
|
|
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))',
|
|
};
|
|
|
|
export function ensureCalendarPopoverStyles() {
|
|
if (typeof document === 'undefined') return;
|
|
if (document.getElementById(STYLE_ID)) return;
|
|
|
|
const style = document.createElement('style');
|
|
style.id = STYLE_ID;
|
|
style.textContent = `
|
|
.calendar-liquid-panel {
|
|
background: rgba(255, 255, 255, 0.2) !important;
|
|
border: 1px solid rgba(255, 255, 255, 0.32) !important;
|
|
box-shadow:
|
|
inset 0 1px 0 rgba(255, 255, 255, 0.6),
|
|
inset 0 -1px 0 rgba(var(--overlay-rgb), 0.06),
|
|
0 8px 20px rgba(var(--overlay-rgb), 0.14),
|
|
0 18px 38px rgba(var(--overlay-rgb), 0.18) !important;
|
|
backdrop-filter: blur(28px) saturate(180%);
|
|
-webkit-backdrop-filter: blur(28px) saturate(180%);
|
|
}
|
|
|
|
.dark .calendar-liquid-panel {
|
|
background: rgba(255, 255, 255, 0.04) !important;
|
|
border: 1px solid rgba(255, 255, 255, 0.12) !important;
|
|
box-shadow:
|
|
inset 0 1px 0 rgba(255, 255, 255, 0.24),
|
|
inset 0 -1px 0 rgba(0, 0, 0, 0.22),
|
|
0 10px 24px rgba(0, 0, 0, 0.3),
|
|
0 24px 54px rgba(0, 0, 0, 0.38) !important;
|
|
}
|
|
|
|
.calendar-liquid-btn {
|
|
background: rgba(255, 255, 255, 0.16) !important;
|
|
border: 1px solid rgba(255, 255, 255, 0.24) !important;
|
|
box-shadow:
|
|
inset 0 1px 0 rgba(255, 255, 255, 0.45),
|
|
inset 0 -1px 0 rgba(var(--overlay-rgb), 0.08),
|
|
0 6px 14px rgba(var(--overlay-rgb), 0.14) !important;
|
|
backdrop-filter: blur(22px) saturate(165%);
|
|
-webkit-backdrop-filter: blur(22px) saturate(165%);
|
|
transition: background 180ms ease, border-color 180ms ease, transform 180ms ease;
|
|
}
|
|
|
|
.calendar-liquid-btn:hover {
|
|
background: rgba(255, 255, 255, 0.22) !important;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.dark .calendar-liquid-btn {
|
|
background: rgba(255, 255, 255, 0.06) !important;
|
|
border: 1px solid rgba(255, 255, 255, 0.12) !important;
|
|
box-shadow:
|
|
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
|
inset 0 -1px 0 rgba(0, 0, 0, 0.2),
|
|
0 8px 18px rgba(0, 0, 0, 0.28) !important;
|
|
}
|
|
|
|
.dark .calendar-liquid-btn:hover {
|
|
background: rgba(255, 255, 255, 0.1) !important;
|
|
}
|
|
|
|
.calendar-liquid-btn input,
|
|
.calendar-liquid-btn input:focus,
|
|
.calendar-liquid-btn input:active {
|
|
background: transparent !important;
|
|
background-color: transparent !important;
|
|
background-image: none !important;
|
|
border: none !important;
|
|
box-shadow: none !important;
|
|
outline: none !important;
|
|
appearance: none !important;
|
|
-webkit-appearance: none !important;
|
|
-moz-appearance: textfield !important;
|
|
backdrop-filter: none !important;
|
|
-webkit-backdrop-filter: none !important;
|
|
filter: none !important;
|
|
}
|
|
|
|
.calendar-liquid-btn input[type='number']::-webkit-outer-spin-button,
|
|
.calendar-liquid-btn input[type='number']::-webkit-inner-spin-button {
|
|
-webkit-appearance: none;
|
|
margin: 0;
|
|
}
|
|
|
|
.calendar-liquid-btn input:-webkit-autofill,
|
|
.calendar-liquid-btn input:-webkit-autofill:hover,
|
|
.calendar-liquid-btn input:-webkit-autofill:focus {
|
|
-webkit-text-fill-color: rgb(var(--text-body-rgb));
|
|
-webkit-box-shadow: 0 0 0 1000px transparent inset !important;
|
|
box-shadow: 0 0 0 1000px transparent inset !important;
|
|
transition: background-color 9999s ease-in-out 0s;
|
|
}
|
|
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
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,
|
|
}) {
|
|
ensureCalendarPopoverStyles();
|
|
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,
|
|
chevronOpenTransform = 'rotate(180deg)',
|
|
chevronClosedTransform = 'rotate(0deg)',
|
|
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 ? chevronOpenTransform : chevronClosedTransform;
|
|
|
|
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,
|
|
chevronOpenTransform,
|
|
chevronClosedTransform,
|
|
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,
|
|
chevronOpenTransform,
|
|
chevronClosedTransform,
|
|
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,
|
|
};
|
|
}
|