Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brown-cats-jump.md
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions apps/docs/content/docs/guide/tailwind-v4.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}}
/>
Expand All @@ -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

Expand Down
57 changes: 41 additions & 16 deletions packages/components/calendar/src/use-calendar-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -37,6 +36,7 @@ export function useCalendarPicker(props: CalendarPickerProps) {

const monthsItemsRef = useRef<ItemsRefMap>();
const yearsItemsRef = useRef<ItemsRefMap>();
const focusedDateRef = useRef<CalendarDate>(state.focusedDate);

const monthDateFormatter = useDateFormatter({
month: "long",
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 0 additions & 7 deletions packages/core/react/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
<a href="https://ph.heroui.chat?utm_source=https://github.com/heroui-inc/heroui&utm_medium=banner">
<img alt="HeroUI Chat on Product Hunt" src="https://heroui-chat-assets.nyc3.cdn.digitaloceanspaces.com/github_banner-round.png">
</a>

<br/>
<br/>

<p align="center">
<a href="https://heroui.com">
<img width="20%" src="https://raw.githubusercontent.com/heroui-inc/heroui/main/apps/docs/public/isotipo.png" alt="heorui" />
Expand Down