fix(calendar): restore GSAP fly-rect + fly-label animation in useCalendar composable
The refactoring in a4d8d81 moved calendar logic into useCalendar.ts but
used the old CSS-transform animation code instead of the GSAP-based
flying rect + flying label implementation. This restores:
- GSAP-based animateCalendarZoomIntoSource and animateCalendarFlipTransition
- Flying label that animates from card title → toolbar on zoom-in and back
- Clone-and-swap pattern with skeleton content in fly-rect (no text)
- Fly-rect/fly-label refs and setters now live in the composable
- isoWeekNumber() and weekNumber field on monthRows
- Sibling card titles and week numbers faded during zoom
- Removed old CSS-transform camera state and calendarSceneTransformStyle
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import gsap from "gsap";
|
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, watchEffect } from "vue";
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, watchEffect } from "vue";
|
||||||
import CrmAuthLoading from "~~/app/components/workspace/auth/CrmAuthLoading.vue";
|
import CrmAuthLoading from "~~/app/components/workspace/auth/CrmAuthLoading.vue";
|
||||||
import CrmCalendarPanel from "~~/app/components/workspace/calendar/CrmCalendarPanel.vue";
|
import CrmCalendarPanel from "~~/app/components/workspace/calendar/CrmCalendarPanel.vue";
|
||||||
@@ -159,14 +158,22 @@ const {
|
|||||||
calendarHoveredMonthIndex,
|
calendarHoveredMonthIndex,
|
||||||
calendarHoveredWeekStartKey,
|
calendarHoveredWeekStartKey,
|
||||||
calendarHoveredDayKey,
|
calendarHoveredDayKey,
|
||||||
|
calendarFlyRectRef,
|
||||||
|
calendarFlyVisible,
|
||||||
|
calendarFlyLabelRef,
|
||||||
|
calendarFlyLabelVisible,
|
||||||
|
calendarToolbarLabelRef,
|
||||||
calendarZoomBusy,
|
calendarZoomBusy,
|
||||||
|
calendarZoomPrimeToken,
|
||||||
normalizedCalendarView,
|
normalizedCalendarView,
|
||||||
calendarZoomLevelIndex,
|
calendarZoomLevelIndex,
|
||||||
calendarSceneTransformStyle,
|
|
||||||
calendarZoomOrder,
|
calendarZoomOrder,
|
||||||
setCalendarContentWrapRef,
|
setCalendarContentWrapRef,
|
||||||
setCalendarContentScrollRef,
|
setCalendarContentScrollRef,
|
||||||
setCalendarSceneRef,
|
setCalendarSceneRef,
|
||||||
|
setCalendarFlyRectRef,
|
||||||
|
setCalendarFlyLabelRef,
|
||||||
|
setCalendarToolbarLabelRef,
|
||||||
setCalendarHoveredMonthIndex,
|
setCalendarHoveredMonthIndex,
|
||||||
setCalendarHoveredWeekStartKey,
|
setCalendarHoveredWeekStartKey,
|
||||||
setCalendarHoveredDayKey,
|
setCalendarHoveredDayKey,
|
||||||
@@ -210,13 +217,6 @@ const {
|
|||||||
refetchCalendar,
|
refetchCalendar,
|
||||||
} = calendar;
|
} = calendar;
|
||||||
|
|
||||||
// Fly rect for zoom animation (kept in orchestrator as template ref)
|
|
||||||
const calendarFlyRectRef = ref<HTMLDivElement | null>(null);
|
|
||||||
const calendarFlyVisible = ref(false);
|
|
||||||
function setCalendarFlyRectRef(element: HTMLDivElement | null) {
|
|
||||||
calendarFlyRectRef.value = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 5. Deals
|
// 5. Deals
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1401,6 +1401,9 @@ onBeforeUnmount(() => {
|
|||||||
:normalized-calendar-view="normalizedCalendarView"
|
:normalized-calendar-view="normalizedCalendarView"
|
||||||
:calendar-fly-visible="calendarFlyVisible"
|
:calendar-fly-visible="calendarFlyVisible"
|
||||||
:set-calendar-fly-rect-ref="setCalendarFlyRectRef"
|
:set-calendar-fly-rect-ref="setCalendarFlyRectRef"
|
||||||
|
:calendar-fly-label-visible="calendarFlyLabelVisible"
|
||||||
|
:set-calendar-fly-label-ref="setCalendarFlyLabelRef"
|
||||||
|
:set-calendar-toolbar-label-ref="setCalendarToolbarLabelRef"
|
||||||
:on-calendar-scene-mouse-leave="onCalendarSceneMouseLeave"
|
:on-calendar-scene-mouse-leave="onCalendarSceneMouseLeave"
|
||||||
:calendar-view="calendarView"
|
:calendar-view="calendarView"
|
||||||
:year-months="yearMonths"
|
:year-months="yearMonths"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import gsap from "gsap";
|
||||||
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick, type ComputedRef } from "vue";
|
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick, type ComputedRef } from "vue";
|
||||||
import { useQuery, useMutation } from "@vue/apollo-composable";
|
import { useQuery, useMutation } from "@vue/apollo-composable";
|
||||||
import {
|
import {
|
||||||
@@ -6,7 +7,6 @@ import {
|
|||||||
ArchiveCalendarEventMutationDocument,
|
ArchiveCalendarEventMutationDocument,
|
||||||
} from "~~/graphql/generated";
|
} from "~~/graphql/generated";
|
||||||
type CalendarHierarchyView = "year" | "month" | "week" | "day";
|
type CalendarHierarchyView = "year" | "month" | "week" | "day";
|
||||||
type CalendarRect = { left: number; top: number; width: number; height: number };
|
|
||||||
|
|
||||||
|
|
||||||
export type CalendarView = "day" | "week" | "month" | "year" | "agenda";
|
export type CalendarView = "day" | "week" | "month" | "year" | "agenda";
|
||||||
@@ -232,6 +232,7 @@ export function useCalendar(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
calendarViewportResizeObserver.disconnect();
|
calendarViewportResizeObserver.disconnect();
|
||||||
calendarViewportResizeObserver = null;
|
calendarViewportResizeObserver = null;
|
||||||
}
|
}
|
||||||
|
calendarKillTweens();
|
||||||
clearCalendarZoomPrime();
|
clearCalendarZoomPrime();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -288,11 +289,21 @@ export function useCalendar(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
const calendarContentWrapRef = ref<HTMLElement | null>(null);
|
const calendarContentWrapRef = ref<HTMLElement | null>(null);
|
||||||
const calendarContentScrollRef = ref<HTMLElement | null>(null);
|
const calendarContentScrollRef = ref<HTMLElement | null>(null);
|
||||||
const calendarSceneRef = ref<HTMLElement | null>(null);
|
const calendarSceneRef = ref<HTMLElement | null>(null);
|
||||||
|
const calendarFlyRectRef = ref<HTMLDivElement | null>(null);
|
||||||
|
const calendarFlyVisible = ref(false);
|
||||||
|
const calendarFlyLabelRef = ref<HTMLDivElement | null>(null);
|
||||||
|
const calendarFlyLabelVisible = ref(false);
|
||||||
|
const calendarToolbarLabelRef = ref<HTMLDivElement | null>(null);
|
||||||
const calendarViewportHeight = ref(0);
|
const calendarViewportHeight = ref(0);
|
||||||
const calendarHoveredMonthIndex = ref<number | null>(null);
|
const calendarHoveredMonthIndex = ref<number | null>(null);
|
||||||
const calendarHoveredWeekStartKey = ref("");
|
const calendarHoveredWeekStartKey = ref("");
|
||||||
const calendarHoveredDayKey = ref("");
|
const calendarHoveredDayKey = ref("");
|
||||||
let calendarViewportResizeObserver: ResizeObserver | null = null;
|
let calendarViewportResizeObserver: ResizeObserver | null = null;
|
||||||
|
let calendarActiveTweens: gsap.core.Tween[] = [];
|
||||||
|
|
||||||
|
const CALENDAR_FLY_DURATION = 0.65;
|
||||||
|
const CALENDAR_FADE_DURATION = 0.18;
|
||||||
|
const CALENDAR_EASE = "power3.inOut";
|
||||||
|
|
||||||
function setCalendarContentWrapRef(element: HTMLElement | null) {
|
function setCalendarContentWrapRef(element: HTMLElement | null) {
|
||||||
calendarContentWrapRef.value = element;
|
calendarContentWrapRef.value = element;
|
||||||
@@ -317,6 +328,18 @@ export function useCalendar(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
calendarSceneRef.value = element;
|
calendarSceneRef.value = element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setCalendarFlyRectRef(element: HTMLDivElement | null) {
|
||||||
|
calendarFlyRectRef.value = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCalendarFlyLabelRef(element: HTMLDivElement | null) {
|
||||||
|
calendarFlyLabelRef.value = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCalendarToolbarLabelRef(element: HTMLDivElement | null) {
|
||||||
|
calendarToolbarLabelRef.value = element;
|
||||||
|
}
|
||||||
|
|
||||||
function setCalendarHoveredMonthIndex(value: number | null) {
|
function setCalendarHoveredMonthIndex(value: number | null) {
|
||||||
calendarHoveredMonthIndex.value = value;
|
calendarHoveredMonthIndex.value = value;
|
||||||
}
|
}
|
||||||
@@ -336,21 +359,31 @@ export function useCalendar(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
clearCalendarZoomPrime();
|
clearCalendarZoomPrime();
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendarZoomBusy = ref(false);
|
function calendarTweenTo(target: gsap.TweenTarget, vars: gsap.TweenVars): Promise<void> {
|
||||||
const calendarCameraState = ref({
|
return new Promise((resolve) => {
|
||||||
active: false,
|
const t = gsap.to(target, {
|
||||||
left: 0,
|
...vars,
|
||||||
top: 0,
|
onComplete: () => {
|
||||||
scale: 1,
|
calendarActiveTweens = calendarActiveTweens.filter((tw) => tw !== t);
|
||||||
durationMs: 0,
|
resolve();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
calendarActiveTweens.push(t);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function calendarKillTweens() {
|
||||||
|
for (const t of calendarActiveTweens) t.kill();
|
||||||
|
calendarActiveTweens = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const calendarZoomBusy = ref(false);
|
||||||
const calendarZoomPrimeToken = ref("");
|
const calendarZoomPrimeToken = ref("");
|
||||||
const calendarZoomPrimeScale = ref(1);
|
const calendarZoomPrimeScale = ref(1);
|
||||||
const calendarZoomPrimeTicks = ref(0);
|
const calendarZoomPrimeTicks = ref(0);
|
||||||
let calendarWheelLockUntil = 0;
|
let calendarWheelLockUntil = 0;
|
||||||
let calendarZoomPrimeTimer: ReturnType<typeof setTimeout> | null = null;
|
let calendarZoomPrimeTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
let calendarZoomPrimeLastAt = 0;
|
let calendarZoomPrimeLastAt = 0;
|
||||||
const CALENDAR_ZOOM_DURATION_MS = 2400;
|
|
||||||
const CALENDAR_ZOOM_PRIME_STEPS = 2;
|
const CALENDAR_ZOOM_PRIME_STEPS = 2;
|
||||||
const CALENDAR_ZOOM_PRIME_MAX_SCALE = 1.05;
|
const CALENDAR_ZOOM_PRIME_MAX_SCALE = 1.05;
|
||||||
const CALENDAR_ZOOM_PRIME_RESET_MS = 900;
|
const CALENDAR_ZOOM_PRIME_RESET_MS = 900;
|
||||||
@@ -360,18 +393,6 @@ export function useCalendar(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
calendarView.value === "agenda" ? "month" : calendarView.value,
|
calendarView.value === "agenda" ? "month" : calendarView.value,
|
||||||
);
|
);
|
||||||
const calendarZoomLevelIndex = computed(() => Math.max(0, calendarZoomOrder.indexOf(normalizedCalendarView.value)));
|
const calendarZoomLevelIndex = computed(() => Math.max(0, calendarZoomOrder.indexOf(normalizedCalendarView.value)));
|
||||||
const calendarSceneTransformStyle = computed(() => {
|
|
||||||
if (!calendarCameraState.value.active) return undefined;
|
|
||||||
return {
|
|
||||||
transform: `translate(${calendarCameraState.value.left}px, ${calendarCameraState.value.top}px) scale(${calendarCameraState.value.scale})`,
|
|
||||||
transformOrigin: "0 0",
|
|
||||||
transition:
|
|
||||||
calendarCameraState.value.durationMs > 0
|
|
||||||
? `transform ${calendarCameraState.value.durationMs}ms cubic-bezier(0.16, 0.86, 0.18, 1)`
|
|
||||||
: "none",
|
|
||||||
willChange: "transform",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
function clearCalendarZoomPrime() {
|
function clearCalendarZoomPrime() {
|
||||||
if (calendarZoomPrimeTimer) {
|
if (calendarZoomPrimeTimer) {
|
||||||
@@ -432,194 +453,226 @@ export function useCalendar(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
return calendarContentWrapRef.value?.querySelector<HTMLElement>(selector) ?? null;
|
return calendarContentWrapRef.value?.querySelector<HTMLElement>(selector) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCalendarViewportRect(): CalendarRect | null {
|
|
||||||
const wrapRect = calendarContentWrapRef.value?.getBoundingClientRect();
|
|
||||||
if (!wrapRect) return null;
|
|
||||||
return {
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
width: Math.max(24, wrapRect.width),
|
|
||||||
height: Math.max(24, wrapRect.height),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCalendarCameraViewportRect() {
|
|
||||||
const viewport = calendarContentScrollRef.value?.getBoundingClientRect();
|
|
||||||
if (!viewport) return null;
|
|
||||||
return {
|
|
||||||
width: Math.max(24, viewport.width),
|
|
||||||
height: Math.max(24, viewport.height),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElementRectInCalendar(element: HTMLElement | null): CalendarRect | null {
|
|
||||||
if (!element) return null;
|
|
||||||
const wrapRect = calendarContentWrapRef.value?.getBoundingClientRect();
|
|
||||||
if (!wrapRect) return null;
|
|
||||||
const rect = element.getBoundingClientRect();
|
|
||||||
const left = Math.max(0, Math.min(rect.left - wrapRect.left, wrapRect.width));
|
|
||||||
const top = Math.max(0, Math.min(rect.top - wrapRect.top, wrapRect.height));
|
|
||||||
const right = Math.max(0, Math.min(rect.right - wrapRect.left, wrapRect.width));
|
|
||||||
const bottom = Math.max(0, Math.min(rect.bottom - wrapRect.top, wrapRect.height));
|
|
||||||
const visibleWidth = right - left;
|
|
||||||
const visibleHeight = bottom - top;
|
|
||||||
if (visibleWidth < 2 || visibleHeight < 2) return null;
|
|
||||||
const width = Math.min(Math.max(24, visibleWidth), wrapRect.width - left);
|
|
||||||
const height = Math.min(Math.max(24, visibleHeight), wrapRect.height - top);
|
|
||||||
return { left, top, width, height };
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElementRectInScene(element: HTMLElement | null): CalendarRect | null {
|
|
||||||
if (!element) return null;
|
|
||||||
const sceneRect = calendarSceneRef.value?.getBoundingClientRect();
|
|
||||||
if (!sceneRect) return null;
|
|
||||||
const rect = element.getBoundingClientRect();
|
|
||||||
const left = rect.left - sceneRect.left;
|
|
||||||
const top = rect.top - sceneRect.top;
|
|
||||||
const width = Math.max(24, rect.width);
|
|
||||||
const height = Math.max(24, rect.height);
|
|
||||||
return { left, top, width, height };
|
|
||||||
}
|
|
||||||
|
|
||||||
function fallbackZoomOriginRectInScene(): CalendarRect | null {
|
|
||||||
const viewport = getCalendarCameraViewportRect();
|
|
||||||
const scroll = calendarContentScrollRef.value;
|
|
||||||
if (!viewport || !scroll) return null;
|
|
||||||
const width = Math.max(96, Math.round(viewport.width * 0.28));
|
|
||||||
const height = Math.max(64, Math.round(viewport.height * 0.24));
|
|
||||||
return {
|
|
||||||
left: scroll.scrollLeft + Math.max(0, (viewport.width - width) / 2),
|
|
||||||
top: scroll.scrollTop + Math.max(0, (viewport.height - height) / 2),
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function weekRowStartForDate(key: string) {
|
function weekRowStartForDate(key: string) {
|
||||||
const date = new Date(`${key}T00:00:00`);
|
const date = new Date(`${key}T00:00:00`);
|
||||||
date.setDate(date.getDate() - date.getDay());
|
date.setDate(date.getDate() - date.getDay());
|
||||||
return dayKey(date);
|
return dayKey(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isoWeekNumber(dateString: string): number {
|
||||||
|
const d = new Date(`${dateString}T00:00:00`);
|
||||||
|
const t = new Date(d.getTime());
|
||||||
|
t.setDate(t.getDate() + 3 - ((t.getDay() + 6) % 7));
|
||||||
|
const y = new Date(t.getFullYear(), 0, 4);
|
||||||
|
return 1 + Math.round(((t.getTime() - y.getTime()) / 86400000 - 3 + ((y.getDay() + 6) % 7)) / 7);
|
||||||
|
}
|
||||||
|
|
||||||
function nextAnimationFrame() {
|
function nextAnimationFrame() {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
requestAnimationFrame(() => resolve());
|
requestAnimationFrame(() => resolve());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function waitForTransformTransition(element: HTMLElement) {
|
// ---------------------------------------------------------------------------
|
||||||
return new Promise<void>((resolve) => {
|
// GSAP animation helpers
|
||||||
let settled = false;
|
// ---------------------------------------------------------------------------
|
||||||
const finish = () => {
|
function cloneElementStyleToFlyRect(source: HTMLElement, flyEl: HTMLElement) {
|
||||||
if (settled) return;
|
const s = getComputedStyle(source);
|
||||||
settled = true;
|
flyEl.style.borderColor = s.borderColor;
|
||||||
element.removeEventListener("transitionend", onTransitionEnd);
|
flyEl.style.borderWidth = s.borderWidth;
|
||||||
clearTimeout(fallbackTimer);
|
flyEl.style.borderStyle = s.borderStyle;
|
||||||
resolve();
|
flyEl.style.backgroundColor = s.backgroundColor;
|
||||||
};
|
flyEl.style.borderRadius = s.borderRadius;
|
||||||
const onTransitionEnd = (event: TransitionEvent) => {
|
flyEl.style.boxShadow = s.boxShadow;
|
||||||
if (event.target !== element) return;
|
|
||||||
if (event.propertyName !== "transform") return;
|
|
||||||
finish();
|
|
||||||
};
|
|
||||||
const fallbackTimer = setTimeout(() => finish(), CALENDAR_ZOOM_DURATION_MS + 160);
|
|
||||||
element.addEventListener("transitionend", onTransitionEnd);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fadeOutCalendarSiblings(sourceElement: HTMLElement) {
|
function resetFlyRectStyle(flyEl: HTMLElement) {
|
||||||
const scene = calendarSceneRef.value;
|
flyEl.style.borderColor = "";
|
||||||
if (!scene) return () => {};
|
flyEl.style.borderWidth = "";
|
||||||
const targets = Array.from(scene.querySelectorAll<HTMLElement>(".calendar-hover-targetable"));
|
flyEl.style.borderStyle = "";
|
||||||
const siblings = targets.filter((element) => {
|
flyEl.style.backgroundColor = "";
|
||||||
if (element === sourceElement) return false;
|
flyEl.style.borderRadius = "";
|
||||||
if (sourceElement.contains(element)) return false;
|
flyEl.style.boxShadow = "";
|
||||||
if (element.contains(sourceElement)) return false;
|
flyEl.innerHTML = "";
|
||||||
return true;
|
|
||||||
});
|
|
||||||
const snapshots = siblings.map((element) => ({
|
|
||||||
element,
|
|
||||||
opacity: element.style.opacity,
|
|
||||||
pointerEvents: element.style.pointerEvents,
|
|
||||||
transition: element.style.transition,
|
|
||||||
}));
|
|
||||||
for (const { element } of snapshots) {
|
|
||||||
element.style.transition = "opacity 180ms ease";
|
|
||||||
element.style.opacity = "0";
|
|
||||||
element.style.pointerEvents = "none";
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
for (const snapshot of snapshots) {
|
|
||||||
snapshot.element.style.opacity = snapshot.opacity;
|
|
||||||
snapshot.element.style.pointerEvents = snapshot.pointerEvents;
|
|
||||||
snapshot.element.style.transition = snapshot.transition;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRenderableRect(rect: DOMRect | null) {
|
function extractSourceLabel(sourceElement: HTMLElement, viewBefore: string): string {
|
||||||
return Boolean(rect && rect.width >= 2 && rect.height >= 2);
|
if (viewBefore === "year") {
|
||||||
|
const p = sourceElement.querySelector("p");
|
||||||
|
return p?.textContent?.trim() ?? "";
|
||||||
|
}
|
||||||
|
if (viewBefore === "month" || viewBefore === "agenda") {
|
||||||
|
const wn = sourceElement.querySelector(".calendar-week-number");
|
||||||
|
return wn ? `Week ${wn.textContent?.trim()}` : "";
|
||||||
|
}
|
||||||
|
if (viewBefore === "week") {
|
||||||
|
const p = sourceElement.querySelector("p");
|
||||||
|
return p?.textContent?.trim() ?? "";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findSourceTitleElement(sourceElement: HTMLElement, viewBefore: string): HTMLElement | null {
|
||||||
|
if (viewBefore === "year") {
|
||||||
|
return sourceElement.parentElement?.querySelector<HTMLElement>(".calendar-card-title") ?? null;
|
||||||
|
}
|
||||||
|
if (viewBefore === "month" || viewBefore === "agenda") {
|
||||||
|
return sourceElement.querySelector<HTMLElement>(".calendar-week-number") ?? null;
|
||||||
|
}
|
||||||
|
if (viewBefore === "week") {
|
||||||
|
return sourceElement.parentElement?.querySelector<HTMLElement>(".calendar-card-title") ?? null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFlyLabelStyle(el: HTMLElement) {
|
||||||
|
el.textContent = "";
|
||||||
|
el.style.fontWeight = "";
|
||||||
|
el.style.color = "";
|
||||||
|
el.style.fontSize = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFlyRectSkeletonContent(): string {
|
||||||
|
return `<div class="calendar-fly-content"><div class="calendar-fly-skeleton">
|
||||||
|
<div class="calendar-fly-skeleton-line" style="width:70%"></div>
|
||||||
|
<div class="calendar-fly-skeleton-line" style="width:45%"></div>
|
||||||
|
<div class="calendar-fly-skeleton-line" style="width:60%"></div>
|
||||||
|
</div></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// GSAP zoom animations
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
async function animateCalendarFlipTransition(
|
async function animateCalendarFlipTransition(
|
||||||
sourceElement: HTMLElement | null,
|
_sourceElement: HTMLElement | null,
|
||||||
apply: () => void,
|
apply: () => void,
|
||||||
resolveTarget: () => HTMLElement | null,
|
resolveTarget: () => HTMLElement | null,
|
||||||
) {
|
) {
|
||||||
clearCalendarZoomPrime();
|
clearCalendarZoomPrime();
|
||||||
calendarZoomBusy.value = true;
|
calendarZoomBusy.value = true;
|
||||||
let restoreSiblings = () => {};
|
calendarKillTweens();
|
||||||
let animatedElement: HTMLElement | null = null;
|
|
||||||
let snapshot: {
|
const flyEl = calendarFlyRectRef.value;
|
||||||
transform: string;
|
const wrapEl = calendarContentWrapRef.value;
|
||||||
transition: string;
|
const sceneEl = calendarSceneRef.value;
|
||||||
transformOrigin: string;
|
const flyLabelEl = calendarFlyLabelRef.value;
|
||||||
willChange: string;
|
const toolbarLabelEl = calendarToolbarLabelRef.value;
|
||||||
zIndex: string;
|
|
||||||
} | null = null;
|
if (!flyEl || !wrapEl) {
|
||||||
|
apply();
|
||||||
|
calendarZoomBusy.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sourceRect = sourceElement?.getBoundingClientRect() ?? null;
|
const wrapRect = wrapEl.getBoundingClientRect();
|
||||||
|
const flyLabelText = calendarPeriodLabel.value;
|
||||||
|
|
||||||
|
// 1. Fade out current scene
|
||||||
|
if (sceneEl) {
|
||||||
|
await calendarTweenTo(sceneEl, { opacity: 0, duration: CALENDAR_FADE_DURATION, ease: "power2.in" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Position fly rect at full viewport
|
||||||
|
gsap.set(flyEl, { left: 0, top: 0, width: wrapRect.width, height: wrapRect.height, opacity: 1 });
|
||||||
|
flyEl.style.borderRadius = "0.75rem";
|
||||||
|
flyEl.style.borderWidth = "1px";
|
||||||
|
flyEl.style.borderStyle = "solid";
|
||||||
|
flyEl.style.borderColor = "color-mix(in oklab, var(--color-base-300) 100%, transparent)";
|
||||||
|
flyEl.style.backgroundColor = "color-mix(in oklab, var(--color-base-100) 100%, transparent)";
|
||||||
|
flyEl.style.boxShadow = "";
|
||||||
|
flyEl.innerHTML = buildFlyRectSkeletonContent();
|
||||||
|
calendarFlyVisible.value = true;
|
||||||
|
|
||||||
|
// 3. Position flying label at toolbar
|
||||||
|
let flyLabelReady = false;
|
||||||
|
if (flyLabelEl && toolbarLabelEl && flyLabelText) {
|
||||||
|
const sectionRect = (flyLabelEl.offsetParent as HTMLElement | null)?.getBoundingClientRect();
|
||||||
|
if (sectionRect) {
|
||||||
|
const toolbarStyle = getComputedStyle(toolbarLabelEl);
|
||||||
|
const toolbarTextRange = document.createRange();
|
||||||
|
toolbarTextRange.selectNodeContents(toolbarLabelEl);
|
||||||
|
const toolbarTextRect = toolbarTextRange.getBoundingClientRect();
|
||||||
|
flyLabelEl.textContent = flyLabelText;
|
||||||
|
flyLabelEl.style.fontWeight = toolbarStyle.fontWeight;
|
||||||
|
flyLabelEl.style.color = toolbarStyle.color;
|
||||||
|
gsap.set(flyLabelEl, {
|
||||||
|
left: toolbarTextRect.left - sectionRect.left,
|
||||||
|
top: toolbarTextRect.top - sectionRect.top,
|
||||||
|
fontSize: parseFloat(toolbarStyle.fontSize),
|
||||||
|
opacity: 1,
|
||||||
|
});
|
||||||
|
toolbarLabelEl.style.opacity = "0";
|
||||||
|
calendarFlyLabelVisible.value = true;
|
||||||
|
flyLabelReady = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Switch to parent view
|
||||||
apply();
|
apply();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
const targetElement = resolveTarget();
|
|
||||||
const targetRect = targetElement?.getBoundingClientRect() ?? null;
|
|
||||||
if (!targetElement || !isRenderableRect(sourceRect) || !isRenderableRect(targetRect)) return;
|
|
||||||
|
|
||||||
restoreSiblings = fadeOutCalendarSiblings(targetElement);
|
|
||||||
animatedElement = targetElement;
|
|
||||||
snapshot = {
|
|
||||||
transform: targetElement.style.transform,
|
|
||||||
transition: targetElement.style.transition,
|
|
||||||
transformOrigin: targetElement.style.transformOrigin,
|
|
||||||
willChange: targetElement.style.willChange,
|
|
||||||
zIndex: targetElement.style.zIndex,
|
|
||||||
};
|
|
||||||
|
|
||||||
const dx = sourceRect!.left - targetRect!.left;
|
|
||||||
const dy = sourceRect!.top - targetRect!.top;
|
|
||||||
const sx = Math.max(0.01, sourceRect!.width / targetRect!.width);
|
|
||||||
const sy = Math.max(0.01, sourceRect!.height / targetRect!.height);
|
|
||||||
|
|
||||||
targetElement.style.transformOrigin = "top left";
|
|
||||||
targetElement.style.willChange = "transform";
|
|
||||||
targetElement.style.zIndex = "24";
|
|
||||||
targetElement.style.transition = "none";
|
|
||||||
targetElement.style.transform = `translate3d(${dx}px, ${dy}px, 0px) scale(${sx}, ${sy})`;
|
|
||||||
targetElement.getBoundingClientRect();
|
|
||||||
await nextAnimationFrame();
|
await nextAnimationFrame();
|
||||||
|
|
||||||
targetElement.style.transition = `transform ${CALENDAR_ZOOM_DURATION_MS}ms cubic-bezier(0.16, 0.86, 0.18, 1)`;
|
// 5. Find target element
|
||||||
targetElement.style.transform = "translate3d(0px, 0px, 0px) scale(1, 1)";
|
const targetElement = resolveTarget();
|
||||||
await waitForTransformTransition(targetElement);
|
const targetRect = targetElement?.getBoundingClientRect() ?? null;
|
||||||
} finally {
|
const viewAfter = calendarView.value;
|
||||||
if (animatedElement && snapshot) {
|
|
||||||
animatedElement.style.transform = snapshot.transform;
|
if (targetElement && targetRect && targetRect.width >= 2 && targetRect.height >= 2) {
|
||||||
animatedElement.style.transition = snapshot.transition;
|
cloneElementStyleToFlyRect(targetElement, flyEl);
|
||||||
animatedElement.style.transformOrigin = snapshot.transformOrigin;
|
targetElement.style.opacity = "0";
|
||||||
animatedElement.style.willChange = snapshot.willChange;
|
|
||||||
animatedElement.style.zIndex = snapshot.zIndex;
|
const tgtLeft = targetRect.left - wrapRect.left;
|
||||||
|
const tgtTop = targetRect.top - wrapRect.top;
|
||||||
|
|
||||||
|
const targetTitleEl = findSourceTitleElement(targetElement, viewAfter);
|
||||||
|
let flyLabelPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
|
if (flyLabelReady && flyLabelEl && targetTitleEl) {
|
||||||
|
const sectionRect = (flyLabelEl.offsetParent as HTMLElement | null)?.getBoundingClientRect();
|
||||||
|
if (sectionRect) {
|
||||||
|
const titleRect = targetTitleEl.getBoundingClientRect();
|
||||||
|
const titleStyle = getComputedStyle(targetTitleEl);
|
||||||
|
targetTitleEl.style.opacity = "0";
|
||||||
|
flyLabelPromise = calendarTweenTo(flyLabelEl, {
|
||||||
|
left: titleRect.left - sectionRect.left,
|
||||||
|
top: titleRect.top - sectionRect.top,
|
||||||
|
fontSize: parseFloat(titleStyle.fontSize),
|
||||||
|
color: titleStyle.color,
|
||||||
|
duration: CALENDAR_FLY_DURATION,
|
||||||
|
ease: CALENDAR_EASE,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
restoreSiblings();
|
}
|
||||||
|
|
||||||
|
// 6. Animate fly rect → target (concurrent with label)
|
||||||
|
const flyRectPromise = calendarTweenTo(flyEl, {
|
||||||
|
left: tgtLeft, top: tgtTop,
|
||||||
|
width: targetRect.width, height: targetRect.height,
|
||||||
|
duration: CALENDAR_FLY_DURATION, ease: CALENDAR_EASE,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([flyRectPromise, flyLabelPromise].filter(Boolean));
|
||||||
|
targetElement.style.opacity = "";
|
||||||
|
if (targetTitleEl) targetTitleEl.style.opacity = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Cleanup
|
||||||
|
calendarFlyLabelVisible.value = false;
|
||||||
|
if (flyLabelEl) resetFlyLabelStyle(flyLabelEl);
|
||||||
|
if (toolbarLabelEl) toolbarLabelEl.style.opacity = "";
|
||||||
|
calendarFlyVisible.value = false;
|
||||||
|
resetFlyRectStyle(flyEl);
|
||||||
|
if (sceneEl) {
|
||||||
|
gsap.set(sceneEl, { opacity: 0 });
|
||||||
|
await calendarTweenTo(sceneEl, { opacity: 1, duration: 0.25, ease: "power2.out" });
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
calendarFlyVisible.value = false;
|
||||||
|
calendarFlyLabelVisible.value = false;
|
||||||
|
resetFlyRectStyle(flyEl);
|
||||||
|
if (flyLabelEl) resetFlyLabelStyle(flyLabelEl);
|
||||||
|
if (toolbarLabelEl) toolbarLabelEl.style.opacity = "";
|
||||||
calendarZoomBusy.value = false;
|
calendarZoomBusy.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -630,60 +683,135 @@ export function useCalendar(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
) {
|
) {
|
||||||
clearCalendarZoomPrime();
|
clearCalendarZoomPrime();
|
||||||
calendarZoomBusy.value = true;
|
calendarZoomBusy.value = true;
|
||||||
let restoreSiblings = () => {};
|
calendarKillTweens();
|
||||||
let snapshot: {
|
|
||||||
transform: string;
|
const flyEl = calendarFlyRectRef.value;
|
||||||
transition: string;
|
const wrapEl = calendarContentWrapRef.value;
|
||||||
transformOrigin: string;
|
const scrollEl = calendarContentScrollRef.value;
|
||||||
willChange: string;
|
const sceneEl = calendarSceneRef.value;
|
||||||
zIndex: string;
|
const flyLabelEl = calendarFlyLabelRef.value;
|
||||||
} | null = null;
|
const toolbarLabelEl = calendarToolbarLabelRef.value;
|
||||||
|
|
||||||
|
if (!sourceElement || !flyEl || !wrapEl || !scrollEl) {
|
||||||
|
apply();
|
||||||
|
calendarZoomBusy.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const viewportRect = calendarContentScrollRef.value?.getBoundingClientRect() ?? null;
|
const wrapRect = wrapEl.getBoundingClientRect();
|
||||||
const sourceRect = sourceElement?.getBoundingClientRect() ?? null;
|
const sourceRect = sourceElement.getBoundingClientRect();
|
||||||
if (!sourceElement || !isRenderableRect(viewportRect) || !isRenderableRect(sourceRect)) {
|
if (sourceRect.width < 2 || sourceRect.height < 2) {
|
||||||
apply();
|
apply();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreSiblings = fadeOutCalendarSiblings(sourceElement);
|
// 1. Find source title element and extract label
|
||||||
snapshot = {
|
const viewBefore = calendarView.value;
|
||||||
transform: sourceElement.style.transform,
|
const labelText = extractSourceLabel(sourceElement, viewBefore);
|
||||||
transition: sourceElement.style.transition,
|
const sourceTitleEl = findSourceTitleElement(sourceElement, viewBefore);
|
||||||
transformOrigin: sourceElement.style.transformOrigin,
|
|
||||||
willChange: sourceElement.style.willChange,
|
|
||||||
zIndex: sourceElement.style.zIndex,
|
|
||||||
};
|
|
||||||
|
|
||||||
const dx = viewportRect!.left - sourceRect!.left;
|
// 2. Fade out siblings (cards + external titles + week numbers)
|
||||||
const dy = viewportRect!.top - sourceRect!.top;
|
const allFadable = Array.from(
|
||||||
const sx = Math.max(0.01, viewportRect!.width / sourceRect!.width);
|
sceneEl?.querySelectorAll<HTMLElement>(".calendar-hover-targetable, .calendar-card-title, .calendar-week-number") ?? [],
|
||||||
const sy = Math.max(0.01, viewportRect!.height / sourceRect!.height);
|
);
|
||||||
|
const siblings = allFadable.filter(
|
||||||
|
(el) => el !== sourceElement && el !== sourceTitleEl
|
||||||
|
&& !sourceElement.contains(el) && !el.contains(sourceElement),
|
||||||
|
);
|
||||||
|
await calendarTweenTo(siblings, { opacity: 0, duration: CALENDAR_FADE_DURATION, ease: "power2.in" });
|
||||||
|
|
||||||
|
// 3. Fade out source children
|
||||||
|
const sourceChildren = Array.from(sourceElement.children) as HTMLElement[];
|
||||||
|
await calendarTweenTo(sourceChildren, { opacity: 0, duration: 0.12, ease: "power2.in" });
|
||||||
|
|
||||||
|
// 4. Clone source style to fly-rect, inject skeleton
|
||||||
|
cloneElementStyleToFlyRect(sourceElement, flyEl);
|
||||||
|
flyEl.innerHTML = buildFlyRectSkeletonContent();
|
||||||
|
const srcLeft = sourceRect.left - wrapRect.left;
|
||||||
|
const srcTop = sourceRect.top - wrapRect.top;
|
||||||
|
gsap.set(flyEl, { left: srcLeft, top: srcTop, width: sourceRect.width, height: sourceRect.height, opacity: 1 });
|
||||||
|
|
||||||
|
// 5. Swap: hide source, show fly-rect
|
||||||
|
sourceElement.style.opacity = "0";
|
||||||
|
calendarFlyVisible.value = true;
|
||||||
|
|
||||||
|
// 6. Setup flying label: source title → toolbar
|
||||||
|
let flyLabelPromise: Promise<void> | null = null;
|
||||||
|
if (flyLabelEl && toolbarLabelEl && sourceTitleEl && labelText) {
|
||||||
|
const sectionRect = (flyLabelEl.offsetParent as HTMLElement | null)?.getBoundingClientRect();
|
||||||
|
if (sectionRect) {
|
||||||
|
const srcTitleRect = sourceTitleEl.getBoundingClientRect();
|
||||||
|
const srcTitleStyle = getComputedStyle(sourceTitleEl);
|
||||||
|
const toolbarStyle = getComputedStyle(toolbarLabelEl);
|
||||||
|
|
||||||
|
flyLabelEl.textContent = labelText;
|
||||||
|
flyLabelEl.style.fontWeight = srcTitleStyle.fontWeight;
|
||||||
|
flyLabelEl.style.color = srcTitleStyle.color;
|
||||||
|
gsap.set(flyLabelEl, {
|
||||||
|
left: srcTitleRect.left - sectionRect.left,
|
||||||
|
top: srcTitleRect.top - sectionRect.top,
|
||||||
|
fontSize: parseFloat(srcTitleStyle.fontSize),
|
||||||
|
opacity: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
sourceTitleEl.style.opacity = "0";
|
||||||
|
toolbarLabelEl.style.opacity = "0";
|
||||||
|
calendarFlyLabelVisible.value = true;
|
||||||
|
|
||||||
sourceElement.style.transformOrigin = "top left";
|
|
||||||
sourceElement.style.willChange = "transform";
|
|
||||||
sourceElement.style.zIndex = "24";
|
|
||||||
sourceElement.style.transition = "none";
|
|
||||||
sourceElement.style.transform = "translate3d(0px, 0px, 0px) scale(1, 1)";
|
|
||||||
sourceElement.getBoundingClientRect();
|
|
||||||
await nextAnimationFrame();
|
await nextAnimationFrame();
|
||||||
|
const endFontSize = parseFloat(toolbarStyle.fontSize);
|
||||||
|
const toolbarTextRange = document.createRange();
|
||||||
|
toolbarTextRange.selectNodeContents(toolbarLabelEl);
|
||||||
|
const toolbarTextRect = toolbarTextRange.getBoundingClientRect();
|
||||||
|
|
||||||
sourceElement.style.transition = `transform ${CALENDAR_ZOOM_DURATION_MS}ms cubic-bezier(0.16, 0.86, 0.18, 1)`;
|
flyLabelPromise = calendarTweenTo(flyLabelEl, {
|
||||||
sourceElement.style.transform = `translate3d(${dx}px, ${dy}px, 0px) scale(${sx}, ${sy})`;
|
left: toolbarTextRect.left - sectionRect.left,
|
||||||
await waitForTransformTransition(sourceElement);
|
top: toolbarTextRect.top - sectionRect.top,
|
||||||
|
fontSize: endFontSize,
|
||||||
|
color: toolbarStyle.color,
|
||||||
|
duration: CALENDAR_FLY_DURATION,
|
||||||
|
ease: CALENDAR_EASE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Animate fly-rect to viewport (concurrent with label)
|
||||||
|
const flyRectPromise = calendarTweenTo(flyEl, {
|
||||||
|
left: 0, top: 0, width: wrapRect.width, height: wrapRect.height,
|
||||||
|
duration: CALENDAR_FLY_DURATION, ease: CALENDAR_EASE,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([flyRectPromise, flyLabelPromise].filter(Boolean));
|
||||||
|
|
||||||
|
// 8. Cleanup flying label
|
||||||
|
calendarFlyLabelVisible.value = false;
|
||||||
|
if (flyLabelEl) resetFlyLabelStyle(flyLabelEl);
|
||||||
|
if (toolbarLabelEl) toolbarLabelEl.style.opacity = "";
|
||||||
|
if (sourceTitleEl) sourceTitleEl.style.opacity = "";
|
||||||
|
|
||||||
|
// 9. Switch view
|
||||||
apply();
|
apply();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
await nextAnimationFrame();
|
|
||||||
} finally {
|
// 10. Hide fly-rect, fade in
|
||||||
if (sourceElement && snapshot) {
|
calendarFlyVisible.value = false;
|
||||||
sourceElement.style.transform = snapshot.transform;
|
resetFlyRectStyle(flyEl);
|
||||||
sourceElement.style.transition = snapshot.transition;
|
if (sceneEl) {
|
||||||
sourceElement.style.transformOrigin = snapshot.transformOrigin;
|
gsap.set(sceneEl, { opacity: 0 });
|
||||||
sourceElement.style.willChange = snapshot.willChange;
|
await calendarTweenTo(sceneEl, { opacity: 1, duration: 0.25, ease: "power2.out" });
|
||||||
sourceElement.style.zIndex = snapshot.zIndex;
|
|
||||||
}
|
}
|
||||||
restoreSiblings();
|
|
||||||
|
// 11. Restore
|
||||||
|
sourceElement.style.opacity = "";
|
||||||
|
for (const child of sourceChildren) child.style.opacity = "";
|
||||||
|
for (const el of siblings) el.style.opacity = "";
|
||||||
|
} finally {
|
||||||
|
calendarFlyVisible.value = false;
|
||||||
|
calendarFlyLabelVisible.value = false;
|
||||||
|
resetFlyRectStyle(flyEl);
|
||||||
|
if (flyLabelEl) resetFlyLabelStyle(flyLabelEl);
|
||||||
|
if (toolbarLabelEl) toolbarLabelEl.style.opacity = "";
|
||||||
calendarZoomBusy.value = false;
|
calendarZoomBusy.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -879,13 +1007,15 @@ export function useCalendar(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const monthRows = computed(() => {
|
const monthRows = computed(() => {
|
||||||
const rows: Array<{ key: string; startKey: string; cells: typeof monthCells.value }> = [];
|
const rows: Array<{ key: string; startKey: string; weekNumber: number; cells: typeof monthCells.value }> = [];
|
||||||
for (let index = 0; index < monthCells.value.length; index += 7) {
|
for (let index = 0; index < monthCells.value.length; index += 7) {
|
||||||
const cells = monthCells.value.slice(index, index + 7);
|
const cells = monthCells.value.slice(index, index + 7);
|
||||||
if (!cells.length) continue;
|
if (!cells.length) continue;
|
||||||
|
const startKey = cells[0]?.key ?? selectedDateKey.value;
|
||||||
rows.push({
|
rows.push({
|
||||||
key: `${cells[0]?.key ?? index}-week-row`,
|
key: `${cells[0]?.key ?? index}-week-row`,
|
||||||
startKey: cells[0]?.key ?? selectedDateKey.value,
|
startKey,
|
||||||
|
weekNumber: isoWeekNumber(startKey),
|
||||||
cells,
|
cells,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1217,40 +1347,42 @@ export function useCalendar(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
calendarContentWrapRef,
|
calendarContentWrapRef,
|
||||||
calendarContentScrollRef,
|
calendarContentScrollRef,
|
||||||
calendarSceneRef,
|
calendarSceneRef,
|
||||||
|
calendarFlyRectRef,
|
||||||
|
calendarFlyVisible,
|
||||||
|
calendarFlyLabelRef,
|
||||||
|
calendarFlyLabelVisible,
|
||||||
|
calendarToolbarLabelRef,
|
||||||
calendarViewportHeight,
|
calendarViewportHeight,
|
||||||
calendarHoveredMonthIndex,
|
calendarHoveredMonthIndex,
|
||||||
calendarHoveredWeekStartKey,
|
calendarHoveredWeekStartKey,
|
||||||
calendarHoveredDayKey,
|
calendarHoveredDayKey,
|
||||||
calendarZoomBusy,
|
calendarZoomBusy,
|
||||||
calendarCameraState,
|
|
||||||
calendarZoomPrimeToken,
|
calendarZoomPrimeToken,
|
||||||
calendarZoomPrimeScale,
|
calendarZoomPrimeScale,
|
||||||
calendarZoomPrimeTicks,
|
calendarZoomPrimeTicks,
|
||||||
normalizedCalendarView,
|
normalizedCalendarView,
|
||||||
calendarZoomLevelIndex,
|
calendarZoomLevelIndex,
|
||||||
calendarSceneTransformStyle,
|
|
||||||
calendarZoomOrder,
|
calendarZoomOrder,
|
||||||
|
|
||||||
// Zoom / camera setters
|
// Zoom / camera setters
|
||||||
setCalendarContentWrapRef,
|
setCalendarContentWrapRef,
|
||||||
setCalendarContentScrollRef,
|
setCalendarContentScrollRef,
|
||||||
setCalendarSceneRef,
|
setCalendarSceneRef,
|
||||||
|
setCalendarFlyRectRef,
|
||||||
|
setCalendarFlyLabelRef,
|
||||||
|
setCalendarToolbarLabelRef,
|
||||||
setCalendarHoveredMonthIndex,
|
setCalendarHoveredMonthIndex,
|
||||||
setCalendarHoveredWeekStartKey,
|
setCalendarHoveredWeekStartKey,
|
||||||
setCalendarHoveredDayKey,
|
setCalendarHoveredDayKey,
|
||||||
onCalendarSceneMouseLeave,
|
onCalendarSceneMouseLeave,
|
||||||
clearCalendarZoomPrime,
|
clearCalendarZoomPrime,
|
||||||
|
calendarKillTweens,
|
||||||
calendarPrimeMonthToken,
|
calendarPrimeMonthToken,
|
||||||
calendarPrimeWeekToken,
|
calendarPrimeWeekToken,
|
||||||
calendarPrimeDayToken,
|
calendarPrimeDayToken,
|
||||||
calendarPrimeStyle,
|
calendarPrimeStyle,
|
||||||
maybePrimeWheelZoom,
|
maybePrimeWheelZoom,
|
||||||
queryCalendarElement,
|
queryCalendarElement,
|
||||||
getCalendarViewportRect,
|
|
||||||
getCalendarCameraViewportRect,
|
|
||||||
getElementRectInCalendar,
|
|
||||||
getElementRectInScene,
|
|
||||||
fallbackZoomOriginRectInScene,
|
|
||||||
weekRowStartForDate,
|
weekRowStartForDate,
|
||||||
|
|
||||||
// Zoom animations
|
// Zoom animations
|
||||||
|
|||||||
Reference in New Issue
Block a user