diff --git a/src/customizations/volto/components/manage/Widgets/DatetimeWidget.jsx b/src/customizations/volto/components/manage/Widgets/DatetimeWidget.jsx index fbb15bdbf..ca94bc2f1 100644 --- a/src/customizations/volto/components/manage/Widgets/DatetimeWidget.jsx +++ b/src/customizations/volto/components/manage/Widgets/DatetimeWidget.jsx @@ -2,26 +2,27 @@ * DatetimeWidget component. * @module components/manage/Widgets/DatetimeWidget * - * https://github.com/plone/volto/blob/16.x.x/src/components/manage/Widgets/DatetimeWidget.jsx - * https://github.com/plone/volto/blob/7ec45f7b4d46233236c651f39a951bad8e389184/src/components/manage/Widgets/DatetimeWidget.jsx + * https://github.com/plone/volto/blob/main/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx * * CUSTOMIZATIONS: * - accept calendar `openDirection` prop and use it in SingleDatePicker, * default to down if no openDirection is given + * - requirement aria attributes added to date and time inputs - To be backported https://github.com/plone/volto/pull/7494 */ + /** * DatetimeWidget component. * @module components/manage/Widgets/DatetimeWidget */ -import React, { Component } from 'react'; -import { compose } from 'redux'; + +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; +import { defineMessages, useIntl } from 'react-intl'; import loadable from '@loadable/component'; import cx from 'classnames'; -import { Icon, FormFieldWrapper } from '@plone/volto/components'; -import { parseDateTime, toBackendLang } from '@plone/volto/helpers'; +import Icon from '@plone/volto/components/theme/Icon/Icon'; +import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper'; +import { parseDateTime, toBackendLang } from '@plone/volto/helpers/Utils/Utils'; import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; import leftKey from '@plone/volto/icons/left-key.svg'; @@ -43,6 +44,10 @@ const messages = defineMessages({ id: 'Time', defaultMessage: 'Time', }, + clearDateTime: { + id: 'Clear date/time', + defaultMessage: 'Clear date and time', + }, }); const PrevIcon = () => ( @@ -60,6 +65,7 @@ const PrevIcon = () => ( ); + const NextIcon = () => (
{ + const { + id, + resettable, + reactDates, + widgetOptions, + moment, + value, + onChange, + dateOnly, + widget, + noPastDates: propNoPastDates, + isDisabled, + formData, + openDirection, // ✅ adicionada + } = props; + + const intl = useIntl(); + const lang = intl.locale; + + const [focused, setFocused] = useState(false); + const [isDefault, setIsDefault] = useState(false); + + const { SingleDatePicker } = reactDates; + + useEffect(() => { + const parsedDateTime = parseDateTime( + toBackendLang(lang), + value, undefined, - this.moment, + moment.default, + ); + setIsDefault( + parsedDateTime?.toISOString() === moment.default().utc().toISOString(), + ); + }, [value, lang, moment]); + + const getInternalValue = () => { + return parseDateTime(toBackendLang(lang), value, undefined, moment.default); + }; + + const getDateOnly = () => { + return ( + dateOnly || + widget === 'date' || + ((id === 'start' || id === 'end') && formData?.whole_day) ); - } - - getDateOnly() { - return this.props.dateOnly || this.props.widget === 'date'; - } - - /** - * Update date storage - * @method onDateChange - * @param {Object} date updated momentjs Object for date - * @returns {undefined} - */ - onDateChange = (date) => { + }; + + const onDateChange = (date) => { if (date) { - const moment = this.props.moment.default; - const isDateOnly = this.getDateOnly(); - const base = (this.getInternalValue() || moment()).set({ + const isDateOnly = getDateOnly(); + const base = (getInternalValue() || moment.default()).set({ year: date.year(), month: date.month(), date: date.date(), @@ -152,96 +149,135 @@ export class DatetimeWidgetComponent extends Component { const dateValue = isDateOnly ? base.format('YYYY-MM-DD') : base.toISOString(); - this.props.onChange(this.props.id, dateValue); + onChange(id, dateValue); } - this.setState({ isDefault: false }); + setIsDefault(false); }; - /** - * Update date storage - * @method onTimeChange - * @param {Object} time updated momentjs Object for time - * @returns {undefined} - */ - onTimeChange = (time) => { - const moment = this.props.moment.default; + const onTimeChange = (time) => { if (time) { - const base = (this.getInternalValue() || moment()).set({ + const base = (getInternalValue() || moment.default()).set({ hours: time?.hours() ?? 0, minutes: time?.minutes() ?? 0, seconds: 0, }); const dateValue = base.toISOString(); - this.props.onChange(this.props.id, dateValue); + onChange(id, dateValue); } }; - onResetDates = () => { - this.setState({ isDefault: false }); - this.props.onChange(this.props.id, null); + const onResetDates = () => { + setIsDefault(false); + onChange(id, null); }; - /** - * Handle SingleDatePicker focus - * @method onFocusChange - * @param {boolean} focused component focus state. - * @returns {undefined} - */ - onFocusChange = ({ focused }) => this.setState({ focused }); - - render() { - const { id, resettable, intl, reactDates, widgetOptions, lang } = - this.props; - const noPastDates = - this.props.noPastDates || widgetOptions?.pattern_options?.noPastDates; - const moment = this.props.moment.default; - const datetime = this.getInternalValue(); - const dateOnly = this.getDateOnly(); - const { SingleDatePicker } = reactDates; + const onFocusChange = ({ focused }) => setFocused(focused); - return ( - + const noPastDates = + propNoPastDates || widgetOptions?.pattern_options?.noPastDates; + const datetime = getInternalValue(); + const isDateOnly = getDateOnly(); + + const renderWidget = !(id === 'end' && formData?.open_end); + + //pezzo aggiunto per gestire aria-required - https://github.com/plone/volto/pull/7494 + useEffect(() => { + const dateSelectors = [ + `#${id}-date`, + `#${id}-date .DateInput_input`, + `#${id}-date input`, + `#${id}`, + `.DateInput_input#${id}`, + ]; + + const timeSelectors = [ + `#${id}-time input`, + `#${id}-time .rc-time-picker-input`, + `.rc-time-picker-input#${id}-time`, + `.time-input #${id}-time`, + `.time-input .rc-time-picker-input`, + ]; + + function findInput(selectors) { + for (let selector of selectors) { + const item = document.querySelector(selector); + if (item && item.tagName === 'INPUT') return item; + if (item && item.querySelector) { + const inner = item.querySelector('input'); + if (inner) return inner; + } + } + return null; + } + + function applyAria() { + const dateInput = findInput(dateSelectors); + if (!dateInput) return; + + if (props.required) dateInput.setAttribute('aria-required', 'true'); + else dateInput.removeAttribute('aria-required'); + + if (props.required && !isDateOnly) { + const timeInput = findInput(timeSelectors); + if (timeInput) timeInput.setAttribute('aria-required', 'true'); + } + } + + applyAria(); + + const observer = new MutationObserver(applyAria); + observer.observe(document.body, { childList: true, subtree: true }); + + // Cleanup + return () => observer.disconnect(); + }, [props.required, id, isDateOnly]); + + // fine pezzo aggiunto per gestire aria-required + + return ( + + {renderWidget && (
false })} - onFocusChange={this.onFocusChange} + onFocusChange={onFocusChange} noBorder - displayFormat={moment + displayFormat={moment.default .localeData(toBackendLang(lang)) .longDateFormat('L')} navPrev={} navNext={} id={`${id}-date`} placeholder={intl.formatMessage(messages.date)} - openDirection={this.props.openDirection ?? 'down'} + openDirection={openDirection ?? 'down'} />
- {!dateOnly && ( + {!isDateOnly && (
this.onResetDates()} + type="button" + disabled={isDisabled || !datetime} + onClick={onResetDates} className="item ui noborder button" + aria-label={intl.formatMessage(messages.clearDateTime)} > )}
- - ); - } -} + )} + + ); +}; -/** - * Property types. - * @property {Object} propTypes Property types. - * @static - */ DatetimeWidgetComponent.propTypes = { id: PropTypes.string.isRequired, title: PropTypes.string.isRequired, @@ -285,11 +318,6 @@ DatetimeWidgetComponent.propTypes = { openDirection: PropTypes.oneOf(['up', 'down']), }; -/** - * Default properties. - * @property {Object} defaultProps Default properties. - * @static - */ DatetimeWidgetComponent.defaultProps = { description: null, required: false, @@ -301,10 +329,6 @@ DatetimeWidgetComponent.defaultProps = { openDirection: 'down', }; -export default compose( - injectLazyLibs(['reactDates', 'moment']), - connect((state) => ({ - lang: state.intl.locale, - })), - injectIntl, -)(DatetimeWidgetComponent); +export default injectLazyLibs(['reactDates', 'moment'])( + DatetimeWidgetComponent, +);