diff --git a/.changeset/zag-1-41-0.md b/.changeset/zag-1-41-0.md new file mode 100644 index 0000000000..4dab6e80a7 --- /dev/null +++ b/.changeset/zag-1-41-0.md @@ -0,0 +1,65 @@ +--- +'@ark-ui/react': minor +'@ark-ui/solid': minor +'@ark-ui/svelte': minor +'@ark-ui/vue': minor +--- + +### Added + +- **Floating Components**: Add `data-side` to placement-aware parts so you can style them based on the current placement (`top`, `bottom`, `left`, `right`). + + > Affects Color Picker, Combobox, Date Picker, Hover Card, Menu, Popover, Select, Tooltip, and Tour. + +- **Date Input**: Add `hideTimeZone` prop. When the value is a `ZonedDateTime`, the `timeZoneName` segment now renders automatically — set `hideTimeZone` to hide it. Arrow navigation and auto-advance after typing now reach read-only focusable segments too. + +- **Splitter** + - Accept CSS units (`px`, `em`, `rem`, `vh`, `vw`) for `defaultSize`, `minSize`, and `maxSize` in addition to percentages. + + ```jsx + + ``` + + - Add `resizeBehavior` per panel. Set to `"preserve-pixel-size"` to keep a panel's pixel size constant when the parent splitter group resizes. + + - Allow non-panel children (toolbars, rails, status bars) inside the splitter root. Use partial trigger ids (`"left:"`, `":right"`) to bind handles around the fixed element. + +### Fixed + +- **Accordion**: Remove redundant `aria-disabled` from item triggers. + +- **Color Picker**: Fire `onValueChangeEnd` when you pick a color with the EyeDropper API — matches the behavior when ending a drag on the area or channel sliders. + +- **Combobox**: Stop `Enter` from submitting the form when an item is highlighted, or when the typed value will be rejected by `allowCustomValue: false`. + +- **Date Input** + - Preserve entered segments when applying min/max. Values clamp segment-by-segment on blur, so `06/15/1999` with min `2000-01-01` becomes `06/15/2000` instead of snapping to `01/01/2000`. + - Fix range mode keyboard navigation so `ArrowRight` moves from the last segment of the start date to the first segment of the end date. + - Fix time-only formatters (no `year` segment) never firing `onValueChange`. + - Fix `setSegmentValue` reading stale display values. + - Fix `dayPeriod` (AM/PM) arrow up/down not updating the visible segment when `hourCycle` changes at runtime. + - Fix typing "A" / "P" on the `dayPeriod` segment not updating the visible AM/PM. + +- **Date Picker** + - Fix clearing the value not resetting `activeIndex` and `hoveredValue` in range mode when input parts are not rendered. + - Fix date input not being writable in locales with multi-character separators (e.g. `cs-CZ`, `sk-SK`, `hu-HU`, `ko-KR`). + - Fix Firefox issue where the native month/year ` setHideTimeZone(event.target.checked)} + type="checkbox" + /> + Hide time zone + + + Meeting time + + + + {(segment) => } + + + + + + + ) +} diff --git a/packages/react/src/components/date-input/tests/date-input.test.tsx b/packages/react/src/components/date-input/tests/date-input.test.tsx index d83aae741e..7da4d5862d 100644 --- a/packages/react/src/components/date-input/tests/date-input.test.tsx +++ b/packages/react/src/components/date-input/tests/date-input.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react' import user from '@testing-library/user-event' import { axe } from 'vitest-axe' import { ComponentUnderTest } from './basic' -import { parseDate } from '@internationalized/date' +import { parseDate, parseZonedDateTime } from '@internationalized/date' import { DateInput } from '..' describe('Date Input', () => { @@ -76,6 +76,27 @@ describe('Date Input', () => { expect(hiddenInput).toHaveValue('6/15/2024') }) + it('should render timeZoneName segment for ZonedDateTime values', () => { + render( + , + ) + expect(document.querySelector('[data-type="timeZoneName"]')).toBeInTheDocument() + }) + + it('should hide timeZoneName segment when hideTimeZone is true', () => { + render( + , + ) + expect(document.querySelector('[data-type="timeZoneName"]')).not.toBeInTheDocument() + }) + it('should sync hidden input values for range selection by index', () => { render( { 'required', 'selectionMode', 'timeZone', + 'hideTimeZone', 'translations', 'value', 'defaultValue', diff --git a/packages/solid/src/components/date-input/date-input.stories.tsx b/packages/solid/src/components/date-input/date-input.stories.tsx index 4df2dddaef..1a764c2198 100644 --- a/packages/solid/src/components/date-input/date-input.stories.tsx +++ b/packages/solid/src/components/date-input/date-input.stories.tsx @@ -19,5 +19,6 @@ export { Range } from './examples/range' export { ReadOnly } from './examples/read-only' export { RootProvider } from './examples/root-provider' export { RTL } from './examples/rtl' +export { TimeZone } from './examples/time-zone' export { WithClearButton } from './examples/with-clear-button' export { WithDatePicker } from './examples/with-date-picker' diff --git a/packages/solid/src/components/date-input/examples/time-zone.tsx b/packages/solid/src/components/date-input/examples/time-zone.tsx new file mode 100644 index 0000000000..fe52419c43 --- /dev/null +++ b/packages/solid/src/components/date-input/examples/time-zone.tsx @@ -0,0 +1,38 @@ +import { DateInput } from '@ark-ui/solid/date-input' +import { parseZonedDateTime } from '@internationalized/date' +import { createSignal } from 'solid-js' +import styles from 'styles/date-input.module.css' + +export const TimeZone = () => { + const [hideTimeZone, setHideTimeZone] = createSignal(false) + + return ( +
+ + + Meeting time + + + + {(segment) => } + + + + + +
+ ) +} diff --git a/packages/solid/src/components/date-input/tests/date-input.test.tsx b/packages/solid/src/components/date-input/tests/date-input.test.tsx index cc5328fbdf..9248eb78bf 100644 --- a/packages/solid/src/components/date-input/tests/date-input.test.tsx +++ b/packages/solid/src/components/date-input/tests/date-input.test.tsx @@ -1,4 +1,4 @@ -import { parseDate } from '@internationalized/date' +import { parseDate, parseZonedDateTime } from '@internationalized/date' import { render, screen } from '@solidjs/testing-library' import user from '@testing-library/user-event' import { axe } from 'vitest-axe' @@ -60,6 +60,27 @@ describe('Date Input', () => { expect(hiddenInput).toHaveValue('6/15/2024') }) + it('should render timeZoneName segment for ZonedDateTime values', () => { + render(() => ( + + )) + expect(document.querySelector('[data-type="timeZoneName"]')).toBeInTheDocument() + }) + + it('should hide timeZoneName segment when hideTimeZone is true', () => { + render(() => ( + + )) + expect(document.querySelector('[data-type="timeZoneName"]')).not.toBeInTheDocument() + }) + it('should sync hidden input values for range selection by index', () => { render(() => ( ({ + Component: TimeZoneExample, + }), +} + export const WithClearButton = { render: () => ({ Component: WithClearButtonExample, diff --git a/packages/svelte/src/lib/components/date-input/examples/time-zone.svelte b/packages/svelte/src/lib/components/date-input/examples/time-zone.svelte new file mode 100644 index 0000000000..6b08eee8ad --- /dev/null +++ b/packages/svelte/src/lib/components/date-input/examples/time-zone.svelte @@ -0,0 +1,32 @@ + + +
+ + + Meeting time + + + + {#snippet render(segment)} + + {/snippet} + + + + + +
diff --git a/packages/vue/package.json b/packages/vue/package.json index efe8fb435b..28497a7513 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -85,72 +85,72 @@ "sideEffects": false, "dependencies": { "@internationalized/date": "3.12.0", - "@zag-js/accordion": "1.40.0", - "@zag-js/anatomy": "1.40.0", - "@zag-js/angle-slider": "1.40.0", - "@zag-js/async-list": "1.40.0", - "@zag-js/auto-resize": "1.40.0", - "@zag-js/avatar": "1.40.0", - "@zag-js/carousel": "1.40.0", - "@zag-js/cascade-select": "1.40.0", - "@zag-js/checkbox": "1.40.0", - "@zag-js/clipboard": "1.40.0", - "@zag-js/collapsible": "1.40.0", - "@zag-js/collection": "1.40.0", - "@zag-js/color-picker": "1.40.0", - "@zag-js/color-utils": "1.40.0", - "@zag-js/combobox": "1.40.0", - "@zag-js/core": "1.40.0", - "@zag-js/date-input": "1.40.0", - "@zag-js/date-picker": "1.40.0", - "@zag-js/date-utils": "1.40.0", - "@zag-js/dialog": "1.40.0", - "@zag-js/dom-query": "1.40.0", - "@zag-js/drawer": "1.40.0", - "@zag-js/editable": "1.40.0", - "@zag-js/file-upload": "1.40.0", - "@zag-js/file-utils": "1.40.0", - "@zag-js/focus-visible": "1.40.0", - "@zag-js/floating-panel": "1.40.0", - "@zag-js/focus-trap": "1.40.0", - "@zag-js/highlight-word": "1.40.0", - "@zag-js/hover-card": "1.40.0", - "@zag-js/i18n-utils": "1.40.0", - "@zag-js/image-cropper": "1.40.0", - "@zag-js/json-tree-utils": "1.40.0", - "@zag-js/listbox": "1.40.0", - "@zag-js/marquee": "1.40.0", - "@zag-js/menu": "1.40.0", - "@zag-js/navigation-menu": "1.40.0", - "@zag-js/number-input": "1.40.0", - "@zag-js/pagination": "1.40.0", - "@zag-js/password-input": "1.40.0", - "@zag-js/pin-input": "1.40.0", - "@zag-js/popover": "1.40.0", - "@zag-js/presence": "1.40.0", - "@zag-js/progress": "1.40.0", - "@zag-js/qr-code": "1.40.0", - "@zag-js/radio-group": "1.40.0", - "@zag-js/rating-group": "1.40.0", - "@zag-js/scroll-area": "1.40.0", - "@zag-js/select": "1.40.0", - "@zag-js/signature-pad": "1.40.0", - "@zag-js/slider": "1.40.0", - "@zag-js/splitter": "1.40.0", - "@zag-js/steps": "1.40.0", - "@zag-js/switch": "1.40.0", - "@zag-js/tabs": "1.40.0", - "@zag-js/tags-input": "1.40.0", - "@zag-js/timer": "1.40.0", - "@zag-js/toast": "1.40.0", - "@zag-js/toggle": "1.40.0", - "@zag-js/toggle-group": "1.40.0", - "@zag-js/tooltip": "1.40.0", - "@zag-js/tour": "1.40.0", - "@zag-js/tree-view": "1.40.0", - "@zag-js/types": "1.40.0", - "@zag-js/utils": "1.40.0", - "@zag-js/vue": "1.40.0" + "@zag-js/accordion": "1.41.0", + "@zag-js/anatomy": "1.41.0", + "@zag-js/angle-slider": "1.41.0", + "@zag-js/async-list": "1.41.0", + "@zag-js/auto-resize": "1.41.0", + "@zag-js/avatar": "1.41.0", + "@zag-js/carousel": "1.41.0", + "@zag-js/cascade-select": "1.41.0", + "@zag-js/checkbox": "1.41.0", + "@zag-js/clipboard": "1.41.0", + "@zag-js/collapsible": "1.41.0", + "@zag-js/collection": "1.41.0", + "@zag-js/color-picker": "1.41.0", + "@zag-js/color-utils": "1.41.0", + "@zag-js/combobox": "1.41.0", + "@zag-js/core": "1.41.0", + "@zag-js/date-input": "1.41.0", + "@zag-js/date-picker": "1.41.0", + "@zag-js/date-utils": "1.41.0", + "@zag-js/dialog": "1.41.0", + "@zag-js/dom-query": "1.41.0", + "@zag-js/drawer": "1.41.0", + "@zag-js/editable": "1.41.0", + "@zag-js/file-upload": "1.41.0", + "@zag-js/file-utils": "1.41.0", + "@zag-js/focus-visible": "1.41.0", + "@zag-js/floating-panel": "1.41.0", + "@zag-js/focus-trap": "1.41.0", + "@zag-js/highlight-word": "1.41.0", + "@zag-js/hover-card": "1.41.0", + "@zag-js/i18n-utils": "1.41.0", + "@zag-js/image-cropper": "1.41.0", + "@zag-js/json-tree-utils": "1.41.0", + "@zag-js/listbox": "1.41.0", + "@zag-js/marquee": "1.41.0", + "@zag-js/menu": "1.41.0", + "@zag-js/navigation-menu": "1.41.0", + "@zag-js/number-input": "1.41.0", + "@zag-js/pagination": "1.41.0", + "@zag-js/password-input": "1.41.0", + "@zag-js/pin-input": "1.41.0", + "@zag-js/popover": "1.41.0", + "@zag-js/presence": "1.41.0", + "@zag-js/progress": "1.41.0", + "@zag-js/qr-code": "1.41.0", + "@zag-js/radio-group": "1.41.0", + "@zag-js/rating-group": "1.41.0", + "@zag-js/scroll-area": "1.41.0", + "@zag-js/select": "1.41.0", + "@zag-js/signature-pad": "1.41.0", + "@zag-js/slider": "1.41.0", + "@zag-js/splitter": "1.41.0", + "@zag-js/steps": "1.41.0", + "@zag-js/switch": "1.41.0", + "@zag-js/tabs": "1.41.0", + "@zag-js/tags-input": "1.41.0", + "@zag-js/timer": "1.41.0", + "@zag-js/toast": "1.41.0", + "@zag-js/toggle": "1.41.0", + "@zag-js/toggle-group": "1.41.0", + "@zag-js/tooltip": "1.41.0", + "@zag-js/tour": "1.41.0", + "@zag-js/tree-view": "1.41.0", + "@zag-js/types": "1.41.0", + "@zag-js/utils": "1.41.0", + "@zag-js/vue": "1.41.0" }, "devDependencies": { "@biomejs/biome": "2.4.9", diff --git a/packages/vue/src/components/date-input/date-input-root.vue b/packages/vue/src/components/date-input/date-input-root.vue index ca70d39735..ef2e461cbe 100644 --- a/packages/vue/src/components/date-input/date-input-root.vue +++ b/packages/vue/src/components/date-input/date-input-root.vue @@ -23,6 +23,7 @@ import { DateInputProvider } from './use-date-input-context' const props = withDefaults(defineProps(), { disabled: undefined, + hideTimeZone: undefined, invalid: undefined, readOnly: undefined, required: undefined, diff --git a/packages/vue/src/components/date-input/date-input.stories.ts b/packages/vue/src/components/date-input/date-input.stories.ts index 135e8686e6..bf99225865 100644 --- a/packages/vue/src/components/date-input/date-input.stories.ts +++ b/packages/vue/src/components/date-input/date-input.stories.ts @@ -13,6 +13,7 @@ import RangeExample from './examples/range.vue' import ReadOnlyExample from './examples/read-only.vue' import RootProviderExample from './examples/root-provider.vue' import RTLExample from './examples/rtl.vue' +import TimeZoneExample from './examples/time-zone.vue' import WithClearButtonExample from './examples/with-clear-button.vue' import WithDatePickerExample from './examples/with-date-picker.vue' @@ -113,6 +114,13 @@ export const RTL = { }), } +export const TimeZone = { + render: () => ({ + components: { Component: TimeZoneExample }, + template: '', + }), +} + export const WithClearButton = { render: () => ({ components: { Component: WithClearButtonExample }, diff --git a/packages/vue/src/components/date-input/date-input.types.ts b/packages/vue/src/components/date-input/date-input.types.ts index e6ca8c3951..86aa83db82 100644 --- a/packages/vue/src/components/date-input/date-input.types.ts +++ b/packages/vue/src/components/date-input/date-input.types.ts @@ -14,6 +14,11 @@ export interface RootProps { * The granularity of the date input. */ granularity?: dateInput.Props['granularity'] + /** + * Whether to hide the time zone segment when the value is a `ZonedDateTime`. + * @default false + */ + hideTimeZone?: boolean /** * The hour cycle used for formatting time segments. */ diff --git a/packages/vue/src/components/date-input/examples/time-zone.vue b/packages/vue/src/components/date-input/examples/time-zone.vue new file mode 100644 index 0000000000..82b0c41238 --- /dev/null +++ b/packages/vue/src/components/date-input/examples/time-zone.vue @@ -0,0 +1,33 @@ + + + diff --git a/packages/vue/src/components/date-input/tests/date-input.test.ts b/packages/vue/src/components/date-input/tests/date-input.test.ts index 77b6d2e1b5..eafd9293c3 100644 --- a/packages/vue/src/components/date-input/tests/date-input.test.ts +++ b/packages/vue/src/components/date-input/tests/date-input.test.ts @@ -1,4 +1,4 @@ -import { parseDate } from '@internationalized/date' +import { parseDate, parseZonedDateTime } from '@internationalized/date' import { render, screen } from '@testing-library/vue' import user from '@testing-library/user-event' import { axe } from 'vitest-axe' @@ -60,6 +60,27 @@ describe('Date Input', () => { expect(hiddenInput).toHaveValue('6/15/2024') }) + it('should render timeZoneName segment for ZonedDateTime values', () => { + render(ComponentUnderTest, { + props: { + defaultValue: [parseZonedDateTime('2025-02-03T08:45:00[America/Los_Angeles]')], + granularity: 'minute', + }, + }) + expect(document.querySelector('[data-type="timeZoneName"]')).toBeInTheDocument() + }) + + it('should hide timeZoneName segment when hideTimeZone is true', () => { + render(ComponentUnderTest, { + props: { + defaultValue: [parseZonedDateTime('2025-02-03T08:45:00[America/Los_Angeles]')], + granularity: 'minute', + hideTimeZone: true, + }, + }) + expect(document.querySelector('[data-type="timeZoneName"]')).not.toBeInTheDocument() + }) + it('should sync hidden input values for range selection by index', () => { render(RangeComponentUnderTest, { props: { diff --git a/website/package.json b/website/package.json index 5624047cad..749d1553e9 100644 --- a/website/package.json +++ b/website/package.json @@ -31,9 +31,9 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@uidotdev/usehooks": "2.4.1", - "@zag-js/anatomy-icons": "1.40.0", - "@zag-js/date-input": "1.40.0", - "@zag-js/docs": "1.40.0", + "@zag-js/anatomy-icons": "1.41.0", + "@zag-js/date-input": "1.41.0", + "@zag-js/docs": "1.41.0", "better-auth": "1.4.18", "effect": "3.19.19", "lucide-react": "1.8.0",