@proyecto-viviana/solidaria 0.2.4 → 0.2.8
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.
- package/LICENSE +21 -0
- package/dist/actiongroup/createActionGroup.d.ts +29 -0
- package/dist/actiongroup/createActionGroup.d.ts.map +1 -0
- package/dist/actiongroup/index.d.ts +2 -0
- package/dist/actiongroup/index.d.ts.map +1 -0
- package/dist/autocomplete/createAutocomplete.d.ts +6 -2
- package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
- package/dist/breadcrumbs/createBreadcrumbs.d.ts +2 -0
- package/dist/breadcrumbs/createBreadcrumbs.d.ts.map +1 -1
- package/dist/button/createToggleButtonGroup.d.ts +32 -0
- package/dist/button/createToggleButtonGroup.d.ts.map +1 -0
- package/dist/button/index.d.ts +2 -0
- package/dist/button/index.d.ts.map +1 -1
- package/dist/calendar/createCalendarCell.d.ts +2 -0
- package/dist/calendar/createCalendarCell.d.ts.map +1 -1
- package/dist/calendar/createCalendarGrid.d.ts.map +1 -1
- package/dist/calendar/createRangeCalendarCell.d.ts +3 -1
- package/dist/calendar/createRangeCalendarCell.d.ts.map +1 -1
- package/dist/checkbox/createCheckboxGroup.d.ts +5 -1
- package/dist/checkbox/createCheckboxGroup.d.ts.map +1 -1
- package/dist/collections/index.d.ts +56 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/color/createColorArea.d.ts.map +1 -1
- package/dist/color/createColorSlider.d.ts.map +1 -1
- package/dist/color/createColorWheel.d.ts.map +1 -1
- package/dist/combobox/createComboBox.d.ts +6 -0
- package/dist/combobox/createComboBox.d.ts.map +1 -1
- package/dist/datepicker/createDatePicker.d.ts +6 -0
- package/dist/datepicker/createDatePicker.d.ts.map +1 -1
- package/dist/datepicker/createDateRangePicker.d.ts +40 -0
- package/dist/datepicker/createDateRangePicker.d.ts.map +1 -0
- package/dist/datepicker/createDateSegment.d.ts +1 -1
- package/dist/datepicker/createDateSegment.d.ts.map +1 -1
- package/dist/datepicker/createTimeSegment.d.ts +29 -0
- package/dist/datepicker/createTimeSegment.d.ts.map +1 -0
- package/dist/datepicker/index.d.ts +2 -0
- package/dist/datepicker/index.d.ts.map +1 -1
- package/dist/disclosure/createDisclosureGroup.d.ts +2 -1
- package/dist/disclosure/createDisclosureGroup.d.ts.map +1 -1
- package/dist/dnd/createDrag.d.ts.map +1 -1
- package/dist/dnd/createDraggableCollection.d.ts +4 -0
- package/dist/dnd/createDraggableCollection.d.ts.map +1 -1
- package/dist/dnd/createDraggableItem.d.ts.map +1 -1
- package/dist/dnd/createDrop.d.ts.map +1 -1
- package/dist/dnd/createDroppableCollection.d.ts +32 -1
- package/dist/dnd/createDroppableCollection.d.ts.map +1 -1
- package/dist/dnd/createDroppableItem.d.ts.map +1 -1
- package/dist/dnd/index.d.ts +1 -1
- package/dist/dnd/index.d.ts.map +1 -1
- package/dist/grid/createGrid.d.ts.map +1 -1
- package/dist/gridlist/createGridList.d.ts.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4659 -3452
- package/dist/index.js.map +1 -7
- package/dist/index.ssr.js +4659 -3452
- package/dist/index.ssr.js.map +1 -7
- package/dist/interactions/createFocus.d.ts.map +1 -1
- package/dist/interactions/createFocusWithin.d.ts.map +1 -1
- package/dist/link/createLink.d.ts +10 -0
- package/dist/link/createLink.d.ts.map +1 -1
- package/dist/listbox/createListBox.d.ts +1 -0
- package/dist/listbox/createListBox.d.ts.map +1 -1
- package/dist/listbox/createOption.d.ts.map +1 -1
- package/dist/menu/createMenu.d.ts +1 -0
- package/dist/menu/createMenu.d.ts.map +1 -1
- package/dist/meter/createMeter.d.ts.map +1 -1
- package/dist/numberfield/createNumberField.d.ts +18 -0
- package/dist/numberfield/createNumberField.d.ts.map +1 -1
- package/dist/overlays/createModal.d.ts +16 -0
- package/dist/overlays/createModal.d.ts.map +1 -1
- package/dist/overlays/createOverlay.d.ts.map +1 -1
- package/dist/overlays/index.d.ts +1 -1
- package/dist/overlays/index.d.ts.map +1 -1
- package/dist/popover/createOverlayPosition.d.ts.map +1 -1
- package/dist/popover/createPopover.d.ts.map +1 -1
- package/dist/progress/createProgressBar.d.ts.map +1 -1
- package/dist/radio/createRadioGroup.d.ts +2 -2
- package/dist/radio/createRadioGroup.d.ts.map +1 -1
- package/dist/searchfield/createSearchField.d.ts.map +1 -1
- package/dist/select/createHiddenSelect.d.ts.map +1 -1
- package/dist/select/createSelect.d.ts.map +1 -1
- package/dist/slider/createSlider.d.ts.map +1 -1
- package/dist/table/createTable.d.ts.map +1 -1
- package/dist/tabs/createTabs.d.ts +1 -1
- package/dist/tabs/createTabs.d.ts.map +1 -1
- package/dist/tag/createTag.d.ts.map +1 -1
- package/dist/tag/createTagGroup.d.ts.map +1 -1
- package/dist/toast/createToast.d.ts +4 -0
- package/dist/toast/createToast.d.ts.map +1 -1
- package/dist/toast/createToastRegion.d.ts.map +1 -1
- package/dist/toolbar/createToolbar.d.ts.map +1 -1
- package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
- package/dist/tree/createTree.d.ts.map +1 -1
- package/dist/tree/createTreeItem.d.ts.map +1 -1
- package/dist/tree/types.d.ts +4 -0
- package/dist/tree/types.d.ts.map +1 -1
- package/dist/utils/env.d.ts +1 -1
- package/dist/utils/env.d.ts.map +1 -1
- package/dist/utils/platform.d.ts.map +1 -1
- package/dist/visually-hidden/createVisuallyHidden.d.ts.map +1 -1
- package/package.json +8 -6
- package/src/actiongroup/createActionGroup.ts +324 -0
- package/src/actiongroup/index.ts +8 -0
- package/src/autocomplete/createAutocomplete.ts +32 -9
- package/src/breadcrumbs/createBreadcrumbs.ts +10 -15
- package/src/button/createButton.ts +1 -1
- package/src/button/createToggleButtonGroup.ts +128 -0
- package/src/button/index.ts +9 -0
- package/src/calendar/createCalendarCell.ts +6 -4
- package/src/calendar/createCalendarGrid.ts +27 -18
- package/src/calendar/createRangeCalendarCell.ts +26 -9
- package/src/checkbox/createCheckboxGroup.ts +21 -4
- package/src/collections/index.ts +242 -0
- package/src/color/createColorArea.ts +380 -314
- package/src/color/createColorField.ts +137 -137
- package/src/color/createColorSlider.ts +286 -197
- package/src/color/createColorSwatch.ts +40 -40
- package/src/color/createColorWheel.ts +218 -208
- package/src/color/index.ts +24 -24
- package/src/color/types.ts +116 -116
- package/src/combobox/createComboBox.ts +670 -647
- package/src/combobox/index.ts +6 -6
- package/src/datepicker/createDatePicker.ts +54 -16
- package/src/datepicker/createDateRangePicker.ts +246 -0
- package/src/datepicker/createDateSegment.ts +185 -31
- package/src/datepicker/createTimeSegment.ts +370 -0
- package/src/datepicker/index.ts +14 -0
- package/src/dialog/createDialog.ts +120 -120
- package/src/dialog/index.ts +2 -2
- package/src/dialog/types.ts +19 -19
- package/src/disclosure/createDisclosureGroup.ts +5 -2
- package/src/dnd/createDrag.ts +224 -209
- package/src/dnd/createDraggableCollection.ts +96 -63
- package/src/dnd/createDraggableItem.ts +259 -243
- package/src/dnd/createDrop.ts +322 -321
- package/src/dnd/createDroppableCollection.ts +682 -293
- package/src/dnd/createDroppableItem.ts +215 -213
- package/src/dnd/index.ts +55 -47
- package/src/dnd/types.ts +89 -89
- package/src/dnd/utils.ts +294 -294
- package/src/focus/createAutoFocus.ts +321 -321
- package/src/focus/createFocusRestore.ts +313 -313
- package/src/focus/createVirtualFocus.ts +396 -396
- package/src/form/createFormValidation.ts +224 -224
- package/src/form/index.ts +11 -11
- package/src/grid/createGrid.ts +3 -1
- package/src/gridlist/createGridList.ts +16 -0
- package/src/gridlist/createGridListItem.ts +1 -1
- package/src/i18n/NumberFormatter.ts +266 -266
- package/src/i18n/createCollator.ts +79 -79
- package/src/i18n/createDateFormatter.ts +83 -83
- package/src/i18n/createFilter.ts +131 -131
- package/src/i18n/createNumberFormatter.ts +52 -52
- package/src/i18n/index.ts +40 -40
- package/src/i18n/locale.tsx +188 -188
- package/src/i18n/utils.ts +99 -99
- package/src/index.ts +51 -0
- package/src/interactions/createFocus.ts +6 -5
- package/src/interactions/createFocusWithin.ts +6 -5
- package/src/interactions/createLongPress.ts +174 -174
- package/src/interactions/createMove.ts +289 -289
- package/src/interactions/createPress.ts +5 -5
- package/src/landmark/createLandmark.ts +377 -377
- package/src/landmark/index.ts +8 -8
- package/src/link/createLink.ts +23 -8
- package/src/listbox/createListBox.ts +308 -269
- package/src/listbox/createOption.ts +162 -151
- package/src/listbox/index.ts +12 -12
- package/src/live-announcer/announce.ts +322 -322
- package/src/live-announcer/index.ts +9 -9
- package/src/menu/createMenu.ts +405 -396
- package/src/menu/createMenuItem.ts +149 -149
- package/src/menu/createMenuTrigger.ts +88 -88
- package/src/menu/index.ts +18 -18
- package/src/meter/createMeter.ts +1 -6
- package/src/numberfield/createNumberField.ts +311 -268
- package/src/numberfield/index.ts +5 -5
- package/src/overlays/ariaHideOutside.ts +219 -219
- package/src/overlays/createInteractOutside.ts +149 -149
- package/src/overlays/createModal.tsx +238 -202
- package/src/overlays/createOverlay.ts +165 -155
- package/src/overlays/createOverlayTrigger.ts +85 -85
- package/src/overlays/createPreventScroll.ts +266 -266
- package/src/overlays/index.ts +48 -44
- package/src/popover/calculatePosition.ts +6 -6
- package/src/popover/createOverlayPosition.ts +7 -4
- package/src/popover/createPopover.ts +21 -7
- package/src/progress/createProgressBar.ts +6 -1
- package/src/radio/createRadioGroup.ts +88 -14
- package/src/searchfield/createSearchField.ts +241 -186
- package/src/searchfield/index.ts +2 -2
- package/src/select/createHiddenSelect.tsx +263 -236
- package/src/select/createSelect.ts +373 -395
- package/src/select/index.ts +14 -14
- package/src/slider/createSlider.ts +364 -349
- package/src/slider/index.ts +2 -2
- package/src/ssr/index.tsx +370 -370
- package/src/table/createTable.ts +3 -1
- package/src/table/createTableColumnHeader.ts +1 -1
- package/src/table/createTableRow.ts +1 -1
- package/src/tabs/createTabs.ts +80 -51
- package/src/tag/createTag.ts +135 -6
- package/src/tag/createTagGroup.ts +7 -2
- package/src/toast/createToast.ts +8 -2
- package/src/toast/createToastRegion.ts +0 -1
- package/src/toolbar/createToolbar.ts +75 -1
- package/src/tooltip/createTooltip.ts +79 -79
- package/src/tooltip/createTooltipTrigger.ts +226 -222
- package/src/tooltip/index.ts +6 -6
- package/src/tree/createTree.ts +261 -246
- package/src/tree/createTreeItem.ts +282 -233
- package/src/tree/createTreeSelectionCheckbox.ts +68 -68
- package/src/tree/index.ts +16 -16
- package/src/tree/types.ts +91 -87
- package/src/utils/env.ts +55 -54
- package/src/utils/platform.ts +16 -6
- package/src/visually-hidden/createVisuallyHidden.ts +139 -124
- package/src/visually-hidden/index.ts +6 -6
package/src/combobox/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export {
|
|
2
|
-
createComboBox,
|
|
3
|
-
getComboBoxData,
|
|
4
|
-
type AriaComboBoxProps,
|
|
5
|
-
type ComboBoxAria,
|
|
6
|
-
} from './createComboBox';
|
|
1
|
+
export {
|
|
2
|
+
createComboBox,
|
|
3
|
+
getComboBoxData,
|
|
4
|
+
type AriaComboBoxProps,
|
|
5
|
+
type ComboBoxAria,
|
|
6
|
+
} from './createComboBox';
|
|
@@ -10,6 +10,7 @@ import { createId } from '../ssr';
|
|
|
10
10
|
import { createLabel } from '../label/createLabel';
|
|
11
11
|
import { access, type MaybeAccessor } from '../utils/reactivity';
|
|
12
12
|
import { mergeProps } from '../utils/mergeProps';
|
|
13
|
+
import { useLocale } from '../i18n';
|
|
13
14
|
import type { DateFieldState, CalendarState } from '@proyecto-viviana/solid-stately';
|
|
14
15
|
|
|
15
16
|
// ============================================
|
|
@@ -39,6 +40,12 @@ export interface AriaDatePickerProps {
|
|
|
39
40
|
description?: string;
|
|
40
41
|
/** Error message. */
|
|
41
42
|
errorMessage?: string;
|
|
43
|
+
/** Accessible label for the calendar trigger button. */
|
|
44
|
+
buttonAriaLabel?: string;
|
|
45
|
+
/** Accessible label for the calendar dialog. */
|
|
46
|
+
dialogAriaLabel?: string;
|
|
47
|
+
/** Accessible label for the calendar grid region. */
|
|
48
|
+
calendarAriaLabel?: string;
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
export interface DatePickerState {
|
|
@@ -84,6 +91,7 @@ export function createDatePicker<T extends DateFieldState, C extends CalendarSta
|
|
|
84
91
|
overlayState: DatePickerState,
|
|
85
92
|
_calendarState?: C
|
|
86
93
|
): DatePickerAria {
|
|
94
|
+
const locale = useLocale();
|
|
87
95
|
const getProps = () => access(props);
|
|
88
96
|
const id = createId(getProps().id);
|
|
89
97
|
const descriptionId = createId();
|
|
@@ -117,29 +125,31 @@ export function createDatePicker<T extends DateFieldState, C extends CalendarSta
|
|
|
117
125
|
// Group props
|
|
118
126
|
const groupProps = createMemo(() => {
|
|
119
127
|
const p = getProps();
|
|
128
|
+
const isInvalid = p.isInvalid || state.isInvalid();
|
|
120
129
|
|
|
121
130
|
return mergeProps(labelFieldProps as Record<string, unknown>, {
|
|
122
131
|
id,
|
|
123
132
|
role: 'group',
|
|
124
133
|
'aria-disabled': p.isDisabled || state.isDisabled() || undefined,
|
|
134
|
+
'aria-readonly': p.isReadOnly || state.isReadOnly() || undefined,
|
|
135
|
+
'aria-required': p.isRequired || state.isRequired() || undefined,
|
|
136
|
+
'aria-invalid': isInvalid || undefined,
|
|
125
137
|
'aria-describedby': getAriaDescribedBy(),
|
|
126
138
|
});
|
|
127
139
|
});
|
|
128
140
|
|
|
129
|
-
// Field props
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
'aria-expanded': overlayState.isOpen,
|
|
133
|
-
'aria-controls': overlayState.isOpen ? dialogId : undefined,
|
|
134
|
-
}));
|
|
141
|
+
// Field props are applied to DateInput (a non-popup container), so avoid
|
|
142
|
+
// popup trigger attributes like aria-expanded/aria-haspopup here.
|
|
143
|
+
const fieldProps = createMemo(() => ({}));
|
|
135
144
|
|
|
136
145
|
// Button props
|
|
137
146
|
const buttonProps = createMemo(() => {
|
|
138
147
|
const p = getProps();
|
|
139
148
|
const isDisabled = p.isDisabled || state.isDisabled();
|
|
149
|
+
const defaults = getDatePickerLabelDefaults(locale().locale);
|
|
140
150
|
|
|
141
151
|
return {
|
|
142
|
-
'aria-label':
|
|
152
|
+
'aria-label': p.buttonAriaLabel ?? defaults.button,
|
|
143
153
|
'aria-haspopup': 'dialog' as const,
|
|
144
154
|
'aria-expanded': overlayState.isOpen,
|
|
145
155
|
'aria-controls': overlayState.isOpen ? dialogId : undefined,
|
|
@@ -154,17 +164,23 @@ export function createDatePicker<T extends DateFieldState, C extends CalendarSta
|
|
|
154
164
|
});
|
|
155
165
|
|
|
156
166
|
// Dialog props
|
|
157
|
-
const dialogProps = createMemo(() =>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
const dialogProps = createMemo(() => {
|
|
168
|
+
const defaults = getDatePickerLabelDefaults(locale().locale);
|
|
169
|
+
return {
|
|
170
|
+
id: dialogId,
|
|
171
|
+
role: 'dialog',
|
|
172
|
+
'aria-modal': true,
|
|
173
|
+
'aria-label': getProps().dialogAriaLabel ?? defaults.dialog,
|
|
174
|
+
};
|
|
175
|
+
});
|
|
163
176
|
|
|
164
177
|
// Calendar props
|
|
165
|
-
const calendarProps = createMemo(() =>
|
|
166
|
-
|
|
167
|
-
|
|
178
|
+
const calendarProps = createMemo(() => {
|
|
179
|
+
const defaults = getDatePickerLabelDefaults(locale().locale);
|
|
180
|
+
return {
|
|
181
|
+
'aria-label': getProps().calendarAriaLabel ?? getProps().dialogAriaLabel ?? defaults.calendar,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
168
184
|
|
|
169
185
|
// Description props
|
|
170
186
|
const descriptionProps = createMemo(() => ({
|
|
@@ -204,3 +220,25 @@ export function createDatePicker<T extends DateFieldState, C extends CalendarSta
|
|
|
204
220
|
},
|
|
205
221
|
};
|
|
206
222
|
}
|
|
223
|
+
|
|
224
|
+
function getDatePickerLabelDefaults(locale: string): {
|
|
225
|
+
button: string;
|
|
226
|
+
dialog: string;
|
|
227
|
+
calendar: string;
|
|
228
|
+
} {
|
|
229
|
+
const language = locale.toLowerCase().split('-')[0] ?? 'en';
|
|
230
|
+
|
|
231
|
+
if (language === 'es') {
|
|
232
|
+
return {
|
|
233
|
+
button: 'Abrir calendario',
|
|
234
|
+
dialog: 'Calendario',
|
|
235
|
+
calendar: 'Calendario',
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
button: 'Open calendar',
|
|
241
|
+
dialog: 'Calendar',
|
|
242
|
+
calendar: 'Calendar',
|
|
243
|
+
};
|
|
244
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createDateRangePicker hook for Solidaria
|
|
3
|
+
*
|
|
4
|
+
* Provides behavior and accessibility wiring for range date pickers.
|
|
5
|
+
* Compatibility target: @react-aria/datepicker useDateRangePicker.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createMemo } from 'solid-js';
|
|
9
|
+
import { createId } from '../ssr';
|
|
10
|
+
import { createLabel } from '../label/createLabel';
|
|
11
|
+
import { access, type MaybeAccessor } from '../utils/reactivity';
|
|
12
|
+
import { mergeProps } from '../utils/mergeProps';
|
|
13
|
+
import { useLocale } from '../i18n';
|
|
14
|
+
import type { RangeCalendarState } from '@proyecto-viviana/solid-stately';
|
|
15
|
+
import type { DatePickerState } from './createDatePicker';
|
|
16
|
+
|
|
17
|
+
export interface AriaDateRangePickerProps {
|
|
18
|
+
id?: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
'aria-label'?: string;
|
|
21
|
+
'aria-labelledby'?: string;
|
|
22
|
+
'aria-describedby'?: string;
|
|
23
|
+
isDisabled?: boolean;
|
|
24
|
+
isReadOnly?: boolean;
|
|
25
|
+
isRequired?: boolean;
|
|
26
|
+
isInvalid?: boolean;
|
|
27
|
+
description?: string;
|
|
28
|
+
errorMessage?: string;
|
|
29
|
+
buttonAriaLabel?: string;
|
|
30
|
+
dialogAriaLabel?: string;
|
|
31
|
+
calendarAriaLabel?: string;
|
|
32
|
+
startFieldAriaLabel?: string;
|
|
33
|
+
endFieldAriaLabel?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DateRangePickerAria {
|
|
37
|
+
groupProps: Record<string, unknown>;
|
|
38
|
+
labelProps: Record<string, unknown>;
|
|
39
|
+
startFieldProps: Record<string, unknown>;
|
|
40
|
+
endFieldProps: Record<string, unknown>;
|
|
41
|
+
buttonProps: Record<string, unknown>;
|
|
42
|
+
dialogProps: Record<string, unknown>;
|
|
43
|
+
calendarProps: Record<string, unknown>;
|
|
44
|
+
descriptionProps: Record<string, unknown>;
|
|
45
|
+
errorMessageProps: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createDateRangePicker<T extends RangeCalendarState>(
|
|
49
|
+
props: MaybeAccessor<AriaDateRangePickerProps>,
|
|
50
|
+
state: T,
|
|
51
|
+
overlayState: DatePickerState
|
|
52
|
+
): DateRangePickerAria {
|
|
53
|
+
const locale = useLocale();
|
|
54
|
+
const getProps = () => access(props);
|
|
55
|
+
const id = createId(getProps().id);
|
|
56
|
+
const startFieldId = createId();
|
|
57
|
+
const endFieldId = createId();
|
|
58
|
+
const descriptionId = createId();
|
|
59
|
+
const errorMessageId = createId();
|
|
60
|
+
const dialogId = createId();
|
|
61
|
+
|
|
62
|
+
const { labelProps, fieldProps: labelFieldProps } = createLabel({
|
|
63
|
+
get label() { return getProps().label; },
|
|
64
|
+
get 'aria-label'() { return getProps()['aria-label']; },
|
|
65
|
+
get 'aria-labelledby'() { return getProps()['aria-labelledby']; },
|
|
66
|
+
labelElementType: 'span',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const getAriaDescribedBy = () => {
|
|
70
|
+
const p = getProps();
|
|
71
|
+
const ids: string[] = [];
|
|
72
|
+
if (p['aria-describedby']) ids.push(p['aria-describedby']);
|
|
73
|
+
if (p.description) ids.push(descriptionId);
|
|
74
|
+
if (p.isInvalid && p.errorMessage) ids.push(errorMessageId);
|
|
75
|
+
return ids.length > 0 ? ids.join(' ') : undefined;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const groupProps = createMemo(() => {
|
|
79
|
+
const p = getProps();
|
|
80
|
+
const isInvalid = p.isInvalid;
|
|
81
|
+
return mergeProps(labelFieldProps as Record<string, unknown>, {
|
|
82
|
+
id,
|
|
83
|
+
role: 'group',
|
|
84
|
+
'aria-disabled': p.isDisabled || state.isDisabled() || undefined,
|
|
85
|
+
'aria-readonly': p.isReadOnly || state.isReadOnly() || undefined,
|
|
86
|
+
'aria-required': p.isRequired || undefined,
|
|
87
|
+
'aria-invalid': isInvalid || undefined,
|
|
88
|
+
'aria-describedby': getAriaDescribedBy(),
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const startFieldProps = createMemo(() => {
|
|
93
|
+
const p = getProps();
|
|
94
|
+
const defaults = getDateRangePickerLabelDefaults(locale().locale);
|
|
95
|
+
const isDisabled = p.isDisabled || state.isDisabled();
|
|
96
|
+
const isReadOnly = p.isReadOnly || state.isReadOnly();
|
|
97
|
+
const isInvalid = p.isInvalid;
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
id: startFieldId,
|
|
101
|
+
'aria-label': p.startFieldAriaLabel ?? defaults.startField,
|
|
102
|
+
'aria-describedby': getAriaDescribedBy(),
|
|
103
|
+
'aria-disabled': isDisabled || undefined,
|
|
104
|
+
'aria-readonly': isReadOnly || undefined,
|
|
105
|
+
'aria-invalid': isInvalid || undefined,
|
|
106
|
+
tabIndex: isDisabled ? -1 : 0,
|
|
107
|
+
onClick: () => {
|
|
108
|
+
if (!isDisabled) {
|
|
109
|
+
overlayState.open();
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
onKeyDown: (event: KeyboardEvent) => {
|
|
113
|
+
if (isDisabled) return;
|
|
114
|
+
if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
overlayState.open();
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const endFieldProps = createMemo(() => {
|
|
123
|
+
const p = getProps();
|
|
124
|
+
const defaults = getDateRangePickerLabelDefaults(locale().locale);
|
|
125
|
+
const isDisabled = p.isDisabled || state.isDisabled();
|
|
126
|
+
const isReadOnly = p.isReadOnly || state.isReadOnly();
|
|
127
|
+
const isInvalid = p.isInvalid;
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
id: endFieldId,
|
|
131
|
+
'aria-label': p.endFieldAriaLabel ?? defaults.endField,
|
|
132
|
+
'aria-describedby': getAriaDescribedBy(),
|
|
133
|
+
'aria-disabled': isDisabled || undefined,
|
|
134
|
+
'aria-readonly': isReadOnly || undefined,
|
|
135
|
+
'aria-invalid': isInvalid || undefined,
|
|
136
|
+
tabIndex: isDisabled ? -1 : 0,
|
|
137
|
+
onClick: () => {
|
|
138
|
+
if (!isDisabled) {
|
|
139
|
+
overlayState.open();
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
onKeyDown: (event: KeyboardEvent) => {
|
|
143
|
+
if (isDisabled) return;
|
|
144
|
+
if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
overlayState.open();
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const buttonProps = createMemo(() => {
|
|
153
|
+
const p = getProps();
|
|
154
|
+
const defaults = getDateRangePickerLabelDefaults(locale().locale);
|
|
155
|
+
const isDisabled = p.isDisabled || state.isDisabled();
|
|
156
|
+
return {
|
|
157
|
+
'aria-label': p.buttonAriaLabel ?? defaults.button,
|
|
158
|
+
'aria-haspopup': 'dialog' as const,
|
|
159
|
+
'aria-expanded': overlayState.isOpen,
|
|
160
|
+
'aria-controls': overlayState.isOpen ? dialogId : undefined,
|
|
161
|
+
disabled: isDisabled,
|
|
162
|
+
tabIndex: 0,
|
|
163
|
+
onClick: () => {
|
|
164
|
+
if (!isDisabled) overlayState.toggle();
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const dialogProps = createMemo(() => {
|
|
170
|
+
const defaults = getDateRangePickerLabelDefaults(locale().locale);
|
|
171
|
+
return {
|
|
172
|
+
id: dialogId,
|
|
173
|
+
role: 'dialog',
|
|
174
|
+
'aria-modal': true,
|
|
175
|
+
'aria-label': getProps().dialogAriaLabel ?? defaults.dialog,
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const calendarProps = createMemo(() => {
|
|
180
|
+
const defaults = getDateRangePickerLabelDefaults(locale().locale);
|
|
181
|
+
return {
|
|
182
|
+
'aria-label': getProps().calendarAriaLabel ?? getProps().dialogAriaLabel ?? defaults.calendar,
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const descriptionProps = createMemo(() => ({ id: descriptionId }));
|
|
187
|
+
const errorMessageProps = createMemo(() => ({ id: errorMessageId, role: 'alert' }));
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
get groupProps() {
|
|
191
|
+
return groupProps();
|
|
192
|
+
},
|
|
193
|
+
get labelProps() {
|
|
194
|
+
return labelProps as Record<string, unknown>;
|
|
195
|
+
},
|
|
196
|
+
get startFieldProps() {
|
|
197
|
+
return startFieldProps();
|
|
198
|
+
},
|
|
199
|
+
get endFieldProps() {
|
|
200
|
+
return endFieldProps();
|
|
201
|
+
},
|
|
202
|
+
get buttonProps() {
|
|
203
|
+
return buttonProps();
|
|
204
|
+
},
|
|
205
|
+
get dialogProps() {
|
|
206
|
+
return dialogProps();
|
|
207
|
+
},
|
|
208
|
+
get calendarProps() {
|
|
209
|
+
return calendarProps();
|
|
210
|
+
},
|
|
211
|
+
get descriptionProps() {
|
|
212
|
+
return descriptionProps();
|
|
213
|
+
},
|
|
214
|
+
get errorMessageProps() {
|
|
215
|
+
return errorMessageProps();
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function getDateRangePickerLabelDefaults(locale: string): {
|
|
221
|
+
button: string;
|
|
222
|
+
dialog: string;
|
|
223
|
+
calendar: string;
|
|
224
|
+
startField: string;
|
|
225
|
+
endField: string;
|
|
226
|
+
} {
|
|
227
|
+
const language = locale.toLowerCase().split('-')[0] ?? 'en';
|
|
228
|
+
|
|
229
|
+
if (language === 'es') {
|
|
230
|
+
return {
|
|
231
|
+
button: 'Abrir calendario de rango',
|
|
232
|
+
dialog: 'Calendario de rango',
|
|
233
|
+
calendar: 'Calendario de rango',
|
|
234
|
+
startField: 'Fecha de inicio',
|
|
235
|
+
endField: 'Fecha de fin',
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
button: 'Open range calendar',
|
|
241
|
+
dialog: 'Range calendar',
|
|
242
|
+
calendar: 'Range calendar',
|
|
243
|
+
startField: 'Start date',
|
|
244
|
+
endField: 'End date',
|
|
245
|
+
};
|
|
246
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { createSignal, createMemo } from 'solid-js';
|
|
9
9
|
import { access, type MaybeAccessor } from '../utils/reactivity';
|
|
10
10
|
import type { DateFieldState, DateSegment, DateSegmentType } from '@proyecto-viviana/solid-stately';
|
|
11
|
+
import { useLocale } from '../i18n';
|
|
11
12
|
|
|
12
13
|
// ============================================
|
|
13
14
|
// TYPES
|
|
@@ -41,11 +42,13 @@ export interface DateSegmentAria {
|
|
|
41
42
|
export function createDateSegment<T extends DateFieldState>(
|
|
42
43
|
props: MaybeAccessor<AriaDateSegmentProps>,
|
|
43
44
|
state: T,
|
|
44
|
-
|
|
45
|
+
ref?: () => HTMLElement | null
|
|
45
46
|
): DateSegmentAria {
|
|
46
47
|
const getProps = () => access(props);
|
|
47
48
|
const [isFocused, setIsFocused] = createSignal(false);
|
|
48
49
|
const [enteredKeys, setEnteredKeys] = createSignal('');
|
|
50
|
+
const [isComposing, setIsComposing] = createSignal(false);
|
|
51
|
+
const locale = useLocale();
|
|
49
52
|
|
|
50
53
|
// Get the segment from props
|
|
51
54
|
const segment = createMemo(() => getProps().segment);
|
|
@@ -56,9 +59,47 @@ export function createDateSegment<T extends DateFieldState>(
|
|
|
56
59
|
return seg.isEditable && !state.isDisabled() && !state.isReadOnly();
|
|
57
60
|
});
|
|
58
61
|
|
|
62
|
+
const focusSegment = (target: 'first' | 'last' | 'prev' | 'next') => {
|
|
63
|
+
const el = ref?.();
|
|
64
|
+
if (!el) return;
|
|
65
|
+
|
|
66
|
+
const container = el.parentElement;
|
|
67
|
+
if (!container) return;
|
|
68
|
+
|
|
69
|
+
const segments = Array.from(
|
|
70
|
+
container.querySelectorAll<HTMLElement>('[role="spinbutton"]')
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (segments.length === 0) return;
|
|
74
|
+
|
|
75
|
+
if (target === 'first') {
|
|
76
|
+
segments[0]?.focus();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (target === 'last') {
|
|
81
|
+
segments[segments.length - 1]?.focus();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const currentIndex = segments.indexOf(el);
|
|
86
|
+
if (currentIndex < 0) return;
|
|
87
|
+
|
|
88
|
+
const nextIndex = target === 'next' ? currentIndex + 1 : currentIndex - 1;
|
|
89
|
+
if (nextIndex >= 0 && nextIndex < segments.length) {
|
|
90
|
+
segments[nextIndex]?.focus();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const clearCurrentSegment = (type: DateSegmentType) => {
|
|
95
|
+
state.clearSegment(type);
|
|
96
|
+
setEnteredKeys('');
|
|
97
|
+
};
|
|
98
|
+
|
|
59
99
|
// Handle keyboard input
|
|
60
100
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
61
101
|
if (!isEditable()) return;
|
|
102
|
+
if (isComposing()) return;
|
|
62
103
|
|
|
63
104
|
const seg = segment();
|
|
64
105
|
const type = seg.type;
|
|
@@ -66,6 +107,14 @@ export function createDateSegment<T extends DateFieldState>(
|
|
|
66
107
|
if (type === 'literal') return;
|
|
67
108
|
|
|
68
109
|
switch (e.key) {
|
|
110
|
+
case 'ArrowRight':
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
focusSegment(locale().direction === 'rtl' ? 'prev' : 'next');
|
|
113
|
+
break;
|
|
114
|
+
case 'ArrowLeft':
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
focusSegment(locale().direction === 'rtl' ? 'next' : 'prev');
|
|
117
|
+
break;
|
|
69
118
|
case 'ArrowUp':
|
|
70
119
|
e.preventDefault();
|
|
71
120
|
state.incrementSegment(type);
|
|
@@ -74,17 +123,33 @@ export function createDateSegment<T extends DateFieldState>(
|
|
|
74
123
|
e.preventDefault();
|
|
75
124
|
state.decrementSegment(type);
|
|
76
125
|
break;
|
|
126
|
+
case 'Home':
|
|
127
|
+
e.preventDefault();
|
|
128
|
+
focusSegment('first');
|
|
129
|
+
break;
|
|
130
|
+
case 'End':
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
focusSegment('last');
|
|
133
|
+
break;
|
|
77
134
|
case 'Backspace':
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
// Match common date-field UX: backspace on an empty placeholder moves to previous segment.
|
|
137
|
+
if (seg.isPlaceholder) {
|
|
138
|
+
focusSegment('prev');
|
|
139
|
+
} else {
|
|
140
|
+
clearCurrentSegment(type);
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
78
143
|
case 'Delete':
|
|
79
144
|
e.preventDefault();
|
|
80
|
-
|
|
81
|
-
setEnteredKeys('');
|
|
145
|
+
clearCurrentSegment(type);
|
|
82
146
|
break;
|
|
83
147
|
default:
|
|
84
148
|
// Handle numeric input
|
|
85
|
-
|
|
149
|
+
const normalizedDigit = normalizeDigit(e.key);
|
|
150
|
+
if (normalizedDigit) {
|
|
86
151
|
e.preventDefault();
|
|
87
|
-
handleNumericInput(
|
|
152
|
+
handleNumericInput(normalizedDigit, type, seg);
|
|
88
153
|
}
|
|
89
154
|
break;
|
|
90
155
|
}
|
|
@@ -100,6 +165,7 @@ export function createDateSegment<T extends DateFieldState>(
|
|
|
100
165
|
const numValue = parseInt(newKeys, 10);
|
|
101
166
|
const maxValue = seg.maxValue ?? 99;
|
|
102
167
|
const minValue = seg.minValue ?? 0;
|
|
168
|
+
const maxDigits = String(maxValue).length;
|
|
103
169
|
|
|
104
170
|
// Check if we should accept more digits
|
|
105
171
|
if (numValue <= maxValue) {
|
|
@@ -107,9 +173,9 @@ export function createDateSegment<T extends DateFieldState>(
|
|
|
107
173
|
|
|
108
174
|
// If entering more digits wouldn't make sense, clear entered keys
|
|
109
175
|
const potentialNextValue = parseInt(newKeys + '0', 10);
|
|
110
|
-
if (potentialNextValue > maxValue || newKeys.length >=
|
|
176
|
+
if (potentialNextValue > maxValue || newKeys.length >= maxDigits) {
|
|
111
177
|
setEnteredKeys('');
|
|
112
|
-
|
|
178
|
+
focusSegment('next');
|
|
113
179
|
} else {
|
|
114
180
|
setEnteredKeys(newKeys);
|
|
115
181
|
}
|
|
@@ -118,8 +184,58 @@ export function createDateSegment<T extends DateFieldState>(
|
|
|
118
184
|
const singleValue = parseInt(key, 10);
|
|
119
185
|
if (singleValue >= minValue && singleValue <= maxValue) {
|
|
120
186
|
state.setSegment(type, singleValue);
|
|
187
|
+
const potentialNextValue = parseInt(key + '0', 10);
|
|
188
|
+
if (potentialNextValue > maxValue || key.length >= maxDigits) {
|
|
189
|
+
setEnteredKeys('');
|
|
190
|
+
focusSegment('next');
|
|
191
|
+
} else {
|
|
192
|
+
setEnteredKeys(key);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
setEnteredKeys('');
|
|
121
196
|
}
|
|
122
|
-
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const handleBeforeInput = (e: InputEvent) => {
|
|
201
|
+
if (!isEditable()) return;
|
|
202
|
+
if (isComposing()) return;
|
|
203
|
+
|
|
204
|
+
const seg = segment();
|
|
205
|
+
if (seg.type === 'literal') return;
|
|
206
|
+
|
|
207
|
+
if (e.inputType === 'deleteContentBackward' || e.inputType === 'deleteContentForward') {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
clearCurrentSegment(seg.type);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (e.inputType === 'insertText' && e.data) {
|
|
214
|
+
const normalizedDigit = normalizeDigit(e.data);
|
|
215
|
+
if (!normalizedDigit) return;
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
handleNumericInput(normalizedDigit, seg.type, seg);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const handleCompositionStart = () => {
|
|
222
|
+
if (!isEditable()) return;
|
|
223
|
+
setIsComposing(true);
|
|
224
|
+
setEnteredKeys('');
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const handleCompositionEnd = (e: CompositionEvent) => {
|
|
228
|
+
if (!isEditable()) return;
|
|
229
|
+
setIsComposing(false);
|
|
230
|
+
|
|
231
|
+
const seg = segment();
|
|
232
|
+
if (seg.type === 'literal') return;
|
|
233
|
+
|
|
234
|
+
const digits = extractNormalizedDigits(e.data ?? '');
|
|
235
|
+
if (digits.length === 0) return;
|
|
236
|
+
|
|
237
|
+
for (const digit of digits) {
|
|
238
|
+
handleNumericInput(digit, seg.type, seg);
|
|
123
239
|
}
|
|
124
240
|
};
|
|
125
241
|
|
|
@@ -150,7 +266,7 @@ export function createDateSegment<T extends DateFieldState>(
|
|
|
150
266
|
return {
|
|
151
267
|
role: 'spinbutton',
|
|
152
268
|
tabIndex: isEditable() ? 0 : -1,
|
|
153
|
-
'aria-label': getSegmentLabel(type),
|
|
269
|
+
'aria-label': getSegmentLabel(type, locale().locale),
|
|
154
270
|
'aria-valuenow': seg.value,
|
|
155
271
|
'aria-valuemin': seg.minValue,
|
|
156
272
|
'aria-valuemax': seg.maxValue,
|
|
@@ -167,6 +283,9 @@ export function createDateSegment<T extends DateFieldState>(
|
|
|
167
283
|
onKeyDown: handleKeyDown,
|
|
168
284
|
onFocus: handleFocus,
|
|
169
285
|
onBlur: handleBlur,
|
|
286
|
+
onBeforeInput: handleBeforeInput,
|
|
287
|
+
onCompositionStart: handleCompositionStart,
|
|
288
|
+
onCompositionEnd: handleCompositionEnd,
|
|
170
289
|
onMouseDown: (e: MouseEvent) => {
|
|
171
290
|
// Prevent cursor positioning in the middle of the segment
|
|
172
291
|
e.preventDefault();
|
|
@@ -203,27 +322,62 @@ export function createDateSegment<T extends DateFieldState>(
|
|
|
203
322
|
// HELPER FUNCTIONS
|
|
204
323
|
// ============================================
|
|
205
324
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
325
|
+
const SEGMENT_LABELS: Record<string, Record<Exclude<DateSegmentType, 'literal'>, string>> = {
|
|
326
|
+
en: {
|
|
327
|
+
year: 'Year',
|
|
328
|
+
month: 'Month',
|
|
329
|
+
day: 'Day',
|
|
330
|
+
hour: 'Hour',
|
|
331
|
+
minute: 'Minute',
|
|
332
|
+
second: 'Second',
|
|
333
|
+
dayPeriod: 'AM/PM',
|
|
334
|
+
era: 'Era',
|
|
335
|
+
timeZoneName: 'Time zone',
|
|
336
|
+
},
|
|
337
|
+
es: {
|
|
338
|
+
year: 'Año',
|
|
339
|
+
month: 'Mes',
|
|
340
|
+
day: 'Día',
|
|
341
|
+
hour: 'Hora',
|
|
342
|
+
minute: 'Minuto',
|
|
343
|
+
second: 'Segundo',
|
|
344
|
+
dayPeriod: 'AM/PM',
|
|
345
|
+
era: 'Era',
|
|
346
|
+
timeZoneName: 'Zona horaria',
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
function getSegmentLabel(type: DateSegmentType, locale: string): string {
|
|
351
|
+
if (type === 'literal') return '';
|
|
352
|
+
|
|
353
|
+
const language = locale.toLowerCase().split('-')[0] ?? 'en';
|
|
354
|
+
const labels = SEGMENT_LABELS[language] ?? SEGMENT_LABELS.en;
|
|
355
|
+
return labels[type];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function normalizeDigit(input: string): string | null {
|
|
359
|
+
if (/^[0-9]$/.test(input)) {
|
|
360
|
+
return input;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const codePoint = input.codePointAt(0);
|
|
364
|
+
if (codePoint == null) return null;
|
|
365
|
+
|
|
366
|
+
// Full-width digits 0-9
|
|
367
|
+
if (codePoint >= 0xff10 && codePoint <= 0xff19) {
|
|
368
|
+
return String(codePoint - 0xff10);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function extractNormalizedDigits(value: string): string[] {
|
|
375
|
+
const digits: string[] = [];
|
|
376
|
+
for (const char of value) {
|
|
377
|
+
const normalized = normalizeDigit(char);
|
|
378
|
+
if (normalized) {
|
|
379
|
+
digits.push(normalized);
|
|
380
|
+
}
|
|
228
381
|
}
|
|
382
|
+
return digits;
|
|
229
383
|
}
|