Skip to content
Closed
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
1 change: 1 addition & 0 deletions packages/dataviews/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

### Bug Fix

- DataForm: Store the day that was clicked in the `datetime` control's calendar instead of the previous day when the site timezone is behind the browser's. [#81637](https://github.com/WordPress/gutenberg/pull/81637)
- Validated form controls: Align invalid focus styling for InputBase-based controls with the design system ([#80417](https://github.com/WordPress/gutenberg/pull/80417)).
- DataViews: Prevent the filter operator select focus ring from being clipped. [#80417](https://github.com/WordPress/gutenberg/pull/80417)
- DataForm: Send a single update per calendar interaction in the `datetime` control instead of two identical ones. Selecting or clearing a date now reveals the validation message by firing a synthetic `invalid` event on the input, rather than briefly moving focus into it and re-sending the value, and announces it to screen readers since focus stays on the calendar. [#81440](https://github.com/WordPress/gutenberg/pull/81440)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { format } from 'date-fns';
import {
BaseControl,
privateApis as componentsPrivateApis,
Expand Down Expand Up @@ -66,15 +67,19 @@ function CalendarDateTimeControl< Item >( {
const onSelectDate = useCallback(
( newDate: Date | null ) => {
if ( newDate ) {
// Extract the date part in WP timezone from the calendar selection
const wpDate = dateI18n( 'Y-m-d', newDate );

// Preserve time if it exists in current value, otherwise use current time
// Read the selected day through the date's own fields, like the
// calendar renders it. Converting the instant with `dateI18n`
// instead would shift the day across midnight whenever the site
// timezone is behind the calendar's.
const wpDate = format( newDate, 'yyyy-MM-dd' );

// Preserve time if it exists in current value, otherwise use
// the start of the selected day.
let wpTime: string;
if ( value ) {
wpTime = dateI18n( 'H:i', getDate( value ) );
} else {
wpTime = dateI18n( 'H:i', newDate );
wpTime = format( newDate, 'HH:mm' );
}

// Combine date and time in WP timezone and convert to ISO
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { dateI18n, getDate, getSettings, setSettings } from '@wordpress/date';
import normalizeFields from '../../../field-types';
import DateTime from '../datetime';

jest.mock( '@wordpress/a11y', () => ( { speak: jest.fn() } ) );

type TestItem = {
published?: string;
};

const field = normalizeFields< TestItem >( [
{
id: 'published',
label: 'Published',
type: 'datetime',
},
] )[ 0 ];

const fullDateFormatter = new Intl.DateTimeFormat( 'en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
} );

const getDayButton = ( date: Date ) =>
screen.getByRole( 'button', {
name: new RegExp( fullDateFormatter.format( date ) ),
} );

describe( 'DateTime control', () => {
describe( 'with a site timezone different from the local timezone', () => {
const originalSettings = getSettings();

beforeAll( () => {
// An offset this far behind guarantees the site's calendar day
// differs from the local one at local midnight, wherever the
// test runs.
setSettings( {
...originalSettings,
timezone: {
...originalSettings.timezone,
offset: -12,
string: '',
},
} );
} );

afterAll( () => {
setSettings( originalSettings );
} );

it( 'should store the day that was clicked in the calendar', async () => {
const user = userEvent.setup();
const onChange = jest.fn();
render(
<DateTime
data={ { published: '2024-03-15T10:30:00.000Z' } }
field={ field }
onChange={ onChange }
/>
);

await user.click( getDayButton( new Date( 2024, 2, 20 ) ) );

expect( onChange ).toHaveBeenCalledTimes( 1 );
const edits = onChange.mock.calls[ 0 ][ 0 ];
// The stored value keeps the clicked calendar day and the time of
// the previous value, both in the site timezone.
expect( dateI18n( 'Y-m-d H:i', getDate( edits.published ) ) ).toBe(
'2024-03-20 22:30'
);
} );
} );
} );
Loading