Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pickers] Introduce a new concept of manager #15395

Open
6 tasks
flaviendelangle opened this issue Nov 13, 2024 · 0 comments
Open
6 tasks

[pickers] Introduce a new concept of manager #15395

flaviendelangle opened this issue Nov 13, 2024 · 0 comments
Assignees
Labels
component: pickers This is the name of the generic UI component, not the React module! new feature New feature or request

Comments

@flaviendelangle
Copy link
Member

flaviendelangle commented Nov 13, 2024

Extracted from #14496 and #14718

Goals

The goal is to decouple our logic and the UI by gathering all the logic specific to a given value type inside a single object.

The end goal would be to be able to create a single unstyled field component instead of six and a single unstyled picker instead of six. This field and picker component would receive the manager as a prop.

This new object is part of the public API because people would have to pass it to the field and pickers built with the Base UI DX.

DX

Initialization

The Date and Time Pickers package would expose one manager per family of component (useDateManager, useTimeManager, useDateTimeManager, useDateRangeManager, useTimeRangeManager, useDateTimeRangeManager).

The non-range managers hooks only take enableAccessibleFieldDOMStructure as a parameter (it's only needed for typing reasons).
The range managers hooks take enableAccessibleFieldDOMStructure and dateSeparator as a parameter (just like we have dateSeparator today in useRangeFieldValueManager).

const manager = useDateManager(); // Will use accessible DOM structure by default
const manager = useDateManager({  enableAccessibleFieldDOMStructure: false });

const manager = useDateRangeManager({ dateSeparator: ' to ' });

Usage in our codebase

Interact with the value

The current valueManager and fieldValueManager are stored in manager.internal_valueManager and manager.internal_fieldValueManager. I would like to keep those APIs private and I think the prefix makes that clear enough.

We can then access its properties as follow:

const emptyValue = manager.internal_valueManager.emptyValue;
const isEqual = manager.internal_valueManager.areValuesEqual(utils, valueLeft, valueRight);
const value = manager.internal_fieldValueManager.parseValueStr(valueStr, referenceValue, parseDate);

I plan to merge both in a single object at some point because they would now both be owned by the same parent object. But it can be done in a standalone PR.

Validate the value

We can still import the validator directly, but in our codebase we should probably always use it through its manager:

const { hasValidationError } = useValidator({
  validator: manager.validator,
  value,
  timezone,
  props,
});

In the future we could imagine passing the manager to useValidator instead but for now passing the validator directly works very well.

Apply the defaults to the field internal props

This logic is currently handed by each field hook (useDateField, useTimeField, useSingleInputDateRangeField, etc...). With the adapter, we can remove this logic and instead move it inside useField which would now receive the internal props without the defaults applied.

const localizationContext = useLocalizationContext();
const internalPropsWithDefaults = manager.internal_applyDefaultsToFieldInternalProps({
  ...localizationContext,
  internalProps,
})

Apply the defaults to the picker internal props

TODO (need to have a clearer vision of the DX for the unstyled pickers and we probably have to introduce a notion of internalProps / forwardedProps like on the field before).

Get the aria-text for the opening trigger

Right now this logic is defined in the picker component (DesktopDatePicker and MobileDatePicker for instance), and by the field after #15671.

const ariaLabel = manager.getOpenDialogAriaLabel(value);

This would allow field with custom UX to easily have a valid aria label.

Impact on our codebase

Field

useField will be replaced by a new PickerField component in the future (which will take advantage of the manager). So migrating useField to the new manager will smoother the transition to PickerField

// Today
const fieldResponse = useField({
    forwardedProps,
    internalProps,
    valueManager: singleItemValueManager,
    fieldValueManager: singleItemFieldValueManager,
    validator: validateDate,
    valueType: 'date',
});

// With manager
const manager = useDateManager(props);
const fieldResponse = useField({ props, manager });

Picker

useDesktopPicker (and equivalents) will be replaced by the upcoming Picker component (or other DX, this one is very unclear for now) in the future (which will take advantage of the manager). So migrating useDesktopPicker (and equivalent) to the new manager will smoother the transition to Picker

// Today
const { renderPicker } = useDesktopPicker({
  props,
  valueManager: singleItemValueManager,
  valueType: 'date',
  getOpenDialogAriaText: buildGetOpenDialogAriaText({
    utils,
    formatKey: 'fullDate',
    contextTranslation: translations.openDatePickerDialogue,
    propsTranslation: props.localeText?.openDatePickerDialogue,
  }),
  validator: validateDate,
});

// With manager
const manager = useDateManager(props);
const { renderPicker } = useDesktopPicker({ props, manager });

Impact on the typing

Point 4 of #14823

This one needs more exploration to confirm that it improves the readability of our codebase AND the readability of our public APIs.

The idea is, on certain interfaces, to replace all the generics that can be inferred from the manager.
The main advantage is too reduce the amount of generics and not have to add a new one everytime we need a new info inside those interfaces.

For example:

// Today
interface UseFieldParameters<
  TValue extends PickerValidValue,
  TEnableAccessibleFieldDOMStructure extends boolean,
  TForwardedProps extends UseFieldCommonForwardedProps &
    UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure>,
  TInternalProps extends UseFieldInternalProps<TIsRange, TEnableAccessibleFieldDOMStructure, any>,
> {}

// With TManager
export interface UseFieldParams<
  TManager extends PickerAnyManagerV8,
  TForwardedProps extends UseFieldForwardedProps<ManagerAccessibleFieldDOMStructure<TManager>>,
> {}

Usage by the community

This part needs to be clarified.
You can have a look at #14496 (comment) and #14718 for more details about the new DX.

import { useDateManager } from '@mui/x-date-pickers/managers';

// Custom field
function MyDateField(props) {
  const manager = useDateManager();

  return (
    <PickersField.Root manager={manager} {...props}>
      <PickersField.Content>
        {(section) => (
          <PickersField.Section section={section}>
            <PickersField.SectionSeparator position="before" />
            <PickersField.SectionContent />
            <PickersField.SectionSeparator position="after" />
          </PickersField.Section>
        )}
      </PickersField.Content>
    </PickersField.Root>
  )
}

// Custom picker
function MyDatePicker(props) {
  const manager = useDateManager();

  return (
    <Picker.Root manager={manager} {...props}>
      <DateField />
      <PickersPopper>
        <Picker.ViewLayout>
          <DatePickerToolbar />
          <DateCalendar />
        <Picker.ViewLayout>
      </PickersPopper>
    </Picker.Root>
  )
}

Migrate to the manager

This list will probably evolve but gives an idea of the work:

  • Create the managers with full support for the field => [pickers] Introduce a new concept of manager #15339
  • Migrate the useDateField &co hooks to use the managers => [pickers] Introduce a new concept of manager #15339
  • Migrate useField to use the managers
  • Migrate useField + useDesktopPicker &co hooks to use the managers for valueManager / valueType and validator (not for applying the default props and other behaviors yet)
  • Add support for getOpenDialogAriaText in the managers and use it in useDesktopPicker &cp
  • Add support for default picker props application in the managers and use it in all the pickers
@github-actions github-actions bot added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Nov 13, 2024
@flaviendelangle flaviendelangle added new feature New feature or request component: pickers This is the name of the generic UI component, not the React module! and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Nov 13, 2024
@flaviendelangle flaviendelangle self-assigned this Nov 13, 2024
@flaviendelangle flaviendelangle changed the title [pickers] Introduce a new concept of valueManager [pickers] Introduce a new concept of manager Nov 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: pickers This is the name of the generic UI component, not the React module! new feature New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant