@react-md/core 6.2.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/dist/_base.scss +1 -0
  2. package/dist/datetime/NativeDateField.d.ts +24 -0
  3. package/dist/datetime/NativeDateField.js +63 -0
  4. package/dist/datetime/NativeDateField.js.map +1 -0
  5. package/dist/datetime/NativeTimeField.d.ts +26 -0
  6. package/dist/datetime/NativeTimeField.js +63 -0
  7. package/dist/datetime/NativeTimeField.js.map +1 -0
  8. package/dist/datetime/useDateField.d.ts +120 -0
  9. package/dist/datetime/useDateField.js +35 -0
  10. package/dist/datetime/useDateField.js.map +1 -0
  11. package/dist/datetime/useTimeField.d.ts +124 -0
  12. package/dist/datetime/useTimeField.js +65 -0
  13. package/dist/datetime/useTimeField.js.map +1 -0
  14. package/dist/datetime/utils.d.ts +34 -0
  15. package/dist/datetime/utils.js +27 -0
  16. package/dist/datetime/utils.js.map +1 -0
  17. package/dist/draggable/utils.d.ts +3 -6
  18. package/dist/draggable/utils.js.map +1 -1
  19. package/dist/expansion-panel/ExpansionList.js +1 -1
  20. package/dist/expansion-panel/ExpansionList.js.map +1 -1
  21. package/dist/expansion-panel/useExpansionList.d.ts +2 -7
  22. package/dist/expansion-panel/useExpansionList.js.map +1 -1
  23. package/dist/form/FormMessage.js +3 -1
  24. package/dist/form/FormMessage.js.map +1 -1
  25. package/dist/form/FormMessageContainer.d.ts +2 -1
  26. package/dist/form/FormMessageContainer.js +3 -2
  27. package/dist/form/FormMessageContainer.js.map +1 -1
  28. package/dist/form/FormMessageCounter.d.ts +3 -2
  29. package/dist/form/FormMessageCounter.js +5 -2
  30. package/dist/form/FormMessageCounter.js.map +1 -1
  31. package/dist/form/Listbox.d.ts +3 -10
  32. package/dist/form/Listbox.js +8 -27
  33. package/dist/form/Listbox.js.map +1 -1
  34. package/dist/form/ListboxProvider.d.ts +17 -0
  35. package/dist/form/ListboxProvider.js +33 -1
  36. package/dist/form/ListboxProvider.js.map +1 -1
  37. package/dist/form/NativeSelect.js +1 -0
  38. package/dist/form/NativeSelect.js.map +1 -1
  39. package/dist/form/TextArea.js +1 -0
  40. package/dist/form/TextArea.js.map +1 -1
  41. package/dist/form/TextField.js +1 -0
  42. package/dist/form/TextField.js.map +1 -1
  43. package/dist/form/_form-message.scss +13 -0
  44. package/dist/form/_select.scss +1 -1
  45. package/dist/form/_slider.scss +1 -1
  46. package/dist/form/_text-field.scss +12 -3
  47. package/dist/form/formMessageContainerStyles.d.ts +7 -0
  48. package/dist/form/formMessageContainerStyles.js +4 -2
  49. package/dist/form/formMessageContainerStyles.js.map +1 -1
  50. package/dist/form/sliderUtils.d.ts +3 -7
  51. package/dist/form/sliderUtils.js.map +1 -1
  52. package/dist/form/types.d.ts +13 -0
  53. package/dist/form/types.js.map +1 -1
  54. package/dist/form/useCombobox.d.ts +6 -2
  55. package/dist/form/useCombobox.js +8 -9
  56. package/dist/form/useCombobox.js.map +1 -1
  57. package/dist/form/useFormReset.d.ts +4 -1
  58. package/dist/form/useFormReset.js +9 -4
  59. package/dist/form/useFormReset.js.map +1 -1
  60. package/dist/form/useNumberField.d.ts +5 -5
  61. package/dist/form/useNumberField.js +10 -2
  62. package/dist/form/useNumberField.js.map +1 -1
  63. package/dist/form/useSelectCombobox.js +2 -2
  64. package/dist/form/useSelectCombobox.js.map +1 -1
  65. package/dist/form/useTextField.d.ts +76 -59
  66. package/dist/form/useTextField.js +7 -1
  67. package/dist/form/useTextField.js.map +1 -1
  68. package/dist/interaction/utils.d.ts +14 -0
  69. package/dist/interaction/utils.js +23 -12
  70. package/dist/interaction/utils.js.map +1 -1
  71. package/dist/menu/MenuBar.js +1 -1
  72. package/dist/menu/MenuBar.js.map +1 -1
  73. package/dist/menu/MenuItemTextField.d.ts +1 -2
  74. package/dist/menu/MenuItemTextField.js.map +1 -1
  75. package/dist/menu/MenuWidget.js +3 -2
  76. package/dist/menu/MenuWidget.js.map +1 -1
  77. package/dist/movement/constants.d.ts +10 -0
  78. package/dist/movement/constants.js +20 -4
  79. package/dist/movement/constants.js.map +1 -1
  80. package/dist/movement/types.d.ts +59 -10
  81. package/dist/movement/types.js.map +1 -1
  82. package/dist/movement/useKeyboardMovementProvider.d.ts +5 -1
  83. package/dist/movement/useKeyboardMovementProvider.js +171 -73
  84. package/dist/movement/useKeyboardMovementProvider.js.map +1 -1
  85. package/dist/tabs/useTabList.js +1 -1
  86. package/dist/tabs/useTabList.js.map +1 -1
  87. package/dist/test-utils/drag.d.ts +6 -9
  88. package/dist/transition/useCarousel.d.ts +2 -2
  89. package/dist/transition/useCarousel.js.map +1 -1
  90. package/dist/tree/Tree.js +1 -1
  91. package/dist/tree/Tree.js.map +1 -1
  92. package/dist/tree/_tree.scss +1 -1
  93. package/dist/tree/useTreeMovement.d.ts +2 -1
  94. package/dist/tree/useTreeMovement.js +2 -1
  95. package/dist/tree/useTreeMovement.js.map +1 -1
  96. package/dist/types.d.ts +14 -0
  97. package/dist/types.js.map +1 -1
  98. package/dist/utils/getMiddleOfRange.d.ts +2 -3
  99. package/dist/utils/getMiddleOfRange.js.map +1 -1
  100. package/dist/utils/getPercentage.d.ts +2 -9
  101. package/dist/utils/getPercentage.js +1 -1
  102. package/dist/utils/getPercentage.js.map +1 -1
  103. package/dist/utils/getRangeSteps.d.ts +2 -3
  104. package/dist/utils/getRangeSteps.js +0 -3
  105. package/dist/utils/getRangeSteps.js.map +1 -1
  106. package/dist/utils/nearest.d.ts +2 -3
  107. package/dist/utils/nearest.js +0 -3
  108. package/dist/utils/nearest.js.map +1 -1
  109. package/dist/utils/trigonometry.d.ts +31 -0
  110. package/dist/utils/trigonometry.js +25 -0
  111. package/dist/utils/trigonometry.js.map +1 -0
  112. package/dist/window-splitter/_window-splitter.scss +1 -1
  113. package/dist/window-splitter/useWindowSplitter.d.ts +1 -1
  114. package/dist/window-splitter/useWindowSplitter.js.map +1 -1
  115. package/package.json +1 -1
  116. package/src/datetime/NativeDateField.tsx +92 -0
  117. package/src/datetime/NativeTimeField.tsx +94 -0
  118. package/src/datetime/useDateField.ts +193 -0
  119. package/src/datetime/useTimeField.ts +233 -0
  120. package/src/datetime/utils.ts +48 -0
  121. package/src/draggable/utils.ts +3 -6
  122. package/src/expansion-panel/ExpansionList.tsx +2 -1
  123. package/src/expansion-panel/useExpansionList.ts +6 -12
  124. package/src/form/FormMessage.tsx +4 -0
  125. package/src/form/FormMessageContainer.tsx +8 -4
  126. package/src/form/FormMessageCounter.tsx +17 -6
  127. package/src/form/Listbox.tsx +18 -46
  128. package/src/form/ListboxProvider.ts +61 -1
  129. package/src/form/NativeSelect.tsx +1 -0
  130. package/src/form/TextArea.tsx +1 -0
  131. package/src/form/TextField.tsx +1 -0
  132. package/src/form/formMessageContainerStyles.ts +10 -2
  133. package/src/form/sliderUtils.ts +3 -7
  134. package/src/form/types.ts +15 -0
  135. package/src/form/useCombobox.ts +15 -10
  136. package/src/form/useFormReset.ts +12 -5
  137. package/src/form/useNumberField.ts +17 -14
  138. package/src/form/useSelectCombobox.ts +2 -2
  139. package/src/form/useTextField.ts +102 -69
  140. package/src/interaction/utils.ts +18 -20
  141. package/src/menu/MenuBar.tsx +1 -1
  142. package/src/menu/MenuItemTextField.tsx +1 -3
  143. package/src/menu/MenuWidget.tsx +4 -2
  144. package/src/movement/constants.ts +26 -4
  145. package/src/movement/types.ts +84 -19
  146. package/src/movement/useKeyboardMovementProvider.ts +209 -95
  147. package/src/tabs/useTabList.ts +1 -1
  148. package/src/test-utils/drag.ts +8 -12
  149. package/src/transition/useCarousel.ts +2 -2
  150. package/src/tree/Tree.tsx +1 -1
  151. package/src/tree/useTreeMovement.ts +4 -0
  152. package/src/types.ts +16 -0
  153. package/src/utils/getMiddleOfRange.ts +2 -3
  154. package/src/utils/getPercentage.ts +3 -11
  155. package/src/utils/getRangeSteps.ts +3 -3
  156. package/src/utils/nearest.ts +3 -3
  157. package/src/utils/trigonometry.ts +46 -0
  158. package/src/window-splitter/useWindowSplitter.ts +3 -2
