diff --git a/.changeset/brown-cats-jump.md b/.changeset/brown-cats-jump.md new file mode 100644 index 0000000000..bd9da7244a --- /dev/null +++ b/.changeset/brown-cats-jump.md @@ -0,0 +1,5 @@ +--- +"@heroui/calendar": patch +--- + +Replace rectangle intersection detection with center-point distance calculation to make the calendar picker more resilient when browser zoom is changed. The new approach finds the closest picker item to the highlight element's center, preventing mismatches between displayed and selected year / month. (#5117) diff --git a/apps/docs/content/docs/guide/tailwind-v4.mdx b/apps/docs/content/docs/guide/tailwind-v4.mdx index d1bfb5e842..e3b100b815 100644 --- a/apps/docs/content/docs/guide/tailwind-v4.mdx +++ b/apps/docs/content/docs/guide/tailwind-v4.mdx @@ -23,7 +23,7 @@ TailwindCSS v4 is now available in Beta! This guide will help you migrate your e cli: "npx heroui-cli@latest upgrade --all --beta", pnpm: "pnpm install @heroui/react@beta", npm: "npm install @heroui/react@beta", - yarn: "yarn install @heroui/react@beta", + yarn: "yarn add @heroui/react@beta", bun: "bun install @heroui/react@beta" }} /> @@ -34,7 +34,7 @@ TailwindCSS v4 is now available in Beta! This guide will help you migrate your e #### Without `tailwind.config.js` -Since Tailwind v4 favors a CSS-first approach, `tailwind.config.js` will not be required. +Since Tailwind v4 favors a CSS-first approach, `tailwind.config.js` will not be required. Create `hero.ts` file diff --git a/packages/components/calendar/src/use-calendar-picker.ts b/packages/components/calendar/src/use-calendar-picker.ts index 22cbbbd8cd..0dfba553b0 100644 --- a/packages/components/calendar/src/use-calendar-picker.ts +++ b/packages/components/calendar/src/use-calendar-picker.ts @@ -5,7 +5,6 @@ import type {HTMLHeroUIProps} from "@heroui/system"; import {useDateFormatter} from "@react-aria/i18n"; import {useCallback, useRef, useEffect} from "react"; import {debounce} from "@heroui/shared-utils"; -import {areRectsIntersecting} from "@heroui/react-utils"; import scrollIntoView from "scroll-into-view-if-needed"; import {getMonthsInYear, getYearRange} from "./utils"; @@ -37,6 +36,7 @@ export function useCalendarPicker(props: CalendarPickerProps) { const monthsItemsRef = useRef(); const yearsItemsRef = useRef(); + const focusedDateRef = useRef(state.focusedDate); const monthDateFormatter = useDateFormatter({ month: "long", @@ -85,34 +85,59 @@ export function useCalendarPicker(props: CalendarPickerProps) { const handleListScroll = useCallback( (e: Event, highlightEl: HTMLElement | null, list: CalendarPickerListType) => { - if (!(e.target instanceof HTMLElement)) return; + if (!(e.target instanceof HTMLElement) || !highlightEl) return; const map = getItemsRefMap(list === "months" ? monthsItemsRef : yearsItemsRef); - const items = Array.from(map.values()); + const items = Array.from(map.entries()); - const item = items.find((itemEl) => { - const rect1 = itemEl.getBoundingClientRect(); - const rect2 = highlightEl?.getBoundingClientRect(); + const highlightRect = highlightEl.getBoundingClientRect(); - if (!rect2) { - return false; - } + const highlightCenter = { + x: highlightRect.left + highlightRect.width / 2, + y: highlightRect.top + highlightRect.height / 2, + }; + + let closestItem: [number, HTMLElement] | null = null; + + let minDistance = Infinity; - return areRectsIntersecting(rect1, rect2); - }); + for (const [value, itemEl] of items) { + const itemRect = itemEl.getBoundingClientRect(); + const itemCenter = { + x: itemRect.left + itemRect.width / 2, + y: itemRect.top + itemRect.height / 2, + }; - const itemValue = Number(item?.getAttribute("data-value")); + // Calculate distance between centers + const distance = Math.sqrt( + Math.pow(highlightCenter.x - itemCenter.x, 2) + + Math.pow(highlightCenter.y - itemCenter.y, 2), + ); - if (!itemValue) return; + if (distance < minDistance) { + minDistance = distance; + closestItem = [value, itemEl]; + } + } - let date = state.focusedDate.set(list === "months" ? {month: itemValue} : {year: itemValue}); + if (!closestItem) return; - state.setFocusedDate(date); + const [itemValue] = closestItem; + + const updatedDate = focusedDateRef.current.set( + list === "months" ? {month: itemValue} : {year: itemValue}, + ); + + state.setFocusedDate(updatedDate); }, - [state, isHeaderExpanded], + [isHeaderExpanded], ); + useEffect(() => { + focusedDateRef.current = state.focusedDate; + }, [state.focusedDate]); + // scroll to the selected month/year when the component is mounted/opened/closed useEffect(() => { if (!isHeaderExpanded) return; diff --git a/packages/core/react/README.md b/packages/core/react/README.md index 4dc196e2c6..a11dfd6aa0 100644 --- a/packages/core/react/README.md +++ b/packages/core/react/README.md @@ -1,10 +1,3 @@ - - HeroUI Chat on Product Hunt - - -
-
-

heorui