@@ -0,0 +1,92 @@
1
+ "use client";
2
+
3
+ import { forwardRef } from "react";
4
+
5
+ import { TextField, type TextFieldProps } from "../form/TextField.js";
6
+ import { type DateFieldOptions, useDateField } from "./useDateField.js";
7
+
8
+ /** @since 6.3.0 */
9
+ export interface NativeDateFieldProps
10
+ extends Omit<TextFieldProps, keyof DateFieldOptions | "value">,
11
+ Omit<DateFieldOptions, "ref"> {}
12
+
13
+ /**
14
+ * The `NativeDateField` is a simple wrapper around the `TextField` using the
15
+ * `useDateField` hook.
16
+ *
17
+ * @example Simple Example
18
+ * ```tsx
19
+ * import { NativeDateField } from "@react-md/core/datetime/NativeDateField";
20
+ * import { type ReactElement } from "react";
21
+ *
22
+ * function Example(): ReactElement {
23
+ * return <NativeDateField label="Delivery Date" name="delivery" />;
24
+ * }
25
+ * ```
26
+ *
27
+ * @see {@link https://react-md.dev/components/native-time-field | NativeDateField Demos}
28
+ * @see {@link https://react-md.dev/components/text-field | TextField Demos}
29
+ * @since 6.3.0
30
+ */
31
+ export const NativeDateField = forwardRef<
32
+ HTMLInputElement,
33
+ NativeDateFieldProps
34
+ >(function NativeDateField(props, ref) {
35
+ const {
36
+ id,
37
+ min,
38
+ max,
39
+ step,
40
+ onBlur,
41
+ onInvalid,
42
+ onChange,
43
+ helpText,
44
+ required,
45
+ validationType,
46
+ disableMessage,
47
+ errorIcon,
48
+ isErrored,
49
+ getErrorIcon,
50
+ getErrorMessage,
51
+ onErrorChange,
52
+ disableReset,
53
+ defaultValue,
54
+ ...remaining
55
+ } = props;
56
+ const { name, form } = props;
57
+ const { fieldProps } = useDateField({
58
+ id,
59
+ ref,
60
+ name,
61
+ form,
62
+ min,
63
+ max,
64
+ step,
65
+ onBlur,
66
+ onChange,
67
+ onInvalid,
68
+ helpText,
69
+ required,
70
+ validationType,
71
+ disableMessage: disableMessage ?? (!min && !max && !step && !required),
72
+ errorIcon,
73
+ isErrored,
74
+ getErrorIcon,
75
+ getErrorMessage,
76
+ onErrorChange,
77
+ disableReset,
78
+ defaultValue,
79
+ });
80
+
81
+ let { messageProps } = remaining;
82
+ if (fieldProps.messageProps) {
83
+ messageProps = {
84
+ ...fieldProps.messageProps,
85
+ ...remaining.messageProps,
86
+ };
87
+ }
88
+
89
+ return (
90
+ <TextField {...remaining} {...fieldProps} messageProps={messageProps} />
91
+ );
92
+ });
@@ -0,0 +1,94 @@
1
+ "use client";
2
+
3
+ import { forwardRef } from "react";
4
+
5
+ import { TextField, type TextFieldProps } from "../form/TextField.js";
6
+ import { type TimeFieldOptions, useTimeField } from "./useTimeField.js";
7
+
8
+ /**
9
+ * @since 6.3.0
10
+ */
11
+ export interface NativeTimeFieldProps
12
+ extends Omit<TextFieldProps, keyof TimeFieldOptions | "value">,
13
+ Omit<TimeFieldOptions, "ref"> {}
14
+
15
+ /**
16
+ * The `NativeTimeField` is a simple wrapper around the `TextField` using the
17
+ * `useTimeField` hook.
18
+ *
19
+ * @example Simple Example
20
+ * ```tsx
21
+ * import { NativeTimeField } from "@react-md/core/datetime/NativeTimeField";
22
+ * import { type ReactElement } from "react";
23
+ *
24
+ * function Example(): ReactElement {
25
+ * return <NativeTimeField label="Time" name="appt" />;
26
+ * }
27
+ * ```
28
+ *
29
+ * @see {@link https://react-md.dev/components/native-time-field | NativeTimeField Demos}
30
+ * @see {@link https://react-md.dev/components/text-field | TextField Demos}
31
+ * @since 6.3.0
32
+ */
33
+ export const NativeTimeField = forwardRef<
34
+ HTMLInputElement,
35
+ NativeTimeFieldProps
36
+ >(function NativeTimeField(props, ref) {
37
+ const {
38
+ id,
39
+ min,
40
+ max,
41
+ step,
42
+ onBlur,
43
+ onInvalid,
44
+ onChange,
45
+ helpText,
46
+ required,
47
+ validationType,
48
+ disableMessage,
49
+ errorIcon,
50
+ isErrored,
51
+ getErrorIcon,
52
+ getErrorMessage,
53
+ onErrorChange,
54
+ disableReset,
55
+ defaultValue,
56
+ ...remaining
57
+ } = props;
58
+ const { name, form } = props;
59
+ const { fieldProps } = useTimeField({
60
+ id,
61
+ ref,
62
+ name,
63
+ form,
64
+ min,
65
+ max,
66
+ step,
67
+ onBlur,
68
+ onChange,
69
+ onInvalid,
70
+ helpText,
71
+ required,
72
+ validationType,
73
+ disableMessage: disableMessage ?? (!min && !max && !step && !required),
74
+ errorIcon,
75
+ isErrored,
76
+ getErrorIcon,
77
+ getErrorMessage,
78
+ onErrorChange,
79
+ disableReset,
80
+ defaultValue,
81
+ });
82
+
83
+ let { messageProps } = remaining;
84
+ if (fieldProps.messageProps) {
85
+ messageProps = {
86
+ ...fieldProps.messageProps,
87
+ ...remaining.messageProps,
88
+ };
89
+ }
90
+
91
+ return (
92
+ <TextField {...remaining} {...fieldProps} messageProps={messageProps} />
93
+ );
94
+ });
@@ -0,0 +1,193 @@
1
+ "use client";
2
+
3
+ import { type InputHTMLAttributes, useRef } from "react";
4
+
5
+ import {
6
+ type ProvidedFormMessageProps,
7
+ type ProvidedTextFieldProps,
8
+ type TextFieldHookOptions,
9
+ type TextFieldImplementation,
10
+ type TextFieldWithMessageImplementation,
11
+ useTextField,
12
+ } from "../form/useTextField.js";
13
+
14
+ /**
15
+ * @since 6.3.0
16
+ */
17
+ export interface DateFieldConstraints {
18
+ /**
19
+ * This **must** be in the format `yyyy-mm-dd`
20
+ *
21
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/date#min | min attribute}
22
+ */
23
+ min?: string;
24
+
25
+ /**
26
+ * This **must** be in the format `yyyy-mm-dd`
27
+ *
28
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/date#max | max attribute}
29
+ */
30
+ max?: string;
31
+
32
+ /**
33
+ * For date inputs, the value of step is given in days; and is treated as a
34
+ * number of milliseconds equal to 86,400,000 times the step value (the
35
+ * underlying numeric value is in milliseconds). The default value of step is
36
+ * 1, indicating 1 day.
37
+ *
38
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/date#step | step attribute}
39
+ */
40
+ step?: number | "any";
41
+ }
42
+
43
+ /** @since 6.3.0 */
44
+ export interface DateFieldOptions
45
+ extends Omit<
46
+ TextFieldHookOptions,
47
+ | "isNumber"
48
+ | "counter"
49
+ | "pattern"
50
+ | "maxLength"
51
+ | "minLength"
52
+ | "disableMaxLength"
53
+ >,
54
+ DateFieldConstraints {}
55
+
56
+ /** @since 6.3.0 */
57
+ export interface ProvidedDateFieldProps
58
+ extends Omit<ProvidedTextFieldProps, "value">,
59
+ Omit<DateFieldConstraints, "step"> {
60
+ type: "date";
61
+ step?: number | "any";
62
+ defaultValue: Required<InputHTMLAttributes<HTMLInputElement>>["defaultValue"];
63
+ }
64
+
65
+ /** @since 6.3.0 */
66
+ export interface ProvidedDateFieldMessageProps extends ProvidedDateFieldProps {
67
+ /**
68
+ * These props will be defined as long as the `disableMessage` prop is not
69
+ * `true` from the `useTextField` hook.
70
+ */
71
+ messageProps: ProvidedFormMessageProps;
72
+ }
73
+
74
+ /** @since 6.3.0 */
75
+ export interface DateFieldImplementation
76
+ extends Omit<TextFieldImplementation, "fieldProps"> {
77
+ fieldProps: ProvidedDateFieldProps;
78
+ }
79
+
80
+ /** @since 6.3.0 */
81
+ export interface DateFieldWithMessageImplementation
82
+ extends Omit<TextFieldWithMessageImplementation, "fieldProps"> {
83
+ fieldProps: ProvidedDateFieldMessageProps;
84
+ }
85
+
86
+ /** @since 6.3.0 */
87
+ export interface ValidatedDateFieldImplementation
88
+ extends DateFieldImplementation {
89
+ fieldProps: ProvidedDateFieldProps | ProvidedDateFieldMessageProps;
90
+ }
91
+
92
+ /**
93
+ * The `useDateField` is a small wrapper around the {@link useTextField} to be used
94
+ * with `<input type="date" />`. It is used in the `NativeDateField` if an example
95
+ * implementation would like to be seen.
96
+ *
97
+ * @example Simple Example
98
+ * ```tsx
99
+ * import { useDateField } from "@react-md/core/datetime/useDateField";
100
+ * import { TextField } from "@react-md/core/form/TextField";
101
+ * import { type ReactElement } from "react";
102
+ *
103
+ * function Example(): ReactElement {
104
+ * const { value, fieldProps, error, errorMessage } = useDateField({
105
+ * name: "delivery",
106
+ * required: true,
107
+ * min: "2025-01-01",
108
+ * max: "2026-01-01",
109
+ * disableMessage: true,
110
+ * });
111
+ *
112
+ * // value: `""` or `"yyyy-mm-dd"`
113
+ *
114
+ * return <TextField label="Delivery Date" {...fieldProps} />
115
+ * }
116
+ * ```
117
+ *
118
+ * @since 6.3.0
119
+ * @see {@link https://react-md.dev/components/native-date-field | NativeDateField Demos}
120
+ * @see {@link https://react-md.dev/hooks/use-date-field | useDateField Demos}
121
+ */
122
+ export function useDateField(
123
+ options: DateFieldOptions & { disableMessage: true }
124
+ ): DateFieldImplementation;
125
+
126
+ /**
127
+ * The `useDateField` is a small wrapper around the {@link useTextField} to be used
128
+ * with `<input type="date" />`. It is used in the `NativeDateField` if an example
129
+ * implementation would like to be seen.
130
+ *
131
+ * @example Simple Example
132
+ * ```tsx
133
+ * import { useDateField } from "@react-md/core/datetime/useDateField";
134
+ * import { TextField } from "@react-md/core/form/TextField";
135
+ * import { type ReactElement } from "react";
136
+ *
137
+ * function Example(): ReactElement {
138
+ * const { value, fieldProps } = useDateField({
139
+ * name: "delivery",
140
+ * required: true,
141
+ * min: "2025-01-01",
142
+ * max: "2026-01-01",
143
+ * });
144
+ *
145
+ * // value: `""` or `"yyyy-mm-dd"`
146
+ *
147
+ * return <TextField label="Delivery Date" {...fieldProps} />
148
+ * }
149
+ * ```
150
+ *
151
+ * @since 6.3.0
152
+ * @see {@link https://react-md.dev/components/native-date-field | NativeDateField Demos}
153
+ * @see {@link https://react-md.dev/hooks/use-date-field | useDateField Demos}
154
+ */
155
+ export function useDateField(
156
+ options: DateFieldOptions
157
+ ): DateFieldWithMessageImplementation;
158
+
159
+ /**
160
+ * @since 6.3.0
161
+ * @see {@link https://react-md.dev/components/native-date-field | NativeTimeField Demos}
162
+ * @see {@link https://react-md.dev/hooks/use-date-field | useTimeField Demos}
163
+ */
164
+ export function useDateField(
165
+ options: DateFieldOptions
166
+ ): ValidatedDateFieldImplementation {
167
+ const { min, max, step, ...fieldOptions } = options;
168
+ const { fieldProps, ...impl } = useTextField(fieldOptions);
169
+
170
+ // NOTE: Unlike the other text field components, the `value` should **not**
171
+ // be provided since the time input behaves a bit weirdly with the `onChange`
172
+ // event and it is better to rely on default browser behavior instead of
173
+ // controlling the value. The flow is:
174
+ // - user types `12:30`
175
+ // - `onChange` is fired with `12:30`
176
+ // - user selects `30` and hits backspace
177
+ // - `onChange` is fired with `""`
178
+ // If the `value` is set, the other time values would be lost
179
+ const { value, ...allowedFieldProps } = fieldProps;
180
+ const initial = useRef(value);
181
+
182
+ return {
183
+ ...impl,
184
+ fieldProps: {
185
+ ...allowedFieldProps,
186
+ defaultValue: initial.current,
187
+ min,
188
+ max,
189
+ step,
190
+ type: "date",
191
+ },
192
+ };
193
+ }
@@ -0,0 +1,233 @@
1
+ "use client";
2
+
3
+ import { type InputHTMLAttributes, useRef } from "react";
4
+
5
+ import {
6
+ type ProvidedFormMessageProps,
7
+ type ProvidedTextFieldProps,
8
+ type TextFieldHookOptions,
9
+ type TextFieldImplementation,
10
+ type TextFieldWithMessageImplementation,
11
+ useTextField,
12
+ } from "../form/useTextField.js";
13
+ import { type TimeFieldStepOptions, getTimeStep } from "./utils.js";
14
+
15
+ /** @since 6.3.0 */
16
+ export interface TimeFieldConstraints {
17
+ /**
18
+ * This **must** be in the format `HH:mm`:
19
+ * - `00:30` (12:30 AM)
20
+ * - `15:15` (03:15 PM)
21
+ *
22
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/time#time_value_format | Time value format}
23
+ */
24
+ min?: string;
25
+
26
+ /**
27
+ * This **must** be in the format `HH:mm`:
28
+ * - `00:30` (12:30 AM)
29
+ * - `15:15` (03:15 PM)
30
+ *
31
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/time#time_value_format | Time value format}
32
+ */
33
+ max?: string;
34
+
35
+ /**
36
+ * For time inputs, the value of step is given in seconds, with a scaling
37
+ * factor of 1000 (since the underlying numeric value is in milliseconds).
38
+ * The default value of step is 60, indicating 60 seconds (or 1 minute, or
39
+ * 60,000 milliseconds).
40
+ *
41
+ * When any is set as the value for step, the default 60 seconds is used, and
42
+ * the seconds value is not displayed in the UI.
43
+ *
44
+ * Here are a few examples:
45
+ *
46
+ * - `15` -&gt; 15 seconds
47
+ * - `60` -&gt; 1 minute
48
+ * - `900` -&gt; 15 minutes
49
+ * - `3600` -&gt; 1 hour
50
+ *
51
+ * Since this might be a bit confusing, the values can be provided in an
52
+ * object instead:
53
+ *
54
+ * ```ts
55
+ * { seconds: 30 }
56
+ * { minutes: 1 }
57
+ * { minutes: 15 }
58
+ * { hours: 1 }
59
+ * { seconds: 15, minutes: 30, hours: 1 }
60
+ * ```
61
+ *
62
+ * NOTE: The `min` and `max` props **must** be provided as well for the
63
+ * `step` to work.
64
+ *
65
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/time#step | step attribute}
66
+ */
67
+ step?: number | "any" | TimeFieldStepOptions;
68
+ }
69
+
70
+ /** @since 6.3.0 */
71
+ export interface TimeFieldOptions
72
+ extends Omit<
73
+ TextFieldHookOptions,
74
+ | "isNumber"
75
+ | "counter"
76
+ | "pattern"
77
+ | "maxLength"
78
+ | "minLength"
79
+ | "disableMaxLength"
80
+ >,
81
+ TimeFieldConstraints {}
82
+
83
+ /** @since 6.3.0 */
84
+ export interface ProvidedTimeFieldProps
85
+ extends Omit<ProvidedTextFieldProps, "value">,
86
+ Omit<TimeFieldConstraints, "step"> {
87
+ type: "time";
88
+ step?: number | "any";
89
+ defaultValue: Required<InputHTMLAttributes<HTMLInputElement>>["defaultValue"];
90
+ }
91
+
92
+ /** @since 6.3.0 */
93
+ export interface ProvidedTimeFieldMessageProps extends ProvidedTimeFieldProps {
94
+ /**
95
+ * These props will be defined as long as the `disableMessage` prop is not
96
+ * `true` from the `useTextField` hook.
97
+ */
98
+ messageProps: ProvidedFormMessageProps;
99
+ }
100
+
101
+ /** @since 6.3.0 */
102
+ export interface TimeFieldImplementation
103
+ extends Omit<TextFieldImplementation, "fieldProps"> {
104
+ fieldProps: ProvidedTimeFieldProps;
105
+ }
106
+
107
+ /** @since 6.3.0 */
108
+ export interface TimeFieldWithMessageImplementation
109
+ extends Omit<TextFieldWithMessageImplementation, "fieldProps"> {
110
+ fieldProps: ProvidedTimeFieldMessageProps;
111
+ }
112
+
113
+ /** @since 6.3.0 */
114
+ export interface ValidatedTimeFieldImplementation
115
+ extends TimeFieldImplementation {
116
+ fieldProps: ProvidedTimeFieldProps | ProvidedTimeFieldMessageProps;
117
+ }
118
+
119
+ /**
120
+ * @since 6.3.0
121
+ * @see {@link https://react-md.dev/components/native-time-field | NativeTimeField Demos}
122
+ * @see {@link https://react-md.dev/hooks/use-time-field | useTimeField Demos}
123
+ */
124
+ export function useTimeField(
125
+ options: TimeFieldOptions & { disableMessage: true }
126
+ ): TimeFieldImplementation;
127
+
128
+ /**
129
+ * The `useTimeField` is a small wrapper around the {@link useTextField} to be used
130
+ * with `<input type="time" />`. It is used in the `NativeTimeField` if an example
131
+ * implementation would like to be seen.
132
+ *
133
+ * @example Simple Example
134
+ * ```tsx
135
+ * import { useTimeField } from "@react-md/core/datetime/useTimeField";
136
+ * import { TextField } from "@react-md/core/form/TextField";
137
+ * import { type ReactElement } from "react";
138
+ *
139
+ * function Example(): ReactElement {
140
+ * const { value, fieldProps, error, errorMessage } = useTimeField({
141
+ * name: "appt",
142
+ * required: true,
143
+ * min: "08:00",
144
+ * max: "17:00",
145
+ * step: { minute: 15 },
146
+ * disableMessage: true,
147
+ * });
148
+ *
149
+ * // value: `""` or `"HH:mm"`
150
+ *
151
+ * return <TextField label="Appointment" {...fieldProps} />
152
+ * }
153
+ * ```
154
+ *
155
+ * @since 6.3.0
156
+ * @see {@link https://react-md.dev/components/native-time-field | NativeTimeField Demos}
157
+ * @see {@link https://react-md.dev/hooks/use-time-field | useTimeField Demos}
158
+ */
159
+ export function useTimeField(
160
+ options: TimeFieldOptions
161
+ ): TimeFieldWithMessageImplementation;
162
+
163
+ /**
164
+ * The `useTimeField` is a small wrapper around the {@link useTextField} to be used
165
+ * with `<input type="time" />`. It is used in the `NativeTimeField` if an example
166
+ * implementation would like to be seen.
167
+ *
168
+ * @example Simple Example
169
+ * ```tsx
170
+ * import { useTimeField } from "@react-md/core/datetime/useTimeField";
171
+ * import { TextField } from "@react-md/core/form/TextField";
172
+ * import { type ReactElement } from "react";
173
+ *
174
+ * function Example(): ReactElement {
175
+ * const { value, fieldProps } = useTimeField({
176
+ * name: "appt",
177
+ * required: true,
178
+ * min: "08:00",
179
+ * max: "17:00",
180
+ * step: { minute: 15 },
181
+ * });
182
+ *
183
+ * // value: `""` or `"HH:mm"`
184
+ *
185
+ * return <TextField label="Appointment" {...fieldProps} />
186
+ * }
187
+ * ```
188
+ *
189
+ * @since 6.3.0
190
+ * @see {@link https://react-md.dev/components/native-time-field | NativeTimeField Demos}
191
+ * @see {@link https://react-md.dev/hooks/use-time-field | useTimeField Demos}
192
+ */
193
+ export function useTimeField(
194
+ options: TimeFieldOptions
195
+ ): ValidatedTimeFieldImplementation {
196
+ const { min, max, step, ...fieldOptions } = options;
197
+ if (
198
+ process.env.NODE_ENV !== "production" &&
199
+ typeof step !== "undefined" &&
200
+ (!min || !max)
201
+ ) {
202
+ throw new Error(
203
+ "A `step` was provided to a time field without the `min` or `max` props."
204
+ );
205
+ }
206
+
207
+ const { errorMessage, fieldProps, ...impl } = useTextField(fieldOptions);
208
+
209
+ // NOTE: Unlike the other text field components, the `value` should **not**
210
+ // be provided since the time input behaves a bit weirdly with the `onChange`
211
+ // event and it is better to rely on default browser behavior instead of
212
+ // controlling the value. The flow is:
213
+ // - user types `12:30`
214
+ // - `onChange` is fired with `12:30`
215
+ // - user selects `30` and hits backspace
216
+ // - `onChange` is fired with `""`
217
+ // If the `value` is set, the other time values would be lost
218
+ const { value, ...allowedFieldProps } = fieldProps;
219
+ const initial = useRef(value);
220
+
221
+ return {
222
+ ...impl,
223
+ errorMessage,
224
+ fieldProps: {
225
+ ...allowedFieldProps,
226
+ defaultValue: initial.current,
227
+ min,
228
+ max,
229
+ step: getTimeStep(step),
230
+ type: "time",
231
+ },
232
+ };
233
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Since time input steps are based on seconds, this is a simple helper to
3
+ * create the step in a human-readable form.
4
+ *
5
+ * @since 6.3.0
6
+ */
7
+ export interface TimeFieldStepOptions {
8
+ /**
9
+ * @defaultValue `0`
10
+ */
11
+ seconds?: number;
12
+
13
+ /**
14
+ * @defaultValue `0`
15
+ */
16
+ minutes?: number;
17
+
18
+ /**
19
+ * @defaultValue `0`
20
+ */
21
+ hours?: number;
22
+ }
23
+
24
+ /**
25
+ * Since time input steps are based on seconds, this is a simple helper to
26
+ * create the step in a human-readable form.
27
+ *
28
+ * @example Simple Example
29
+ * ```tsx
30
+ * const step1 = getTimeStep({ minutes: 15 });
31
+ * const step2 = getTimeStep({ hours: 1 });
32
+ * const step3 = getTimeStep({ seconds: 15, minutes: 30, hours: 2 });
33
+ * ```
34
+ *
35
+ * @since 6.3.0
36
+ */
37
+ export function getTimeStep(
38
+ step: TimeFieldStepOptions | "any" | number | undefined
39
+ ): number | "any" | undefined {
40
+ if (!step || typeof step === "string" || typeof step === "number") {
41
+ return step;
42
+ }
43
+
44
+ const { hours = 0, minutes = 0, seconds = 0 } = step;
45
+
46
+ const total = Math.abs(Math.round(seconds + minutes * 60 + hours * 60 * 60));
47
+ return total === 0 ? undefined : total;
48
+ }
@@ -1,5 +1,6 @@
1
1
  import { type MouseEvent, type RefObject, type TouchEvent } from "react";
2
2
 
3
+ import { type MinMaxRange } from "../types.js";
3
4
  import {
4
5
  type ClientPositionEvent,
5
6
  type ClientPositionOptions,
@@ -62,9 +63,7 @@ export const getDragPosition = (options: DragPositionOptions): number => {
62
63
  /**
63
64
  * @internal
64
65
  */
65
- interface RelativeDragPositionOptions extends DragPositionOptions {
66
- min: number;
67
- max: number;
66
+ interface RelativeDragPositionOptions extends DragPositionOptions, MinMaxRange {
68
67
  step: number;
69
68
  rangeMin: number;
70
69
  rangeMax: number;
@@ -218,9 +217,7 @@ export const updateDragPosition = (
218
217
  * @internal
219
218
  * @since 6.0.0
220
219
  */
221
- export interface DeserializeDraggableValueOptions {
222
- min: number;
223
- max: number;
220
+ export interface DeserializeDraggableValueOptions extends MinMaxRange {
224
221
  item: string;
225
222
  }
226
223
 
@@ -29,6 +29,7 @@ export const ExpansionList = forwardRef<HTMLDivElement, ExpansionListProps>(
29
29
  const { onClick, onFocus, onKeyDown, children, ...remaining } = props;
30
30
 
31
31
  const { movementContext, movementProps } = useExpansionList({
32
+ ref,
32
33
  onClick,
33
34
  onFocus,
34
35
  onKeyDown,
@@ -36,7 +37,7 @@ export const ExpansionList = forwardRef<HTMLDivElement, ExpansionListProps>(
36
37
 
37
38
  return (
38
39
  <KeyboardMovementProvider value={movementContext}>
39
- <div {...remaining} {...movementProps} ref={ref}>
40
+ <div {...remaining} {...movementProps}>
40
41
  {children}
41
42
  </div>
42
43
  </KeyboardMovementProvider>