@proyecto-viviana/solidaria 0.4.0 → 0.4.1
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/dist/_chunk/DragManager.js +627 -0
- package/dist/_chunk/DragManager.js.map +1 -0
- package/dist/_chunk/DragManager.jsx +627 -0
- package/dist/_chunk/DragManager.jsx.map +1 -0
- package/dist/_chunk/FocusScope.js +400 -0
- package/dist/_chunk/FocusScope.js.map +1 -0
- package/dist/_chunk/FocusScope.jsx +375 -0
- package/dist/_chunk/FocusScope.jsx.map +1 -0
- package/dist/_chunk/actiongroup.js +13 -79
- package/dist/_chunk/actiongroup.js.map +1 -1
- package/dist/_chunk/actiongroup.jsx +13 -79
- package/dist/_chunk/actiongroup.jsx.map +1 -1
- package/dist/_chunk/{live-announcer.js → announce.js} +1 -1
- package/dist/_chunk/announce.js.map +1 -0
- package/dist/_chunk/{live-announcer.jsx → announce.jsx} +1 -1
- package/dist/_chunk/announce.jsx.map +1 -0
- package/dist/_chunk/autocomplete.js +56 -8
- package/dist/_chunk/autocomplete.js.map +1 -1
- package/dist/_chunk/autocomplete.jsx +56 -8
- package/dist/_chunk/autocomplete.jsx.map +1 -1
- package/dist/_chunk/breadcrumbs.js +6 -4
- package/dist/_chunk/breadcrumbs.js.map +1 -1
- package/dist/_chunk/breadcrumbs.jsx +6 -4
- package/dist/_chunk/breadcrumbs.jsx.map +1 -1
- package/dist/_chunk/button.js +8 -9
- package/dist/_chunk/button.js.map +1 -1
- package/dist/_chunk/button.jsx +8 -9
- package/dist/_chunk/button.jsx.map +1 -1
- package/dist/_chunk/calendar.js +27 -26
- package/dist/_chunk/calendar.js.map +1 -1
- package/dist/_chunk/calendar.jsx +27 -26
- package/dist/_chunk/calendar.jsx.map +1 -1
- package/dist/_chunk/checkbox.js +5 -4
- package/dist/_chunk/checkbox.js.map +1 -1
- package/dist/_chunk/checkbox.jsx +5 -4
- package/dist/_chunk/checkbox.jsx.map +1 -1
- package/dist/_chunk/color.js +302 -18
- package/dist/_chunk/color.js.map +1 -1
- package/dist/_chunk/color.jsx +302 -18
- package/dist/_chunk/color.jsx.map +1 -1
- package/dist/_chunk/combobox.js +638 -12
- package/dist/_chunk/combobox.js.map +1 -1
- package/dist/_chunk/combobox.jsx +638 -12
- package/dist/_chunk/combobox.jsx.map +1 -1
- package/dist/_chunk/constants.js +19 -0
- package/dist/_chunk/constants.js.map +1 -0
- package/dist/_chunk/constants.jsx +19 -0
- package/dist/_chunk/constants.jsx.map +1 -0
- package/dist/_chunk/createCollator.js +3 -171
- package/dist/_chunk/createCollator.js.map +1 -1
- package/dist/_chunk/createCollator.jsx +3 -165
- package/dist/_chunk/createCollator.jsx.map +1 -1
- package/dist/_chunk/createField.js +2 -1
- package/dist/_chunk/createField.js.map +1 -1
- package/dist/_chunk/createField.jsx +2 -1
- package/dist/_chunk/createField.jsx.map +1 -1
- package/dist/_chunk/createFocus.js +12 -8
- package/dist/_chunk/createFocus.js.map +1 -1
- package/dist/_chunk/createFocus.jsx +12 -8
- package/dist/_chunk/createFocus.jsx.map +1 -1
- package/dist/_chunk/createFocusWithin.js +12 -13
- package/dist/_chunk/createFocusWithin.js.map +1 -1
- package/dist/_chunk/createFocusWithin.jsx +12 -13
- package/dist/_chunk/createFocusWithin.jsx.map +1 -1
- package/dist/_chunk/createFocusable.js +3 -3
- package/dist/_chunk/createFocusable.js.map +1 -1
- package/dist/_chunk/createFocusable.jsx +3 -3
- package/dist/_chunk/createFocusable.jsx.map +1 -1
- package/dist/_chunk/{form.js → createFormReset.js} +1 -1
- package/dist/_chunk/createFormReset.js.map +1 -0
- package/dist/_chunk/{form.jsx → createFormReset.jsx} +1 -1
- package/dist/_chunk/createFormReset.jsx.map +1 -0
- package/dist/_chunk/createHover.js +2 -1
- package/dist/_chunk/createHover.js.map +1 -1
- package/dist/_chunk/createHover.jsx +2 -1
- package/dist/_chunk/createHover.jsx.map +1 -1
- package/dist/_chunk/createInteractionModality.js +2 -3
- package/dist/_chunk/createInteractionModality.js.map +1 -1
- package/dist/_chunk/createInteractionModality.jsx +2 -3
- package/dist/_chunk/createInteractionModality.jsx.map +1 -1
- package/dist/_chunk/createLabel.js +2 -1
- package/dist/_chunk/createLabel.js.map +1 -1
- package/dist/_chunk/createLabel.jsx +2 -1
- package/dist/_chunk/createLabel.jsx.map +1 -1
- package/dist/_chunk/createLabels.js +3 -2
- package/dist/_chunk/createLabels.js.map +1 -1
- package/dist/_chunk/createLabels.jsx +3 -2
- package/dist/_chunk/createLabels.jsx.map +1 -1
- package/dist/_chunk/createLandmark.js +1 -1
- package/dist/_chunk/createLandmark.jsx +1 -1
- package/dist/_chunk/createProgressBar.js +2 -1
- package/dist/_chunk/createProgressBar.js.map +1 -1
- package/dist/_chunk/createProgressBar.jsx +2 -1
- package/dist/_chunk/createProgressBar.jsx.map +1 -1
- package/dist/_chunk/createSelectableItem.js +7 -3
- package/dist/_chunk/createSelectableItem.js.map +1 -1
- package/dist/_chunk/createSelectableItem.jsx +7 -3
- package/dist/_chunk/createSelectableItem.jsx.map +1 -1
- package/dist/_chunk/createStringFormatter.js +61 -0
- package/dist/_chunk/createStringFormatter.js.map +1 -0
- package/dist/_chunk/createStringFormatter.jsx +61 -0
- package/dist/_chunk/createStringFormatter.jsx.map +1 -0
- package/dist/_chunk/createToggle.js +5 -4
- package/dist/_chunk/createToggle.js.map +1 -1
- package/dist/_chunk/createToggle.jsx +5 -4
- package/dist/_chunk/createToggle.jsx.map +1 -1
- package/dist/_chunk/datepicker.js +1413 -1100
- package/dist/_chunk/datepicker.js.map +1 -1
- package/dist/_chunk/datepicker.jsx +1413 -1100
- package/dist/_chunk/datepicker.jsx.map +1 -1
- package/dist/_chunk/disclosure.js +2 -2
- package/dist/_chunk/disclosure.js.map +1 -1
- package/dist/_chunk/disclosure.jsx +2 -2
- package/dist/_chunk/disclosure.jsx.map +1 -1
- package/dist/_chunk/dnd.js +1586 -495
- package/dist/_chunk/dnd.js.map +1 -1
- package/dist/_chunk/dnd.jsx +1586 -495
- package/dist/_chunk/dnd.jsx.map +1 -1
- package/dist/_chunk/env.js +1 -19
- package/dist/_chunk/env.js.map +1 -1
- package/dist/_chunk/env.jsx +1 -19
- package/dist/_chunk/env.jsx.map +1 -1
- package/dist/_chunk/focus.js +1 -1
- package/dist/_chunk/focus.js.map +1 -1
- package/dist/_chunk/focus.jsx +1 -1
- package/dist/_chunk/focus.jsx.map +1 -1
- package/dist/_chunk/focus2.js +54 -318
- package/dist/_chunk/focus2.js.map +1 -1
- package/dist/_chunk/focus2.jsx +53 -292
- package/dist/_chunk/focus2.jsx.map +1 -1
- package/dist/_chunk/grid.js +1 -1
- package/dist/_chunk/grid.jsx +1 -1
- package/dist/_chunk/gridlist.js +14 -9
- package/dist/_chunk/gridlist.js.map +1 -1
- package/dist/_chunk/gridlist.jsx +14 -9
- package/dist/_chunk/gridlist.jsx.map +1 -1
- package/dist/_chunk/i18n.js +3 -58
- package/dist/_chunk/i18n.js.map +1 -1
- package/dist/_chunk/i18n.jsx +3 -58
- package/dist/_chunk/i18n.jsx.map +1 -1
- package/dist/_chunk/interactions.js +99 -9
- package/dist/_chunk/interactions.js.map +1 -1
- package/dist/_chunk/interactions.jsx +97 -9
- package/dist/_chunk/interactions.jsx.map +1 -1
- package/dist/_chunk/link.js +2 -1
- package/dist/_chunk/link.js.map +1 -1
- package/dist/_chunk/link.jsx +2 -1
- package/dist/_chunk/link.jsx.map +1 -1
- package/dist/_chunk/listbox.js +42 -10
- package/dist/_chunk/listbox.js.map +1 -1
- package/dist/_chunk/listbox.jsx +42 -10
- package/dist/_chunk/listbox.jsx.map +1 -1
- package/dist/_chunk/locale.js +173 -0
- package/dist/_chunk/locale.js.map +1 -0
- package/dist/_chunk/locale.jsx +167 -0
- package/dist/_chunk/locale.jsx.map +1 -0
- package/dist/_chunk/menu.js +5 -4
- package/dist/_chunk/menu.js.map +1 -1
- package/dist/_chunk/menu.jsx +5 -4
- package/dist/_chunk/menu.jsx.map +1 -1
- package/dist/_chunk/numberfield.js +3 -2
- package/dist/_chunk/numberfield.js.map +1 -1
- package/dist/_chunk/numberfield.jsx +3 -2
- package/dist/_chunk/numberfield.jsx.map +1 -1
- package/dist/_chunk/overlays.js +1 -1
- package/dist/_chunk/overlays.jsx +1 -1
- package/dist/_chunk/popover.js +3 -3
- package/dist/_chunk/popover.js.map +1 -1
- package/dist/_chunk/popover.jsx +3 -3
- package/dist/_chunk/popover.jsx.map +1 -1
- package/dist/_chunk/radio.js +7 -6
- package/dist/_chunk/radio.js.map +1 -1
- package/dist/_chunk/radio.jsx +7 -6
- package/dist/_chunk/radio.jsx.map +1 -1
- package/dist/_chunk/reactivity.js +21 -0
- package/dist/_chunk/reactivity.js.map +1 -0
- package/dist/_chunk/reactivity.jsx +21 -0
- package/dist/_chunk/reactivity.jsx.map +1 -0
- package/dist/_chunk/searchfield.js +2 -1
- package/dist/_chunk/searchfield.js.map +1 -1
- package/dist/_chunk/searchfield.jsx +2 -1
- package/dist/_chunk/searchfield.jsx.map +1 -1
- package/dist/_chunk/select.js +8 -5
- package/dist/_chunk/select.js.map +1 -1
- package/dist/_chunk/select.jsx +8 -5
- package/dist/_chunk/select.jsx.map +1 -1
- package/dist/_chunk/selection.js +8 -21
- package/dist/_chunk/selection.js.map +1 -1
- package/dist/_chunk/selection.jsx +8 -21
- package/dist/_chunk/selection.jsx.map +1 -1
- package/dist/_chunk/separator.js +1 -1
- package/dist/_chunk/separator.jsx +1 -1
- package/dist/_chunk/slider.js +5 -4
- package/dist/_chunk/slider.js.map +1 -1
- package/dist/_chunk/slider.jsx +5 -4
- package/dist/_chunk/slider.jsx.map +1 -1
- package/dist/_chunk/spinbutton.js +153 -0
- package/dist/_chunk/spinbutton.js.map +1 -0
- package/dist/_chunk/spinbutton.jsx +153 -0
- package/dist/_chunk/spinbutton.jsx.map +1 -0
- package/dist/_chunk/table.js +45 -8
- package/dist/_chunk/table.js.map +1 -1
- package/dist/_chunk/table.jsx +45 -8
- package/dist/_chunk/table.jsx.map +1 -1
- package/dist/_chunk/tabs.js +11 -8
- package/dist/_chunk/tabs.js.map +1 -1
- package/dist/_chunk/tabs.jsx +11 -8
- package/dist/_chunk/tabs.jsx.map +1 -1
- package/dist/_chunk/tag.js +54 -18
- package/dist/_chunk/tag.js.map +1 -1
- package/dist/_chunk/tag.jsx +54 -18
- package/dist/_chunk/tag.jsx.map +1 -1
- package/dist/_chunk/textfield.js +2 -1
- package/dist/_chunk/textfield.js.map +1 -1
- package/dist/_chunk/textfield.jsx +2 -1
- package/dist/_chunk/textfield.jsx.map +1 -1
- package/dist/_chunk/toolbar.js +4 -36
- package/dist/_chunk/toolbar.js.map +1 -1
- package/dist/_chunk/toolbar.jsx +4 -36
- package/dist/_chunk/toolbar.jsx.map +1 -1
- package/dist/_chunk/tooltip.js +2 -2
- package/dist/_chunk/tooltip.js.map +1 -1
- package/dist/_chunk/tooltip.jsx +2 -2
- package/dist/_chunk/tooltip.jsx.map +1 -1
- package/dist/_chunk/tree.js +42 -14
- package/dist/_chunk/tree.js.map +1 -1
- package/dist/_chunk/tree.jsx +42 -14
- package/dist/_chunk/tree.jsx.map +1 -1
- package/dist/_chunk/utils.js +59 -19
- package/dist/_chunk/utils.js.map +1 -1
- package/dist/_chunk/utils.jsx +59 -19
- package/dist/_chunk/utils.jsx.map +1 -1
- package/dist/_chunk/visually-hidden.js +3 -2
- package/dist/_chunk/visually-hidden.js.map +1 -1
- package/dist/_chunk/visually-hidden.jsx +3 -2
- package/dist/_chunk/visually-hidden.jsx.map +1 -1
- package/dist/actiongroup/createActionGroup.d.ts.map +1 -1
- package/dist/autocomplete/createAutocomplete.d.ts +10 -7
- package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
- package/dist/breadcrumbs/createBreadcrumbs.d.ts.map +1 -1
- package/dist/button/createToggleButtonGroup.d.ts.map +1 -1
- package/dist/calendar/createCalendar.d.ts.map +1 -1
- package/dist/calendar/createCalendarCell.d.ts.map +1 -1
- package/dist/calendar/createCalendarGrid.d.ts.map +1 -1
- package/dist/calendar/createRangeCalendar.d.ts.map +1 -1
- package/dist/calendar/createRangeCalendarCell.d.ts.map +1 -1
- package/dist/calendar/intl/index.d.ts +1 -1
- package/dist/calendar/intl/index.d.ts.map +1 -1
- package/dist/collections/index.js +1 -1
- package/dist/collections/index.jsx +1 -1
- package/dist/color/createColorArea.d.ts.map +1 -1
- package/dist/color/createColorField.d.ts.map +1 -1
- package/dist/color/createColorSlider.d.ts.map +1 -1
- package/dist/color/createColorSwatch.d.ts.map +1 -1
- package/dist/color/createColorWheel.d.ts.map +1 -1
- package/dist/color/intl/index.d.ts +30 -0
- package/dist/color/intl/index.d.ts.map +1 -0
- package/dist/combobox/createComboBox.d.ts.map +1 -1
- package/dist/combobox/intl/generated-locales.d.ts +16 -0
- package/dist/combobox/intl/generated-locales.d.ts.map +1 -0
- package/dist/combobox/intl/index.d.ts +2 -2
- package/dist/combobox/intl/index.d.ts.map +1 -1
- package/dist/datepicker/createDateField.d.ts +45 -23
- package/dist/datepicker/createDateField.d.ts.map +1 -1
- package/dist/datepicker/createDatePicker.d.ts +34 -6
- package/dist/datepicker/createDatePicker.d.ts.map +1 -1
- package/dist/datepicker/createDatePickerGroup.d.ts +13 -13
- package/dist/datepicker/createDatePickerGroup.d.ts.map +1 -1
- package/dist/datepicker/createDateRangePicker.d.ts +55 -7
- package/dist/datepicker/createDateRangePicker.d.ts.map +1 -1
- package/dist/datepicker/createDateSegment.d.ts +6 -15
- package/dist/datepicker/createDateSegment.d.ts.map +1 -1
- package/dist/datepicker/createDisplayNames.d.ts +17 -0
- package/dist/datepicker/createDisplayNames.d.ts.map +1 -0
- package/dist/datepicker/createTimeField.d.ts +12 -49
- package/dist/datepicker/createTimeField.d.ts.map +1 -1
- package/dist/datepicker/index.d.ts +3 -3
- package/dist/datepicker/index.d.ts.map +1 -1
- package/dist/datepicker/index.js +2 -2
- package/dist/datepicker/index.jsx +2 -2
- package/dist/datepicker/intl.d.ts +12 -0
- package/dist/datepicker/intl.d.ts.map +1 -0
- package/dist/dnd/DragManager.d.ts +86 -0
- package/dist/dnd/DragManager.d.ts.map +1 -0
- package/dist/dnd/DropTargetKeyboardNavigation.d.ts +42 -0
- package/dist/dnd/DropTargetKeyboardNavigation.d.ts.map +1 -0
- package/dist/dnd/createDrag.d.ts.map +1 -1
- package/dist/dnd/createDraggableCollection.d.ts.map +1 -1
- package/dist/dnd/createDraggableItem.d.ts +12 -7
- package/dist/dnd/createDraggableItem.d.ts.map +1 -1
- package/dist/dnd/createDroppableCollection.d.ts +7 -15
- package/dist/dnd/createDroppableCollection.d.ts.map +1 -1
- package/dist/dnd/index.d.ts +5 -1
- package/dist/dnd/index.d.ts.map +1 -1
- package/dist/dnd/index.js +3 -2
- package/dist/dnd/index.jsx +3 -2
- package/dist/dnd/intl/index.d.ts +39 -0
- package/dist/dnd/intl/index.d.ts.map +1 -0
- package/dist/dnd/utils.d.ts +5 -0
- package/dist/dnd/utils.d.ts.map +1 -1
- package/dist/focus/FocusScope.d.ts +7 -0
- package/dist/focus/FocusScope.d.ts.map +1 -1
- package/dist/focus/index.d.ts +2 -1
- package/dist/focus/index.d.ts.map +1 -1
- package/dist/focus/index.js +3 -2
- package/dist/focus/index.jsx +3 -2
- package/dist/focus/virtualFocus.d.ts +26 -0
- package/dist/focus/virtualFocus.d.ts.map +1 -0
- package/dist/form/index.js +1 -1
- package/dist/form/index.jsx +1 -1
- package/dist/gridlist/createGridList.d.ts.map +1 -1
- package/dist/i18n/index.js +4 -2
- package/dist/i18n/index.jsx +4 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -12
- package/dist/index.jsx +18 -12
- package/dist/interactions/FocusableProvider.d.ts.map +1 -1
- package/dist/interactions/createFocus.d.ts.map +1 -1
- package/dist/interactions/createFocusWithin.d.ts.map +1 -1
- package/dist/interactions/createInteractionModality.d.ts.map +1 -1
- package/dist/interactions/index.js +1 -2
- package/dist/interactions/index.jsx +1 -2
- package/dist/label/createLabels.d.ts.map +1 -1
- package/dist/listbox/createListBox.d.ts.map +1 -1
- package/dist/listbox/createOption.d.ts +1 -1
- package/dist/listbox/createOption.d.ts.map +1 -1
- package/dist/live-announcer/index.js +1 -1
- package/dist/live-announcer/index.jsx +1 -1
- package/dist/select/createSelect.d.ts +2 -0
- package/dist/select/createSelect.d.ts.map +1 -1
- package/dist/selection/createSelectableItem.d.ts.map +1 -1
- package/dist/selection/index.js +2 -1
- package/dist/selection/index.jsx +2 -1
- package/dist/spinbutton/createSpinButton.d.ts +41 -0
- package/dist/spinbutton/createSpinButton.d.ts.map +1 -0
- package/dist/spinbutton/index.d.ts +3 -0
- package/dist/spinbutton/index.d.ts.map +1 -0
- package/dist/spinbutton/index.js +2 -0
- package/dist/spinbutton/index.jsx +2 -0
- package/dist/spinbutton/intl.d.ts +5 -0
- package/dist/spinbutton/intl.d.ts.map +1 -0
- package/dist/table/createTable.d.ts.map +1 -1
- package/dist/table/createTableColumnHeader.d.ts.map +1 -1
- package/dist/table/createTableRow.d.ts.map +1 -1
- package/dist/table/createTableSelectionCheckbox.d.ts.map +1 -1
- package/dist/table/types.d.ts +8 -0
- package/dist/table/types.d.ts.map +1 -1
- package/dist/table/utils.d.ts +15 -0
- package/dist/table/utils.d.ts.map +1 -0
- package/dist/tabs/createTabs.d.ts +1 -0
- package/dist/tabs/createTabs.d.ts.map +1 -1
- package/dist/tag/createTag.d.ts.map +1 -1
- package/dist/tag/createTagGroup.d.ts +7 -0
- package/dist/tag/createTagGroup.d.ts.map +1 -1
- package/dist/toolbar/createToolbar.d.ts.map +1 -1
- package/dist/tree/createTree.d.ts.map +1 -1
- package/dist/tree/createTreeSelectionCheckbox.d.ts.map +1 -1
- package/dist/utils/animation.d.ts.map +1 -1
- package/dist/utils/dom.d.ts +1 -0
- package/dist/utils/dom.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -3
- package/dist/utils/index.jsx +4 -3
- package/package.json +2 -2
- package/src/actiongroup/createActionGroup.ts +26 -109
- package/src/autocomplete/createAutocomplete.ts +136 -15
- package/src/breadcrumbs/createBreadcrumbs.ts +12 -4
- package/src/button/createToggleButtonGroup.ts +19 -14
- package/src/calendar/createCalendar.ts +3 -4
- package/src/calendar/createCalendarCell.ts +17 -3
- package/src/calendar/createCalendarGrid.ts +5 -4
- package/src/calendar/createRangeCalendar.ts +27 -17
- package/src/calendar/createRangeCalendarCell.ts +17 -3
- package/src/calendar/intl/index.ts +8 -0
- package/src/color/createColorArea.ts +17 -10
- package/src/color/createColorField.ts +8 -1
- package/src/color/createColorSlider.ts +1 -2
- package/src/color/createColorSwatch.ts +4 -2
- package/src/color/createColorWheel.ts +0 -6
- package/src/color/intl/index.ts +316 -0
- package/src/combobox/createComboBox.ts +35 -4
- package/src/combobox/intl/generated-locales.ts +635 -0
- package/src/combobox/intl/index.ts +8 -2
- package/src/datepicker/createDateField.ts +228 -213
- package/src/datepicker/createDatePicker.ts +157 -146
- package/src/datepicker/createDatePickerGroup.ts +88 -82
- package/src/datepicker/createDateRangePicker.ts +230 -179
- package/src/datepicker/createDateSegment.ts +386 -389
- package/src/datepicker/createDisplayNames.ts +45 -0
- package/src/datepicker/createTimeField.ts +36 -129
- package/src/datepicker/index.ts +13 -14
- package/src/datepicker/intl.ts +633 -0
- package/src/dnd/DragManager.ts +693 -0
- package/src/dnd/DropTargetKeyboardNavigation.ts +337 -0
- package/src/dnd/createDrag.ts +31 -5
- package/src/dnd/createDraggableCollection.ts +33 -15
- package/src/dnd/createDraggableItem.ts +92 -5
- package/src/dnd/createDroppableCollection.ts +487 -373
- package/src/dnd/index.ts +11 -0
- package/src/dnd/intl/ar-AE.json +29 -0
- package/src/dnd/intl/bg-BG.json +29 -0
- package/src/dnd/intl/cs-CZ.json +29 -0
- package/src/dnd/intl/da-DK.json +29 -0
- package/src/dnd/intl/de-DE.json +29 -0
- package/src/dnd/intl/el-GR.json +29 -0
- package/src/dnd/intl/en-US.json +29 -0
- package/src/dnd/intl/es-ES.json +29 -0
- package/src/dnd/intl/et-EE.json +29 -0
- package/src/dnd/intl/fi-FI.json +29 -0
- package/src/dnd/intl/fr-FR.json +29 -0
- package/src/dnd/intl/he-IL.json +29 -0
- package/src/dnd/intl/hr-HR.json +29 -0
- package/src/dnd/intl/hu-HU.json +29 -0
- package/src/dnd/intl/index.ts +139 -0
- package/src/dnd/intl/it-IT.json +29 -0
- package/src/dnd/intl/ja-JP.json +29 -0
- package/src/dnd/intl/ko-KR.json +29 -0
- package/src/dnd/intl/lt-LT.json +29 -0
- package/src/dnd/intl/lv-LV.json +29 -0
- package/src/dnd/intl/nb-NO.json +29 -0
- package/src/dnd/intl/nl-NL.json +29 -0
- package/src/dnd/intl/pl-PL.json +29 -0
- package/src/dnd/intl/pt-BR.json +29 -0
- package/src/dnd/intl/pt-PT.json +29 -0
- package/src/dnd/intl/ro-RO.json +29 -0
- package/src/dnd/intl/ru-RU.json +29 -0
- package/src/dnd/intl/sk-SK.json +29 -0
- package/src/dnd/intl/sl-SI.json +29 -0
- package/src/dnd/intl/sr-SP.json +29 -0
- package/src/dnd/intl/sv-SE.json +29 -0
- package/src/dnd/intl/tr-TR.json +29 -0
- package/src/dnd/intl/uk-UA.json +29 -0
- package/src/dnd/intl/zh-CN.json +29 -0
- package/src/dnd/intl/zh-TW.json +29 -0
- package/src/dnd/utils.ts +36 -0
- package/src/focus/FocusScope.tsx +120 -12
- package/src/focus/index.ts +8 -0
- package/src/focus/virtualFocus.ts +55 -0
- package/src/gridlist/createGridList.ts +52 -16
- package/src/index.ts +11 -3
- package/src/interactions/FocusableProvider.tsx +11 -6
- package/src/interactions/createFocus.ts +26 -21
- package/src/interactions/createFocusWithin.ts +23 -24
- package/src/interactions/createInteractionModality.ts +5 -3
- package/src/label/createLabels.ts +7 -4
- package/src/listbox/createListBox.ts +74 -2
- package/src/listbox/createOption.ts +16 -1
- package/src/select/createSelect.ts +23 -2
- package/src/selection/createSelectableItem.ts +14 -0
- package/src/spinbutton/createSpinButton.ts +163 -0
- package/src/spinbutton/index.ts +10 -0
- package/src/spinbutton/intl.ts +115 -0
- package/src/table/createTable.ts +26 -1
- package/src/table/createTableColumnHeader.ts +10 -0
- package/src/table/createTableRow.ts +10 -0
- package/src/table/createTableSelectionCheckbox.ts +24 -1
- package/src/table/types.ts +8 -0
- package/src/table/utils.ts +41 -0
- package/src/tabs/createTabs.ts +53 -12
- package/src/tag/createTag.ts +21 -32
- package/src/tag/createTagGroup.ts +92 -0
- package/src/toolbar/createToolbar.ts +6 -58
- package/src/tree/createTree.ts +69 -14
- package/src/tree/createTreeSelectionCheckbox.ts +14 -1
- package/src/utils/animation.ts +119 -27
- package/src/utils/dom.ts +3 -2
- package/src/utils/index.ts +2 -0
- package/dist/_chunk/createLongPress.js +0 -91
- package/dist/_chunk/createLongPress.js.map +0 -1
- package/dist/_chunk/createLongPress.jsx +0 -91
- package/dist/_chunk/createLongPress.jsx.map +0 -1
- package/dist/_chunk/form.js.map +0 -1
- package/dist/_chunk/form.jsx.map +0 -1
- package/dist/_chunk/live-announcer.js.map +0 -1
- package/dist/_chunk/live-announcer.jsx.map +0 -1
- package/dist/datepicker/createTimeSegment.d.ts +0 -29
- package/dist/datepicker/createTimeSegment.d.ts.map +0 -1
- package/src/datepicker/createTimeSegment.ts +0 -352
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as mergeProps$1 } from "./env.jsx";
|
|
2
2
|
import { t as filterDOMProps } from "./filterDOMProps.jsx";
|
|
3
3
|
import { a as getEventTarget, p as isFocusable, t as focusSafely, y as nodeContains } from "./focus.jsx";
|
|
4
4
|
import { i as createButton } from "./button.jsx";
|
|
5
|
-
import {
|
|
5
|
+
import { i as useLocale } from "./locale.jsx";
|
|
6
6
|
import { onCleanup } from "solid-js";
|
|
7
7
|
//#region src/actiongroup/createActionGroup.ts
|
|
8
8
|
const actionGroupData = /* @__PURE__ */ new WeakMap();
|
|
@@ -47,85 +47,36 @@ function createActionGroup(props, state, _ref) {
|
|
|
47
47
|
const focusRelative = (root, direction) => {
|
|
48
48
|
const focusables = getFocusableItems(root);
|
|
49
49
|
if (focusables.length === 0) return null;
|
|
50
|
-
if (direction === "first") {
|
|
51
|
-
const first = focusables[0];
|
|
52
|
-
focusSafely(first);
|
|
53
|
-
return first;
|
|
54
|
-
}
|
|
55
|
-
if (direction === "last") {
|
|
56
|
-
const last = focusables[focusables.length - 1];
|
|
57
|
-
focusSafely(last);
|
|
58
|
-
return last;
|
|
59
|
-
}
|
|
60
50
|
const active = root.ownerDocument.activeElement;
|
|
61
51
|
const currentIndex = active ? focusables.indexOf(active) : -1;
|
|
62
52
|
const next = focusables[currentIndex === -1 ? direction === "next" ? 0 : focusables.length - 1 : (currentIndex + (direction === "next" ? 1 : -1) + focusables.length) % focusables.length];
|
|
63
53
|
focusSafely(next);
|
|
64
54
|
return next;
|
|
65
55
|
};
|
|
66
|
-
const resolveKeyFromElement = (element) => {
|
|
67
|
-
if (!element) return null;
|
|
68
|
-
const keyedElement = element.closest("[data-key]");
|
|
69
|
-
if (!(keyedElement instanceof HTMLElement)) return null;
|
|
70
|
-
const rawKey = keyedElement.getAttribute("data-key");
|
|
71
|
-
if (!rawKey) return null;
|
|
72
|
-
for (const item of state.collection()) if (String(item.key) === rawKey) return item.key;
|
|
73
|
-
return null;
|
|
74
|
-
};
|
|
75
|
-
const handleFocusMove = (movedTo) => {
|
|
76
|
-
const key = resolveKeyFromElement(movedTo);
|
|
77
|
-
if (key == null) return;
|
|
78
|
-
state.setFocusedKey(key);
|
|
79
|
-
if (state.selectionMode() === "single") state.replaceSelection(key);
|
|
80
|
-
};
|
|
81
56
|
const onKeyDown = (e) => {
|
|
82
57
|
const root = groupRef;
|
|
83
58
|
if (!root || isActionGroupDisabled(props, state)) return;
|
|
84
59
|
if (!nodeContains(e.currentTarget, getEventTarget(e))) return;
|
|
85
|
-
const
|
|
86
|
-
const
|
|
60
|
+
const orientation = props.orientation ?? "horizontal";
|
|
61
|
+
const flipDirection = locale().direction === "rtl" && orientation === "horizontal";
|
|
87
62
|
switch (e.key) {
|
|
88
63
|
case "ArrowRight":
|
|
89
64
|
case "ArrowDown":
|
|
90
|
-
if (e.key === "ArrowRight" && isHorizontal && isRTL) {
|
|
91
|
-
e.preventDefault();
|
|
92
|
-
e.stopPropagation();
|
|
93
|
-
handleFocusMove(focusRelative(root, "previous"));
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (e.key === "ArrowRight" && isHorizontal || e.key === "ArrowDown" && !isHorizontal) {
|
|
97
|
-
e.preventDefault();
|
|
98
|
-
e.stopPropagation();
|
|
99
|
-
handleFocusMove(focusRelative(root, "next"));
|
|
100
|
-
}
|
|
101
|
-
return;
|
|
102
|
-
case "ArrowLeft":
|
|
103
|
-
case "ArrowUp":
|
|
104
|
-
if (e.key === "ArrowLeft" && isHorizontal && isRTL) {
|
|
105
|
-
e.preventDefault();
|
|
106
|
-
e.stopPropagation();
|
|
107
|
-
handleFocusMove(focusRelative(root, "next"));
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (e.key === "ArrowLeft" && isHorizontal || e.key === "ArrowUp" && !isHorizontal) {
|
|
111
|
-
e.preventDefault();
|
|
112
|
-
e.stopPropagation();
|
|
113
|
-
handleFocusMove(focusRelative(root, "previous"));
|
|
114
|
-
}
|
|
115
|
-
return;
|
|
116
|
-
case "Home":
|
|
117
65
|
e.preventDefault();
|
|
118
66
|
e.stopPropagation();
|
|
119
|
-
|
|
67
|
+
if (e.key === "ArrowRight" && flipDirection) focusRelative(root, "previous");
|
|
68
|
+
else focusRelative(root, "next");
|
|
120
69
|
return;
|
|
121
|
-
case "
|
|
70
|
+
case "ArrowLeft":
|
|
71
|
+
case "ArrowUp":
|
|
122
72
|
e.preventDefault();
|
|
123
73
|
e.stopPropagation();
|
|
124
|
-
|
|
74
|
+
if (e.key === "ArrowLeft" && flipDirection) focusRelative(root, "next");
|
|
75
|
+
else focusRelative(root, "previous");
|
|
125
76
|
return;
|
|
126
77
|
}
|
|
127
78
|
};
|
|
128
|
-
const actionGroupProps = mergeProps(filterDOMProps(props, { labelable: true }), {
|
|
79
|
+
const actionGroupProps = mergeProps$1(filterDOMProps(props, { labelable: true }), {
|
|
129
80
|
ref: (el) => {
|
|
130
81
|
groupRef = el;
|
|
131
82
|
applyRoleAttributes();
|
|
@@ -164,24 +115,10 @@ function createActionGroupItem(props, state) {
|
|
|
164
115
|
}
|
|
165
116
|
});
|
|
166
117
|
const isFocused = () => props.key === state.focusedKey();
|
|
167
|
-
const getFirstEnabledKey = () => {
|
|
168
|
-
const collection = state.collection();
|
|
169
|
-
let key = collection.getFirstKey();
|
|
170
|
-
while (key != null && state.isDisabled(key)) key = collection.getKeyAfter(key);
|
|
171
|
-
return key;
|
|
172
|
-
};
|
|
173
|
-
const getDefaultTabStopKey = () => {
|
|
174
|
-
if (state.selectionMode() !== "none") {
|
|
175
|
-
const selectedKeys = state.selectedKeys();
|
|
176
|
-
if (selectedKeys === "all") return getFirstEnabledKey();
|
|
177
|
-
for (const item of state.collection()) if (!state.isDisabled(item.key) && selectedKeys.has(item.key)) return item.key;
|
|
178
|
-
}
|
|
179
|
-
return getFirstEnabledKey();
|
|
180
|
-
};
|
|
181
118
|
onCleanup(() => {
|
|
182
119
|
if (isFocused()) state.setFocusedKey(null);
|
|
183
120
|
});
|
|
184
|
-
return { buttonProps: mergeProps(button.buttonProps, {
|
|
121
|
+
return { buttonProps: mergeProps$1(button.buttonProps, {
|
|
185
122
|
get role() {
|
|
186
123
|
return ITEM_ROLE_BY_MODE[state.selectionMode()];
|
|
187
124
|
},
|
|
@@ -190,10 +127,7 @@ function createActionGroupItem(props, state) {
|
|
|
190
127
|
return state.isSelected(props.key);
|
|
191
128
|
},
|
|
192
129
|
get tabIndex() {
|
|
193
|
-
|
|
194
|
-
if (isFocused()) return 0;
|
|
195
|
-
if (state.focusedKey() != null) return -1;
|
|
196
|
-
return getDefaultTabStopKey() === props.key ? 0 : -1;
|
|
130
|
+
return isFocused() || state.focusedKey() == null ? 0 : -1;
|
|
197
131
|
},
|
|
198
132
|
"data-key": String(props.key),
|
|
199
133
|
onFocus: () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actiongroup.jsx","names":[],"sources":["../../src/actiongroup/createActionGroup.ts"],"sourcesContent":["import { onCleanup, type JSX, type Accessor } from \"solid-js\";\nimport { createButton } from \"../button\";\nimport {\n filterDOMProps,\n getEventTarget,\n mergeProps,\n nodeContains,\n isFocusable,\n focusSafely,\n} from \"../utils\";\nimport { useLocale } from \"../i18n\";\nimport type { Orientation } from \"../toolbar\";\nimport type { Key, ListState } from \"@proyecto-viviana/solid-stately\";\n\nexport interface AriaActionGroupProps<T = unknown> {\n /** List items (optional, parity with React Aria prop shape). */\n items?: T[];\n /** Whether the whole action group is disabled. */\n isDisabled?: boolean;\n /** Group orientation. */\n orientation?: Orientation;\n /** Accessible label. */\n \"aria-label\"?: string;\n /** Labelled-by id. */\n \"aria-labelledby\"?: string;\n /** Handler called when an item action is triggered. */\n onAction?: (key: Key) => void;\n}\n\nexport interface ActionGroupAria {\n actionGroupProps: JSX.HTMLAttributes<HTMLElement>;\n}\n\nexport interface AriaActionGroupItemProps {\n key: Key;\n}\n\nexport interface ActionGroupItemAria {\n buttonProps: JSX.HTMLAttributes<HTMLElement>;\n}\n\ninterface ActionGroupData {\n onAction?: (key: Key) => void;\n}\n\nconst actionGroupData = new WeakMap<object, ActionGroupData>();\n\nconst GROUP_ROLE_BY_MODE = {\n none: \"toolbar\",\n single: \"radiogroup\",\n multiple: \"toolbar\",\n} as const;\n\nconst ITEM_ROLE_BY_MODE = {\n none: undefined,\n single: \"radio\",\n multiple: \"checkbox\",\n} as const;\n\nfunction isActionGroupDisabled<T>(props: AriaActionGroupProps<T>, state: ListState<T>): boolean {\n if (props.isDisabled) return true;\n const keys = [...state.collection().getKeys()];\n if (keys.length === 0) return true;\n return !keys.some((key) => !state.isDisabled(key));\n}\n\nexport function createActionGroup<T>(\n props: AriaActionGroupProps<T>,\n state: ListState<T>,\n _ref?: Accessor<HTMLElement | null>,\n): ActionGroupAria {\n const locale = useLocale();\n let groupRef: HTMLElement | undefined;\n const applyRoleAttributes = (): void => {\n if (!groupRef) return;\n const selectionMode = state.selectionMode();\n const mappedRole = GROUP_ROLE_BY_MODE[selectionMode];\n const nestedToolbar = Boolean(groupRef.parentElement?.closest('[role=\"toolbar\"]'));\n const role = mappedRole === \"toolbar\" && nestedToolbar ? \"group\" : mappedRole;\n groupRef.setAttribute(\"role\", role);\n if (mappedRole === \"toolbar\" && !nestedToolbar) {\n groupRef.setAttribute(\"aria-orientation\", props.orientation ?? \"horizontal\");\n } else {\n groupRef.removeAttribute(\"aria-orientation\");\n }\n };\n\n const getFocusableItems = (root: HTMLElement): HTMLElement[] => {\n const out: HTMLElement[] = [];\n const pushIfFocusable = (el: Element | null | undefined): void => {\n if (!el || !(el instanceof HTMLElement)) return;\n if (isFocusable(el) && el.getAttribute(\"aria-disabled\") !== \"true\") {\n out.push(el);\n }\n };\n\n pushIfFocusable(root);\n for (const node of root.querySelectorAll(\"*\")) {\n pushIfFocusable(node);\n }\n return out;\n };\n\n const focusRelative = (\n root: HTMLElement,\n direction: \"next\" | \"previous\" | \"first\" | \"last\",\n ): HTMLElement | null => {\n const focusables = getFocusableItems(root);\n if (focusables.length === 0) return null;\n\n if (direction === \"first\") {\n const first = focusables[0];\n focusSafely(first);\n return first;\n }\n\n if (direction === \"last\") {\n const last = focusables[focusables.length - 1];\n focusSafely(last);\n return last;\n }\n\n const active = root.ownerDocument.activeElement as HTMLElement | null;\n const currentIndex = active ? focusables.indexOf(active) : -1;\n const delta = direction === \"next\" ? 1 : -1;\n const nextIndex =\n currentIndex === -1\n ? direction === \"next\"\n ? 0\n : focusables.length - 1\n : (currentIndex + delta + focusables.length) % focusables.length;\n const next = focusables[nextIndex];\n focusSafely(next);\n return next;\n };\n\n const resolveKeyFromElement = (element: HTMLElement | null): Key | null => {\n if (!element) return null;\n const keyedElement = element.closest(\"[data-key]\");\n if (!(keyedElement instanceof HTMLElement)) return null;\n const rawKey = keyedElement.getAttribute(\"data-key\");\n if (!rawKey) return null;\n for (const item of state.collection()) {\n if (String(item.key) === rawKey) {\n return item.key;\n }\n }\n return null;\n };\n\n const handleFocusMove = (movedTo: HTMLElement | null): void => {\n const key = resolveKeyFromElement(movedTo);\n if (key == null) return;\n state.setFocusedKey(key);\n if (state.selectionMode() === \"single\") {\n state.replaceSelection(key);\n }\n };\n\n const onKeyDown: JSX.EventHandler<HTMLElement, KeyboardEvent> = (e) => {\n const root = groupRef;\n if (!root || isActionGroupDisabled(props, state)) return;\n if (!nodeContains(e.currentTarget, getEventTarget(e))) return;\n\n const orientation = props.orientation ?? \"horizontal\";\n const isHorizontal = orientation === \"horizontal\";\n const isRTL = locale().direction === \"rtl\" && isHorizontal;\n\n switch (e.key) {\n case \"ArrowRight\":\n case \"ArrowDown\": {\n if (e.key === \"ArrowRight\" && isHorizontal && isRTL) {\n e.preventDefault();\n e.stopPropagation();\n handleFocusMove(focusRelative(root, \"previous\"));\n return;\n }\n if ((e.key === \"ArrowRight\" && isHorizontal) || (e.key === \"ArrowDown\" && !isHorizontal)) {\n e.preventDefault();\n e.stopPropagation();\n handleFocusMove(focusRelative(root, \"next\"));\n }\n return;\n }\n case \"ArrowLeft\":\n case \"ArrowUp\": {\n if (e.key === \"ArrowLeft\" && isHorizontal && isRTL) {\n e.preventDefault();\n e.stopPropagation();\n handleFocusMove(focusRelative(root, \"next\"));\n return;\n }\n if ((e.key === \"ArrowLeft\" && isHorizontal) || (e.key === \"ArrowUp\" && !isHorizontal)) {\n e.preventDefault();\n e.stopPropagation();\n handleFocusMove(focusRelative(root, \"previous\"));\n }\n return;\n }\n case \"Home\": {\n e.preventDefault();\n e.stopPropagation();\n handleFocusMove(focusRelative(root, \"first\"));\n return;\n }\n case \"End\": {\n e.preventDefault();\n e.stopPropagation();\n handleFocusMove(focusRelative(root, \"last\"));\n return;\n }\n }\n };\n\n const actionGroupProps: JSX.HTMLAttributes<HTMLElement> = mergeProps(\n filterDOMProps(props as Record<string, unknown>, { labelable: true }),\n {\n ref: (el: HTMLElement) => {\n groupRef = el;\n applyRoleAttributes();\n queueMicrotask(() => {\n if (!groupRef) return;\n applyRoleAttributes();\n });\n },\n onKeyDown,\n get \"aria-label\"() {\n return props[\"aria-label\"];\n },\n get \"aria-labelledby\"() {\n return props[\"aria-label\"] ? undefined : props[\"aria-labelledby\"];\n },\n get \"aria-disabled\"() {\n return isActionGroupDisabled(props, state) || undefined;\n },\n },\n );\n\n actionGroupData.set(state, {\n get onAction() {\n return props.onAction;\n },\n });\n\n onCleanup(() => {\n actionGroupData.delete(state);\n });\n\n return { actionGroupProps };\n}\n\nexport function createActionGroupItem<T>(\n props: AriaActionGroupItemProps,\n state: ListState<T>,\n): ActionGroupItemAria {\n const button = createButton({\n elementType: \"button\",\n isDisabled: state.isDisabled(props.key),\n onPress: () => {\n state.setFocusedKey(props.key);\n actionGroupData.get(state)?.onAction?.(props.key);\n if (state.selectionMode() !== \"none\") {\n state.select(props.key);\n }\n },\n });\n\n const isFocused = () => props.key === state.focusedKey();\n const getFirstEnabledKey = (): Key | null => {\n const collection = state.collection();\n let key = collection.getFirstKey();\n while (key != null && state.isDisabled(key)) {\n key = collection.getKeyAfter(key);\n }\n return key;\n };\n\n const getDefaultTabStopKey = (): Key | null => {\n const selectionMode = state.selectionMode();\n if (selectionMode !== \"none\") {\n const selectedKeys = state.selectedKeys();\n if (selectedKeys === \"all\") {\n return getFirstEnabledKey();\n }\n\n for (const item of state.collection()) {\n if (!state.isDisabled(item.key) && selectedKeys.has(item.key)) {\n return item.key;\n }\n }\n }\n\n return getFirstEnabledKey();\n };\n\n onCleanup(() => {\n if (isFocused()) {\n state.setFocusedKey(null);\n }\n });\n\n const buttonProps: JSX.HTMLAttributes<HTMLElement> = mergeProps(button.buttonProps, {\n get role() {\n return ITEM_ROLE_BY_MODE[state.selectionMode()];\n },\n get \"aria-checked\"() {\n const mode = state.selectionMode();\n if (mode === \"none\") return undefined;\n return state.isSelected(props.key);\n },\n get tabIndex() {\n if (state.isDisabled(props.key)) {\n return -1;\n }\n\n if (isFocused()) {\n return 0;\n }\n\n if (state.focusedKey() != null) {\n return -1;\n }\n\n const defaultTabStopKey = getDefaultTabStopKey();\n return defaultTabStopKey === props.key ? 0 : -1;\n },\n \"data-key\": String(props.key),\n onFocus: () => {\n state.setFocusedKey(props.key);\n },\n });\n\n return { buttonProps };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"actiongroup.jsx","names":["mergeProps"],"sources":["../../src/actiongroup/createActionGroup.ts"],"sourcesContent":["import { onCleanup, type JSX, type Accessor } from \"solid-js\";\nimport { createButton } from \"../button\";\nimport {\n filterDOMProps,\n getEventTarget,\n mergeProps,\n nodeContains,\n isFocusable,\n focusSafely,\n} from \"../utils\";\nimport { useLocale } from \"../i18n\";\nimport type { Orientation } from \"../toolbar\";\nimport type { Key, ListState } from \"@proyecto-viviana/solid-stately\";\n\nexport interface AriaActionGroupProps<T = unknown> {\n /** List items (optional, parity with React Aria prop shape). */\n items?: T[];\n /** Whether the whole action group is disabled. */\n isDisabled?: boolean;\n /** Group orientation. */\n orientation?: Orientation;\n /** Accessible label. */\n \"aria-label\"?: string;\n /** Labelled-by id. */\n \"aria-labelledby\"?: string;\n /** Handler called when an item action is triggered. */\n onAction?: (key: Key) => void;\n}\n\nexport interface ActionGroupAria {\n actionGroupProps: JSX.HTMLAttributes<HTMLElement>;\n}\n\nexport interface AriaActionGroupItemProps {\n key: Key;\n}\n\nexport interface ActionGroupItemAria {\n buttonProps: JSX.HTMLAttributes<HTMLElement>;\n}\n\ninterface ActionGroupData {\n onAction?: (key: Key) => void;\n}\n\nconst actionGroupData = new WeakMap<object, ActionGroupData>();\n\nconst GROUP_ROLE_BY_MODE = {\n none: \"toolbar\",\n single: \"radiogroup\",\n multiple: \"toolbar\",\n} as const;\n\nconst ITEM_ROLE_BY_MODE = {\n none: undefined,\n single: \"radio\",\n multiple: \"checkbox\",\n} as const;\n\nfunction isActionGroupDisabled<T>(props: AriaActionGroupProps<T>, state: ListState<T>): boolean {\n if (props.isDisabled) return true;\n const keys = [...state.collection().getKeys()];\n if (keys.length === 0) return true;\n return !keys.some((key) => !state.isDisabled(key));\n}\n\nexport function createActionGroup<T>(\n props: AriaActionGroupProps<T>,\n state: ListState<T>,\n _ref?: Accessor<HTMLElement | null>,\n): ActionGroupAria {\n const locale = useLocale();\n let groupRef: HTMLElement | undefined;\n const applyRoleAttributes = (): void => {\n if (!groupRef) return;\n const selectionMode = state.selectionMode();\n const mappedRole = GROUP_ROLE_BY_MODE[selectionMode];\n const nestedToolbar = Boolean(groupRef.parentElement?.closest('[role=\"toolbar\"]'));\n const role = mappedRole === \"toolbar\" && nestedToolbar ? \"group\" : mappedRole;\n groupRef.setAttribute(\"role\", role);\n if (mappedRole === \"toolbar\" && !nestedToolbar) {\n groupRef.setAttribute(\"aria-orientation\", props.orientation ?? \"horizontal\");\n } else {\n groupRef.removeAttribute(\"aria-orientation\");\n }\n };\n\n const getFocusableItems = (root: HTMLElement): HTMLElement[] => {\n const out: HTMLElement[] = [];\n const pushIfFocusable = (el: Element | null | undefined): void => {\n if (!el || !(el instanceof HTMLElement)) return;\n if (isFocusable(el) && el.getAttribute(\"aria-disabled\") !== \"true\") {\n out.push(el);\n }\n };\n\n pushIfFocusable(root);\n for (const node of root.querySelectorAll(\"*\")) {\n pushIfFocusable(node);\n }\n return out;\n };\n\n const focusRelative = (\n root: HTMLElement,\n direction: \"next\" | \"previous\",\n ): HTMLElement | null => {\n const focusables = getFocusableItems(root);\n if (focusables.length === 0) return null;\n\n const active = root.ownerDocument.activeElement as HTMLElement | null;\n const currentIndex = active ? focusables.indexOf(active) : -1;\n const delta = direction === \"next\" ? 1 : -1;\n const nextIndex =\n currentIndex === -1\n ? direction === \"next\"\n ? 0\n : focusables.length - 1\n : (currentIndex + delta + focusables.length) % focusables.length;\n const next = focusables[nextIndex];\n focusSafely(next);\n return next;\n };\n\n // Mirrors react-aria `useActionGroup.onKeyDown` (3.50.0): only the four arrows\n // are handled and they are ORIENTATION-AGNOSTIC — ArrowRight/ArrowDown always\n // move next, ArrowLeft/ArrowUp always move previous (orientation only drives\n // `aria-orientation`). `flipDirection` swaps ArrowRight/ArrowLeft under RTL for\n // horizontal groups. No Home/End (they fall through to the browser). Focus\n // moves via `focusRelative` → `focusSafely` → the item's own `onFocus`, which\n // sets the focused key — so there is no selection-follows-focus here.\n const onKeyDown: JSX.EventHandler<HTMLElement, KeyboardEvent> = (e) => {\n const root = groupRef;\n if (!root || isActionGroupDisabled(props, state)) return;\n if (!nodeContains(e.currentTarget, getEventTarget(e))) return;\n\n const orientation = props.orientation ?? \"horizontal\";\n const flipDirection = locale().direction === \"rtl\" && orientation === \"horizontal\";\n\n switch (e.key) {\n case \"ArrowRight\":\n case \"ArrowDown\": {\n e.preventDefault();\n e.stopPropagation();\n if (e.key === \"ArrowRight\" && flipDirection) {\n focusRelative(root, \"previous\");\n } else {\n focusRelative(root, \"next\");\n }\n return;\n }\n case \"ArrowLeft\":\n case \"ArrowUp\": {\n e.preventDefault();\n e.stopPropagation();\n if (e.key === \"ArrowLeft\" && flipDirection) {\n focusRelative(root, \"next\");\n } else {\n focusRelative(root, \"previous\");\n }\n return;\n }\n }\n };\n\n const actionGroupProps: JSX.HTMLAttributes<HTMLElement> = mergeProps(\n filterDOMProps(props as Record<string, unknown>, { labelable: true }),\n {\n ref: (el: HTMLElement) => {\n groupRef = el;\n applyRoleAttributes();\n queueMicrotask(() => {\n if (!groupRef) return;\n applyRoleAttributes();\n });\n },\n onKeyDown,\n get \"aria-label\"() {\n return props[\"aria-label\"];\n },\n get \"aria-labelledby\"() {\n return props[\"aria-label\"] ? undefined : props[\"aria-labelledby\"];\n },\n get \"aria-disabled\"() {\n return isActionGroupDisabled(props, state) || undefined;\n },\n },\n );\n\n actionGroupData.set(state, {\n get onAction() {\n return props.onAction;\n },\n });\n\n onCleanup(() => {\n actionGroupData.delete(state);\n });\n\n return { actionGroupProps };\n}\n\nexport function createActionGroupItem<T>(\n props: AriaActionGroupItemProps,\n state: ListState<T>,\n): ActionGroupItemAria {\n const button = createButton({\n elementType: \"button\",\n isDisabled: state.isDisabled(props.key),\n onPress: () => {\n state.setFocusedKey(props.key);\n actionGroupData.get(state)?.onAction?.(props.key);\n if (state.selectionMode() !== \"none\") {\n state.select(props.key);\n }\n },\n });\n\n const isFocused = () => props.key === state.focusedKey();\n\n onCleanup(() => {\n if (isFocused()) {\n state.setFocusedKey(null);\n }\n });\n\n const buttonProps: JSX.HTMLAttributes<HTMLElement> = mergeProps(button.buttonProps, {\n get role() {\n return ITEM_ROLE_BY_MODE[state.selectionMode()];\n },\n get \"aria-checked\"() {\n const mode = state.selectionMode();\n if (mode === \"none\") return undefined;\n return state.isSelected(props.key);\n },\n // Mirrors react-aria `useActionGroupItem` (3.50.0): every enabled item is\n // tabbable until focus engages the group, then the roving stop follows the\n // focused key. There is NO single default tab stop and NO selection bias —\n // disabled items are made non-tabbable by their native `disabled` attribute\n // (via createButton), not by this getter.\n get tabIndex() {\n return isFocused() || state.focusedKey() == null ? 0 : -1;\n },\n \"data-key\": String(props.key),\n onFocus: () => {\n state.setFocusedKey(props.key);\n },\n });\n\n return { buttonProps };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announce.js","names":[],"sources":["../../src/live-announcer/announce.ts"],"sourcesContent":["/**\n * Live announcer for solidaria\n *\n * Provides functions to make announcements to screen readers using\n * ARIA live regions. Useful for announcing dynamic content changes.\n *\n * Port of react-aria's @react-aria/live-announcer.\n *\n * SSR Safety: All functions are safe to call during SSR. The announcer\n * will only be created when running in the browser.\n */\n\nimport { onCleanup, createEffect } from \"solid-js\";\nimport { isServer } from \"solid-js/web\";\n\nexport type Assertiveness = \"assertive\" | \"polite\";\n\nexport type Message = string | { \"aria-labelledby\": string };\n\nconst LIVEREGION_TIMEOUT_DELAY = 7000;\n\n/**\n * Singleton class that manages live region announcements.\n * Implemented using vanilla DOM for simplicity and framework independence.\n */\nclass LiveAnnouncer {\n node: HTMLElement | null = null;\n assertiveLog: HTMLElement | null = null;\n politeLog: HTMLElement | null = null;\n\n constructor() {\n if (typeof document !== \"undefined\") {\n this.node = document.createElement(\"div\");\n this.node.dataset.liveAnnouncer = \"true\";\n // Visually hidden styles\n Object.assign(this.node.style, {\n border: \"0\",\n clip: \"rect(0 0 0 0)\",\n clipPath: \"inset(50%)\",\n height: \"1px\",\n margin: \"-1px\",\n overflow: \"hidden\",\n padding: \"0\",\n position: \"absolute\",\n width: \"1px\",\n whiteSpace: \"nowrap\",\n });\n\n this.assertiveLog = this.createLog(\"assertive\");\n this.node.appendChild(this.assertiveLog);\n\n this.politeLog = this.createLog(\"polite\");\n this.node.appendChild(this.politeLog);\n\n document.body.prepend(this.node);\n }\n }\n\n isAttached(): boolean {\n return this.node?.isConnected ?? false;\n }\n\n createLog(ariaLive: string): HTMLElement {\n const node = document.createElement(\"div\");\n node.setAttribute(\"role\", \"log\");\n node.setAttribute(\"aria-live\", ariaLive);\n node.setAttribute(\"aria-relevant\", \"additions\");\n return node;\n }\n\n destroy(): void {\n if (!this.node) {\n return;\n }\n\n document.body.removeChild(this.node);\n this.node = null;\n }\n\n announce(\n message: Message,\n assertiveness: Assertiveness = \"assertive\",\n timeout: number = LIVEREGION_TIMEOUT_DELAY,\n ): void {\n if (!this.node) {\n return;\n }\n\n const node = document.createElement(\"div\");\n if (typeof message === \"object\") {\n // To read an aria-labelledby, the element must have an appropriate role, such as img.\n node.setAttribute(\"role\", \"img\");\n node.setAttribute(\"aria-labelledby\", message[\"aria-labelledby\"]);\n } else {\n node.textContent = message;\n }\n\n if (assertiveness === \"assertive\") {\n this.assertiveLog?.appendChild(node);\n } else {\n this.politeLog?.appendChild(node);\n }\n\n if (message !== \"\") {\n setTimeout(() => {\n node.remove();\n }, timeout);\n }\n }\n\n clear(assertiveness?: Assertiveness): void {\n if (!this.node) {\n return;\n }\n\n if ((!assertiveness || assertiveness === \"assertive\") && this.assertiveLog) {\n this.assertiveLog.innerHTML = \"\";\n }\n\n if ((!assertiveness || assertiveness === \"polite\") && this.politeLog) {\n this.politeLog.innerHTML = \"\";\n }\n }\n}\n\nlet liveAnnouncer: LiveAnnouncer | null = null;\n\n/**\n * Announces a message to screen readers using ARIA live regions.\n *\n * @param message - The message to announce, or an object with aria-labelledby\n * @param assertiveness - 'assertive' interrupts, 'polite' waits for a pause\n * @param timeout - How long to keep the message in the DOM (default 7000ms)\n *\n * @example\n * ```tsx\n * // Simple announcement\n * announce('Item added to cart');\n *\n * // Polite announcement (won't interrupt)\n * announce('3 results found', 'polite');\n *\n * // Using aria-labelledby for complex content\n * announce({ 'aria-labelledby': 'my-element-id' });\n * ```\n */\nexport function announce(\n message: Message,\n assertiveness: Assertiveness = \"assertive\",\n timeout: number = LIVEREGION_TIMEOUT_DELAY,\n): void {\n if (!liveAnnouncer) {\n liveAnnouncer = new LiveAnnouncer();\n // Wait for the live announcer regions to be added to the DOM, then announce.\n // Otherwise Safari won't announce the message if it's added too quickly.\n // Found most times less than 100ms were not consistent when announcing with Safari.\n\n // Check for test environment\n const isTestEnv =\n typeof (globalThis as Record<string, unknown>).IS_SOLIDARIA_TEST === \"boolean\"\n ? (globalThis as Record<string, unknown>).IS_SOLIDARIA_TEST\n : typeof (globalThis as Record<string, unknown>).vitest !== \"undefined\";\n\n if (!isTestEnv) {\n setTimeout(() => {\n if (liveAnnouncer?.isAttached()) {\n liveAnnouncer?.announce(message, assertiveness, timeout);\n }\n }, 100);\n } else {\n liveAnnouncer.announce(message, assertiveness, timeout);\n }\n } else {\n liveAnnouncer.announce(message, assertiveness, timeout);\n }\n}\n\n/**\n * Clears all queued announcements for the given assertiveness level.\n *\n * @param assertiveness - Which log to clear ('assertive' or 'polite')\n *\n * @example\n * ```tsx\n * // Clear assertive announcements\n * clearAnnouncer('assertive');\n *\n * // Clear polite announcements\n * clearAnnouncer('polite');\n * ```\n */\nexport function clearAnnouncer(assertiveness?: Assertiveness): void {\n if (liveAnnouncer) {\n liveAnnouncer.clear(assertiveness);\n }\n}\n\n/**\n * Removes the live announcer from the DOM entirely.\n * Call this when unmounting your app or when announcements are no longer needed.\n *\n * @example\n * ```tsx\n * // Clean up on app unmount\n * onCleanup(() => {\n * destroyAnnouncer();\n * });\n * ```\n */\nexport function destroyAnnouncer(): void {\n if (liveAnnouncer) {\n liveAnnouncer.destroy();\n liveAnnouncer = null;\n }\n}\n\nexport interface UseAnnouncerResult {\n /** Announce a message to screen readers. */\n announce: (message: Message, assertiveness?: Assertiveness, timeout?: number) => void;\n /** Clear announcements for the given assertiveness level. */\n clear: (assertiveness?: Assertiveness) => void;\n}\n\n/**\n * Hook that provides access to the live announcer with automatic cleanup.\n *\n * This is the recommended way to use the announcer in SolidJS components.\n * It ensures the announcer is created only on the client and provides\n * type-safe methods for announcing and clearing messages.\n *\n * SSR Safety: Returns no-op functions during SSR. The announcer is only\n * created when the component mounts on the client.\n *\n * @example\n * ```tsx\n * function SearchResults(props) {\n * const announcer = useAnnouncer();\n *\n * createEffect(() => {\n * const count = props.results.length;\n * announcer.announce(`${count} results found`, 'polite');\n * });\n *\n * return <ul>...</ul>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * function Form() {\n * const announcer = useAnnouncer();\n *\n * const onSubmit = async () => {\n * try {\n * await submitForm();\n * announcer.announce('Form submitted successfully');\n * } catch (error) {\n * announcer.announce('Error submitting form', 'assertive');\n * }\n * };\n *\n * return <form onSubmit={onSubmit}>...</form>;\n * }\n * ```\n */\nexport function useAnnouncer(): UseAnnouncerResult {\n // During SSR, return no-op functions\n if (isServer) {\n return {\n announce: () => {},\n clear: () => {},\n };\n }\n\n // Ensure the announcer is initialized\n createEffect(() => {\n // Initialize on first use\n if (!liveAnnouncer) {\n liveAnnouncer = new LiveAnnouncer();\n }\n });\n\n return {\n announce: (\n message: Message,\n assertiveness: Assertiveness = \"assertive\",\n timeout: number = LIVEREGION_TIMEOUT_DELAY,\n ) => {\n announce(message, assertiveness, timeout);\n },\n clear: (assertiveness?: Assertiveness) => {\n clearAnnouncer(assertiveness);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announce.jsx","names":[],"sources":["../../src/live-announcer/announce.ts"],"sourcesContent":["/**\n * Live announcer for solidaria\n *\n * Provides functions to make announcements to screen readers using\n * ARIA live regions. Useful for announcing dynamic content changes.\n *\n * Port of react-aria's @react-aria/live-announcer.\n *\n * SSR Safety: All functions are safe to call during SSR. The announcer\n * will only be created when running in the browser.\n */\n\nimport { onCleanup, createEffect } from \"solid-js\";\nimport { isServer } from \"solid-js/web\";\n\nexport type Assertiveness = \"assertive\" | \"polite\";\n\nexport type Message = string | { \"aria-labelledby\": string };\n\nconst LIVEREGION_TIMEOUT_DELAY = 7000;\n\n/**\n * Singleton class that manages live region announcements.\n * Implemented using vanilla DOM for simplicity and framework independence.\n */\nclass LiveAnnouncer {\n node: HTMLElement | null = null;\n assertiveLog: HTMLElement | null = null;\n politeLog: HTMLElement | null = null;\n\n constructor() {\n if (typeof document !== \"undefined\") {\n this.node = document.createElement(\"div\");\n this.node.dataset.liveAnnouncer = \"true\";\n // Visually hidden styles\n Object.assign(this.node.style, {\n border: \"0\",\n clip: \"rect(0 0 0 0)\",\n clipPath: \"inset(50%)\",\n height: \"1px\",\n margin: \"-1px\",\n overflow: \"hidden\",\n padding: \"0\",\n position: \"absolute\",\n width: \"1px\",\n whiteSpace: \"nowrap\",\n });\n\n this.assertiveLog = this.createLog(\"assertive\");\n this.node.appendChild(this.assertiveLog);\n\n this.politeLog = this.createLog(\"polite\");\n this.node.appendChild(this.politeLog);\n\n document.body.prepend(this.node);\n }\n }\n\n isAttached(): boolean {\n return this.node?.isConnected ?? false;\n }\n\n createLog(ariaLive: string): HTMLElement {\n const node = document.createElement(\"div\");\n node.setAttribute(\"role\", \"log\");\n node.setAttribute(\"aria-live\", ariaLive);\n node.setAttribute(\"aria-relevant\", \"additions\");\n return node;\n }\n\n destroy(): void {\n if (!this.node) {\n return;\n }\n\n document.body.removeChild(this.node);\n this.node = null;\n }\n\n announce(\n message: Message,\n assertiveness: Assertiveness = \"assertive\",\n timeout: number = LIVEREGION_TIMEOUT_DELAY,\n ): void {\n if (!this.node) {\n return;\n }\n\n const node = document.createElement(\"div\");\n if (typeof message === \"object\") {\n // To read an aria-labelledby, the element must have an appropriate role, such as img.\n node.setAttribute(\"role\", \"img\");\n node.setAttribute(\"aria-labelledby\", message[\"aria-labelledby\"]);\n } else {\n node.textContent = message;\n }\n\n if (assertiveness === \"assertive\") {\n this.assertiveLog?.appendChild(node);\n } else {\n this.politeLog?.appendChild(node);\n }\n\n if (message !== \"\") {\n setTimeout(() => {\n node.remove();\n }, timeout);\n }\n }\n\n clear(assertiveness?: Assertiveness): void {\n if (!this.node) {\n return;\n }\n\n if ((!assertiveness || assertiveness === \"assertive\") && this.assertiveLog) {\n this.assertiveLog.innerHTML = \"\";\n }\n\n if ((!assertiveness || assertiveness === \"polite\") && this.politeLog) {\n this.politeLog.innerHTML = \"\";\n }\n }\n}\n\nlet liveAnnouncer: LiveAnnouncer | null = null;\n\n/**\n * Announces a message to screen readers using ARIA live regions.\n *\n * @param message - The message to announce, or an object with aria-labelledby\n * @param assertiveness - 'assertive' interrupts, 'polite' waits for a pause\n * @param timeout - How long to keep the message in the DOM (default 7000ms)\n *\n * @example\n * ```tsx\n * // Simple announcement\n * announce('Item added to cart');\n *\n * // Polite announcement (won't interrupt)\n * announce('3 results found', 'polite');\n *\n * // Using aria-labelledby for complex content\n * announce({ 'aria-labelledby': 'my-element-id' });\n * ```\n */\nexport function announce(\n message: Message,\n assertiveness: Assertiveness = \"assertive\",\n timeout: number = LIVEREGION_TIMEOUT_DELAY,\n): void {\n if (!liveAnnouncer) {\n liveAnnouncer = new LiveAnnouncer();\n // Wait for the live announcer regions to be added to the DOM, then announce.\n // Otherwise Safari won't announce the message if it's added too quickly.\n // Found most times less than 100ms were not consistent when announcing with Safari.\n\n // Check for test environment\n const isTestEnv =\n typeof (globalThis as Record<string, unknown>).IS_SOLIDARIA_TEST === \"boolean\"\n ? (globalThis as Record<string, unknown>).IS_SOLIDARIA_TEST\n : typeof (globalThis as Record<string, unknown>).vitest !== \"undefined\";\n\n if (!isTestEnv) {\n setTimeout(() => {\n if (liveAnnouncer?.isAttached()) {\n liveAnnouncer?.announce(message, assertiveness, timeout);\n }\n }, 100);\n } else {\n liveAnnouncer.announce(message, assertiveness, timeout);\n }\n } else {\n liveAnnouncer.announce(message, assertiveness, timeout);\n }\n}\n\n/**\n * Clears all queued announcements for the given assertiveness level.\n *\n * @param assertiveness - Which log to clear ('assertive' or 'polite')\n *\n * @example\n * ```tsx\n * // Clear assertive announcements\n * clearAnnouncer('assertive');\n *\n * // Clear polite announcements\n * clearAnnouncer('polite');\n * ```\n */\nexport function clearAnnouncer(assertiveness?: Assertiveness): void {\n if (liveAnnouncer) {\n liveAnnouncer.clear(assertiveness);\n }\n}\n\n/**\n * Removes the live announcer from the DOM entirely.\n * Call this when unmounting your app or when announcements are no longer needed.\n *\n * @example\n * ```tsx\n * // Clean up on app unmount\n * onCleanup(() => {\n * destroyAnnouncer();\n * });\n * ```\n */\nexport function destroyAnnouncer(): void {\n if (liveAnnouncer) {\n liveAnnouncer.destroy();\n liveAnnouncer = null;\n }\n}\n\nexport interface UseAnnouncerResult {\n /** Announce a message to screen readers. */\n announce: (message: Message, assertiveness?: Assertiveness, timeout?: number) => void;\n /** Clear announcements for the given assertiveness level. */\n clear: (assertiveness?: Assertiveness) => void;\n}\n\n/**\n * Hook that provides access to the live announcer with automatic cleanup.\n *\n * This is the recommended way to use the announcer in SolidJS components.\n * It ensures the announcer is created only on the client and provides\n * type-safe methods for announcing and clearing messages.\n *\n * SSR Safety: Returns no-op functions during SSR. The announcer is only\n * created when the component mounts on the client.\n *\n * @example\n * ```tsx\n * function SearchResults(props) {\n * const announcer = useAnnouncer();\n *\n * createEffect(() => {\n * const count = props.results.length;\n * announcer.announce(`${count} results found`, 'polite');\n * });\n *\n * return <ul>...</ul>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * function Form() {\n * const announcer = useAnnouncer();\n *\n * const onSubmit = async () => {\n * try {\n * await submitForm();\n * announcer.announce('Form submitted successfully');\n * } catch (error) {\n * announcer.announce('Error submitting form', 'assertive');\n * }\n * };\n *\n * return <form onSubmit={onSubmit}>...</form>;\n * }\n * ```\n */\nexport function useAnnouncer(): UseAnnouncerResult {\n // During SSR, return no-op functions\n if (isServer) {\n return {\n announce: () => {},\n clear: () => {},\n };\n }\n\n // Ensure the announcer is initialized\n createEffect(() => {\n // Initialize on first use\n if (!liveAnnouncer) {\n liveAnnouncer = new LiveAnnouncer();\n }\n });\n\n return {\n announce: (\n message: Message,\n assertiveness: Assertiveness = \"assertive\",\n timeout: number = LIVEREGION_TIMEOUT_DELAY,\n ) => {\n announce(message, assertiveness, timeout);\n },\n clear: (assertiveness?: Assertiveness) => {\n clearAnnouncer(assertiveness);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { a as getEventTarget, i as getActiveElement } from "./focus.js";
|
|
2
|
+
import { s as getPointerType } from "./createInteractionModality.js";
|
|
1
3
|
import { createId, getOwnerDocument } from "../ssr/index.js";
|
|
4
|
+
import { n as FOCUS_EVENT, t as CLEAR_FOCUS_EVENT } from "./constants.js";
|
|
2
5
|
import { createEffect, createSignal, onCleanup } from "solid-js";
|
|
3
6
|
//#region src/autocomplete/createAutocomplete.ts
|
|
4
7
|
/**
|
|
@@ -24,8 +27,10 @@ function toKeyboardEventInit(e) {
|
|
|
24
27
|
cancelable: e.cancelable
|
|
25
28
|
};
|
|
26
29
|
}
|
|
27
|
-
|
|
28
|
-
const
|
|
30
|
+
/** @deprecated Use {@link FOCUS_EVENT} from the selection layer. */
|
|
31
|
+
const AUTOCOMPLETE_FOCUS_EVENT = FOCUS_EVENT;
|
|
32
|
+
/** @deprecated Use {@link CLEAR_FOCUS_EVENT} from the selection layer. */
|
|
33
|
+
const AUTOCOMPLETE_CLEAR_FOCUS_EVENT = CLEAR_FOCUS_EVENT;
|
|
29
34
|
/**
|
|
30
35
|
* Provides the behavior and accessibility implementation for an autocomplete component.
|
|
31
36
|
* An autocomplete combines a text input with a collection, allowing users to filter
|
|
@@ -61,6 +66,10 @@ function createAutocomplete(props, state) {
|
|
|
61
66
|
const [shouldUseVirtualFocus] = createSignal(!disableVirtualFocus);
|
|
62
67
|
const [autoFocusOnMount, setAutoFocusOnMount] = createSignal(false);
|
|
63
68
|
let lastInputType = "";
|
|
69
|
+
let queuedActiveDescendant = null;
|
|
70
|
+
let delayNextActiveDescendant = false;
|
|
71
|
+
let activeDescendantTimeout;
|
|
72
|
+
const ACTIVE_DESCENDANT_DELAY = 500;
|
|
64
73
|
const handleBeforeInput = (e) => {
|
|
65
74
|
lastInputType = e.inputType || "";
|
|
66
75
|
};
|
|
@@ -74,12 +83,13 @@ function createAutocomplete(props, state) {
|
|
|
74
83
|
}
|
|
75
84
|
});
|
|
76
85
|
const focusFirstItem = () => {
|
|
86
|
+
delayNextActiveDescendant = true;
|
|
77
87
|
const collection = collectionRef();
|
|
78
88
|
if (!collection) {
|
|
79
89
|
setAutoFocusOnMount(true);
|
|
80
90
|
return;
|
|
81
91
|
}
|
|
82
|
-
collection.dispatchEvent(new CustomEvent(
|
|
92
|
+
collection.dispatchEvent(new CustomEvent(FOCUS_EVENT, {
|
|
83
93
|
cancelable: true,
|
|
84
94
|
bubbles: true,
|
|
85
95
|
detail: { focusStrategy: "first" }
|
|
@@ -87,9 +97,12 @@ function createAutocomplete(props, state) {
|
|
|
87
97
|
};
|
|
88
98
|
const clearVirtualFocus = (clearFocusKey = false) => {
|
|
89
99
|
setAutoFocusOnMount(false);
|
|
100
|
+
clearTimeout(activeDescendantTimeout);
|
|
101
|
+
queuedActiveDescendant = null;
|
|
102
|
+
delayNextActiveDescendant = false;
|
|
90
103
|
state.setFocusedNodeId(null);
|
|
91
104
|
const collection = collectionRef();
|
|
92
|
-
if (collection) collection.dispatchEvent(new CustomEvent(
|
|
105
|
+
if (collection) collection.dispatchEvent(new CustomEvent(CLEAR_FOCUS_EVENT, {
|
|
93
106
|
cancelable: true,
|
|
94
107
|
bubbles: true,
|
|
95
108
|
detail: { clearFocusKey }
|
|
@@ -102,7 +115,7 @@ function createAutocomplete(props, state) {
|
|
|
102
115
|
};
|
|
103
116
|
const onKeyDown = (e) => {
|
|
104
117
|
if ("isComposing" in e && e.isComposing) return;
|
|
105
|
-
const focusedNodeId =
|
|
118
|
+
const focusedNodeId = queuedActiveDescendant;
|
|
106
119
|
const collection = collectionRef();
|
|
107
120
|
const ownerDocument = getOwnerDocument(inputRef() ?? collection);
|
|
108
121
|
switch (e.key) {
|
|
@@ -118,7 +131,7 @@ function createAutocomplete(props, state) {
|
|
|
118
131
|
case "PageUp":
|
|
119
132
|
case "PageDown":
|
|
120
133
|
e.preventDefault();
|
|
121
|
-
if (collection) collection.dispatchEvent(new CustomEvent(
|
|
134
|
+
if (collection) collection.dispatchEvent(new CustomEvent(FOCUS_EVENT, {
|
|
122
135
|
cancelable: true,
|
|
123
136
|
bubbles: true
|
|
124
137
|
}));
|
|
@@ -140,7 +153,7 @@ function createAutocomplete(props, state) {
|
|
|
140
153
|
}
|
|
141
154
|
return;
|
|
142
155
|
}
|
|
143
|
-
if (
|
|
156
|
+
if (collection) {
|
|
144
157
|
e.stopPropagation();
|
|
145
158
|
let shouldPerformDefaultAction = true;
|
|
146
159
|
if (focusedNodeId) {
|
|
@@ -160,7 +173,42 @@ function createAutocomplete(props, state) {
|
|
|
160
173
|
const onBlur = (e) => {
|
|
161
174
|
if (!e.isTrusted) return;
|
|
162
175
|
};
|
|
163
|
-
const
|
|
176
|
+
const updateActiveDescendant = (e) => {
|
|
177
|
+
const input = inputRef();
|
|
178
|
+
if (!e.isTrusted && shouldUseVirtualFocus() && input && getActiveElement(getOwnerDocument(input)) !== input && getPointerType() !== "touch") input.focus();
|
|
179
|
+
const target = getEventTarget(e);
|
|
180
|
+
if (e.isTrusted || !target || queuedActiveDescendant === target.id) return;
|
|
181
|
+
const collection = collectionRef();
|
|
182
|
+
clearTimeout(activeDescendantTimeout);
|
|
183
|
+
if (target !== collection) {
|
|
184
|
+
const targetId = target.id;
|
|
185
|
+
if (delayNextActiveDescendant) {
|
|
186
|
+
queuedActiveDescendant = targetId;
|
|
187
|
+
activeDescendantTimeout = setTimeout(() => {
|
|
188
|
+
state.setFocusedNodeId(targetId);
|
|
189
|
+
}, ACTIVE_DESCENDANT_DELAY);
|
|
190
|
+
} else {
|
|
191
|
+
queuedActiveDescendant = targetId;
|
|
192
|
+
state.setFocusedNodeId(targetId);
|
|
193
|
+
}
|
|
194
|
+
} else if (queuedActiveDescendant && !getOwnerDocument(collection)?.getElementById(queuedActiveDescendant)) {
|
|
195
|
+
queuedActiveDescendant = null;
|
|
196
|
+
state.setFocusedNodeId(null);
|
|
197
|
+
}
|
|
198
|
+
delayNextActiveDescendant = false;
|
|
199
|
+
};
|
|
200
|
+
createEffect(() => {
|
|
201
|
+
if (!shouldUseVirtualFocus()) return;
|
|
202
|
+
const collection = collectionRef();
|
|
203
|
+
if (collection) {
|
|
204
|
+
collection.addEventListener("focusin", updateActiveDescendant);
|
|
205
|
+
onCleanup(() => {
|
|
206
|
+
collection.removeEventListener("focusin", updateActiveDescendant);
|
|
207
|
+
clearTimeout(activeDescendantTimeout);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
const filterFn = filter ? (nodeTextValue, node) => filter(nodeTextValue, state.inputValue(), node) : void 0;
|
|
164
212
|
return {
|
|
165
213
|
inputProps: {
|
|
166
214
|
value: state.inputValue,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autocomplete.js","names":["inputEvent"],"sources":["../../src/autocomplete/createAutocomplete.ts"],"sourcesContent":["/**\n * createAutocomplete - Accessibility hook for autocomplete components\n *\n * Provides keyboard navigation, virtual focus via aria-activedescendant,\n * and filtering capabilities for autocomplete inputs.\n *\n * Based on @react-aria/autocomplete useAutocomplete.\n */\n\nimport { createSignal, createEffect, onCleanup, type Accessor } from \"solid-js\";\nimport { createId, getOwnerDocument } from \"../ssr\";\nimport { type AutocompleteState } from \"@proyecto-viviana/solid-stately\";\n\nexport interface CollectionOptions {\n /** The id of the collection element. */\n id?: string;\n /** Accessible label for the collection. */\n \"aria-label\"?: string;\n /** Whether the collection items should use virtual focus instead of being focused directly. */\n shouldUseVirtualFocus: boolean;\n /** Whether typeahead is disabled. */\n disallowTypeAhead: boolean;\n /** Whether and where to focus an item when the collection mounts. */\n autoFocus?: boolean | \"first\" | \"last\";\n}\n\nexport interface AutocompleteInputProps {\n /** Current input value. */\n value: Accessor<string>;\n /** Handler for input value changes. */\n onChange: (value: string) => void;\n /** Handler for key events. */\n onKeyDown: (e: KeyboardEvent) => void;\n /** Handler for focus events. */\n onFocus: (e: FocusEvent) => void;\n /** Handler for blur events. */\n onBlur: (e: FocusEvent) => void;\n /** The id of the currently focused item for aria-activedescendant. */\n \"aria-activedescendant\": Accessor<string | undefined>;\n /** The id of the controlled collection. */\n \"aria-controls\": string;\n /** Autocomplete type. */\n \"aria-autocomplete\": \"list\" | \"none\" | \"inline\" | \"both\";\n /** Enter key hint for mobile keyboards. */\n enterKeyHint: \"go\";\n /** Disable autocorrect. */\n autoCorrect: \"off\";\n /** Disable spell check. */\n spellCheck: \"false\";\n /** Disable browser autocomplete. */\n autoComplete: \"off\";\n}\n\nexport interface AriaAutocompleteOptions<_T = unknown> {\n /** Ref accessor for the input element. */\n inputRef: Accessor<HTMLInputElement | undefined>;\n /** Ref accessor for the collection element. */\n collectionRef: Accessor<HTMLElement | undefined>;\n /** Optional id override for the controlled collection element. */\n collectionId?: string;\n /** Optional accessible name for the controlled collection element. */\n collectionAriaLabel?: string;\n /**\n * An optional filter function used to determine if an option should be included.\n * @param textValue - The text value of the item\n * @param inputValue - The current input value\n */\n filter?: (textValue: string, inputValue: string) => boolean;\n /**\n * Whether to focus the first item after filtering.\n * @default false\n */\n disableAutoFocusFirst?: boolean;\n /**\n * Whether to disable virtual focus (aria-activedescendant).\n * @default false\n */\n disableVirtualFocus?: boolean;\n}\n\nexport interface AutocompleteAria<_T = unknown> {\n /** Props for the autocomplete input element. */\n inputProps: AutocompleteInputProps;\n /** Props for the collection (ListBox/Menu). */\n collectionProps: CollectionOptions;\n /** A filter function that returns if the item should be shown. */\n filter?: (textValue: string) => boolean;\n}\n\nfunction toKeyboardEventInit(e: KeyboardEvent): KeyboardEventInit {\n return {\n key: e.key,\n code: e.code,\n location: e.location,\n repeat: e.repeat,\n isComposing: e.isComposing,\n ctrlKey: e.ctrlKey,\n shiftKey: e.shiftKey,\n altKey: e.altKey,\n metaKey: e.metaKey,\n bubbles: e.bubbles,\n cancelable: e.cancelable,\n };\n}\n\n// Custom event names for collection communication\nexport const AUTOCOMPLETE_FOCUS_EVENT = \"autocomplete:focus\";\nexport const AUTOCOMPLETE_CLEAR_FOCUS_EVENT = \"autocomplete:clearfocus\";\n\n/**\n * Provides the behavior and accessibility implementation for an autocomplete component.\n * An autocomplete combines a text input with a collection, allowing users to filter\n * the collection's contents to match a query.\n *\n * @example\n * ```tsx\n * const state = createAutocompleteState({ defaultInputValue: '' });\n * let inputRef, collectionRef;\n *\n * const { inputProps, collectionProps, filter } = createAutocomplete({\n * inputRef: () => inputRef,\n * collectionRef: () => collectionRef,\n * filter: (textValue, inputValue) =>\n * textValue.toLowerCase().includes(inputValue.toLowerCase()),\n * }, state);\n *\n * return (\n * <div>\n * <input ref={inputRef} {...inputProps} />\n * <ul ref={collectionRef} {...collectionProps}>\n * {items.filter(item => filter?.(item.name) ?? true).map(item => (\n * <li key={item.id}>{item.name}</li>\n * ))}\n * </ul>\n * </div>\n * );\n * ```\n */\nexport function createAutocomplete<T = unknown>(\n props: AriaAutocompleteOptions<T>,\n state: AutocompleteState,\n): AutocompleteAria<T> {\n const {\n inputRef,\n collectionRef,\n filter,\n collectionId: collectionIdProp,\n collectionAriaLabel,\n disableAutoFocusFirst = false,\n disableVirtualFocus = false,\n } = props;\n\n const collectionId = collectionIdProp ?? createId();\n const [shouldUseVirtualFocus] = createSignal(!disableVirtualFocus);\n // When the first item should be focused but the collection hasn't mounted yet,\n // defer it by threading autoFocus through collectionProps so the collection\n // focuses on mount (mirrors upstream's autoFocusOnMount state).\n const [autoFocusOnMount, setAutoFocusOnMount] = createSignal(false);\n let lastInputType = \"\";\n\n // Track the input type for determining focus behavior. We listen to\n // `beforeinput` (not `input`) so `lastInputType` is set before the input's\n // `onChange` runs, mirroring @react-aria/autocomplete.\n const handleBeforeInput = (e: Event) => {\n const inputEvent = e as InputEvent;\n lastInputType = inputEvent.inputType || \"\";\n };\n\n // Set up beforeinput event listener\n createEffect(() => {\n const input = inputRef();\n if (input) {\n input.addEventListener(\"beforeinput\", handleBeforeInput);\n onCleanup(() => {\n input.removeEventListener(\"beforeinput\", handleBeforeInput);\n });\n }\n });\n\n // Focus first item in collection\n const focusFirstItem = () => {\n const collection = collectionRef();\n if (!collection) {\n // Collection isn't mounted yet — defer focusing the first item to mount\n // time via collectionProps.autoFocus instead of firing a focus event into\n // a not-yet-rendered collection.\n setAutoFocusOnMount(true);\n return;\n }\n collection.dispatchEvent(\n new CustomEvent(AUTOCOMPLETE_FOCUS_EVENT, {\n cancelable: true,\n bubbles: true,\n detail: { focusStrategy: \"first\" },\n }),\n );\n };\n\n // Clear virtual focus\n const clearVirtualFocus = (clearFocusKey = false) => {\n setAutoFocusOnMount(false);\n state.setFocusedNodeId(null);\n const collection = collectionRef();\n if (collection) {\n collection.dispatchEvent(\n new CustomEvent(AUTOCOMPLETE_CLEAR_FOCUS_EVENT, {\n cancelable: true,\n bubbles: true,\n detail: { clearFocusKey },\n }),\n );\n }\n };\n\n // Handle input value changes\n const onChange = (value: string) => {\n // Focus first item when typing forward, clear when backspacing/pasting.\n // IME composition (e.g. CJK input) reports 'insertCompositionText'/\n // 'insertFromComposition' instead of 'insertText'; treat those as forward\n // typing so the first item still gets virtual focus.\n if (\n (lastInputType === \"insertText\" ||\n lastInputType === \"insertCompositionText\" ||\n lastInputType === \"insertFromComposition\") &&\n !disableAutoFocusFirst\n ) {\n focusFirstItem();\n } else if (\n lastInputType &&\n (lastInputType.includes(\"insert\") ||\n lastInputType.includes(\"delete\") ||\n lastInputType.includes(\"history\"))\n ) {\n clearVirtualFocus(true);\n }\n\n state.setInputValue(value);\n };\n\n // Handle keyboard navigation\n const onKeyDown = (e: KeyboardEvent) => {\n if (\"isComposing\" in e && e.isComposing) {\n return;\n }\n\n const focusedNodeId = state.focusedNodeId();\n const collection = collectionRef();\n const ownerDocument = getOwnerDocument(inputRef() ?? collection);\n\n switch (e.key) {\n case \"Escape\":\n // Let the input handle Escape (e.g., clear value)\n if (e.defaultPrevented) {\n return;\n }\n break;\n\n case \" \":\n // Space shouldn't trigger item action\n return;\n\n case \"Tab\":\n // Let Tab propagate normally for focus management\n return;\n\n case \"ArrowUp\":\n case \"ArrowDown\":\n case \"Home\":\n case \"End\":\n case \"PageUp\":\n case \"PageDown\": {\n // Prevent cursor movement in input\n e.preventDefault();\n\n // Dispatch focus event to collection\n if (collection) {\n collection.dispatchEvent(\n new CustomEvent(AUTOCOMPLETE_FOCUS_EVENT, {\n cancelable: true,\n bubbles: true,\n }),\n );\n }\n break;\n }\n\n case \"ArrowLeft\":\n case \"ArrowRight\":\n // With no virtual focus, just let the arrow move the text cursor; stop\n // propagation so it doesn't leak into the collection. With virtual focus,\n // fall through to dispatch the key to the focused item below, then clear\n // the active descendant only after the item has seen it.\n if (!focusedNodeId) {\n e.stopPropagation();\n return;\n }\n break;\n\n case \"Enter\":\n // Trigger click on focused item\n if (focusedNodeId) {\n const item = ownerDocument?.getElementById(focusedNodeId);\n if (item) {\n item.click();\n e.preventDefault();\n }\n }\n return;\n }\n\n // Forward keyboard events to the focused item (or the collection when there\n // is no virtual focus) so the collection can act on them, e.g. onAction.\n if (!e.defaultPrevented && collection) {\n e.stopPropagation();\n\n let shouldPerformDefaultAction = true;\n if (focusedNodeId) {\n const item = ownerDocument?.getElementById(focusedNodeId);\n if (item) {\n shouldPerformDefaultAction = item.dispatchEvent(\n new KeyboardEvent(e.type, toKeyboardEventInit(e)),\n );\n }\n } else {\n shouldPerformDefaultAction = collection.dispatchEvent(\n new KeyboardEvent(e.type, toKeyboardEventInit(e)),\n );\n }\n\n // After the focused item has seen ArrowLeft/ArrowRight, clear the active\n // descendant so SR announcements aren't interrupted, but retain the\n // collection's focused key so navigation restarts where it left off. If\n // the item consumed the key (e.g. an expandable row collapsing), it\n // cancels the event and we keep virtual focus.\n if (shouldPerformDefaultAction && (e.key === \"ArrowLeft\" || e.key === \"ArrowRight\")) {\n clearVirtualFocus();\n }\n }\n };\n\n // Handle focus events\n const onFocus = (e: FocusEvent) => {\n if (!e.isTrusted) return;\n\n // Restore virtual focus when refocusing input\n const focusedNodeId = state.focusedNodeId();\n if (focusedNodeId) {\n const item = document.getElementById(focusedNodeId);\n if (item) {\n // Item still exists, keep focus on it\n }\n }\n };\n\n const onBlur = (e: FocusEvent) => {\n if (!e.isTrusted) return;\n // Virtual focus blur handling would go here\n };\n\n // Create filter function\n const filterFn = filter\n ? (textValue: string) => filter(textValue, state.inputValue())\n : undefined;\n\n return {\n inputProps: {\n value: state.inputValue,\n onChange,\n onKeyDown,\n onFocus,\n onBlur,\n get \"aria-activedescendant\"() {\n return () => (shouldUseVirtualFocus() ? (state.focusedNodeId() ?? undefined) : undefined);\n },\n \"aria-controls\": collectionId,\n \"aria-autocomplete\": \"list\",\n enterKeyHint: \"go\",\n autoCorrect: \"off\",\n spellCheck: \"false\",\n autoComplete: \"off\",\n },\n collectionProps: {\n id: collectionId,\n \"aria-label\": collectionAriaLabel,\n shouldUseVirtualFocus: shouldUseVirtualFocus(),\n disallowTypeAhead: shouldUseVirtualFocus(),\n get autoFocus() {\n return autoFocusOnMount() ? \"first\" : false;\n },\n },\n filter: filterFn,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"autocomplete.js","names":["inputEvent"],"sources":["../../src/autocomplete/createAutocomplete.ts"],"sourcesContent":["/**\n * createAutocomplete - Accessibility hook for autocomplete components\n *\n * Provides keyboard navigation, virtual focus via aria-activedescendant,\n * and filtering capabilities for autocomplete inputs.\n *\n * Based on @react-aria/autocomplete useAutocomplete.\n */\n\nimport { createSignal, createEffect, onCleanup, type Accessor } from \"solid-js\";\nimport { createId, getOwnerDocument } from \"../ssr\";\nimport { type AutocompleteState, type CollectionNode } from \"@proyecto-viviana/solid-stately\";\nimport { FOCUS_EVENT, CLEAR_FOCUS_EVENT } from \"../selection/constants\";\nimport { getActiveElement, getEventTarget } from \"../utils/dom\";\nimport { getPointerType } from \"../interactions\";\n\nexport interface CollectionOptions {\n /** The id of the collection element. */\n id?: string;\n /** Accessible label for the collection. */\n \"aria-label\"?: string;\n /** Whether the collection items should use virtual focus instead of being focused directly. */\n shouldUseVirtualFocus: boolean;\n /** Whether typeahead is disabled. */\n disallowTypeAhead: boolean;\n /** Whether and where to focus an item when the collection mounts. */\n autoFocus?: boolean | \"first\" | \"last\";\n}\n\nexport interface AutocompleteInputProps {\n /** Current input value. */\n value: Accessor<string>;\n /** Handler for input value changes. */\n onChange: (value: string) => void;\n /** Handler for key events. */\n onKeyDown: (e: KeyboardEvent) => void;\n /** Handler for focus events. */\n onFocus: (e: FocusEvent) => void;\n /** Handler for blur events. */\n onBlur: (e: FocusEvent) => void;\n /** The id of the currently focused item for aria-activedescendant. */\n \"aria-activedescendant\": Accessor<string | undefined>;\n /** The id of the controlled collection. */\n \"aria-controls\": string;\n /** Autocomplete type. */\n \"aria-autocomplete\": \"list\" | \"none\" | \"inline\" | \"both\";\n /** Enter key hint for mobile keyboards. */\n enterKeyHint: \"go\";\n /** Disable autocorrect. */\n autoCorrect: \"off\";\n /** Disable spell check. */\n spellCheck: \"false\";\n /** Disable browser autocomplete. */\n autoComplete: \"off\";\n}\n\nexport interface AriaAutocompleteOptions<T = unknown> {\n /** Ref accessor for the input element. */\n inputRef: Accessor<HTMLInputElement | undefined>;\n /** Ref accessor for the collection element. */\n collectionRef: Accessor<HTMLElement | undefined>;\n /** Optional id override for the controlled collection element. */\n collectionId?: string;\n /** Optional accessible name for the controlled collection element. */\n collectionAriaLabel?: string;\n /**\n * An optional filter function used to determine if an option should be included.\n * @param textValue - The text value of the item\n * @param inputValue - The current input value\n * @param node - The collection node being filtered\n */\n filter?: (textValue: string, inputValue: string, node: CollectionNode<T>) => boolean;\n /**\n * Whether to focus the first item after filtering.\n * @default false\n */\n disableAutoFocusFirst?: boolean;\n /**\n * Whether to disable virtual focus (aria-activedescendant).\n * @default false\n */\n disableVirtualFocus?: boolean;\n}\n\nexport interface AutocompleteAria<T = unknown> {\n /** Props for the autocomplete input element. */\n inputProps: AutocompleteInputProps;\n /** Props for the collection (ListBox/Menu). */\n collectionProps: CollectionOptions;\n /** A filter function that returns if the item should be shown. */\n filter?: (nodeTextValue: string, node: CollectionNode<T>) => boolean;\n}\n\nfunction toKeyboardEventInit(e: KeyboardEvent): KeyboardEventInit {\n return {\n key: e.key,\n code: e.code,\n location: e.location,\n repeat: e.repeat,\n isComposing: e.isComposing,\n ctrlKey: e.ctrlKey,\n shiftKey: e.shiftKey,\n altKey: e.altKey,\n metaKey: e.metaKey,\n bubbles: e.bubbles,\n cancelable: e.cancelable,\n };\n}\n\n// Custom event names for collection communication.\n// These are the shared @react-aria/selection constants the collection's\n// createSelectableCollection listens for (FOCUS_EVENT / CLEAR_FOCUS_EVENT);\n// the aliases below are retained for backwards-compatible re-exports.\n/** @deprecated Use {@link FOCUS_EVENT} from the selection layer. */\nexport const AUTOCOMPLETE_FOCUS_EVENT = FOCUS_EVENT;\n/** @deprecated Use {@link CLEAR_FOCUS_EVENT} from the selection layer. */\nexport const AUTOCOMPLETE_CLEAR_FOCUS_EVENT = CLEAR_FOCUS_EVENT;\n\n/**\n * Provides the behavior and accessibility implementation for an autocomplete component.\n * An autocomplete combines a text input with a collection, allowing users to filter\n * the collection's contents to match a query.\n *\n * @example\n * ```tsx\n * const state = createAutocompleteState({ defaultInputValue: '' });\n * let inputRef, collectionRef;\n *\n * const { inputProps, collectionProps, filter } = createAutocomplete({\n * inputRef: () => inputRef,\n * collectionRef: () => collectionRef,\n * filter: (textValue, inputValue) =>\n * textValue.toLowerCase().includes(inputValue.toLowerCase()),\n * }, state);\n *\n * return (\n * <div>\n * <input ref={inputRef} {...inputProps} />\n * <ul ref={collectionRef} {...collectionProps}>\n * {items.filter(item => filter?.(item.name) ?? true).map(item => (\n * <li key={item.id}>{item.name}</li>\n * ))}\n * </ul>\n * </div>\n * );\n * ```\n */\nexport function createAutocomplete<T = unknown>(\n props: AriaAutocompleteOptions<T>,\n state: AutocompleteState,\n): AutocompleteAria<T> {\n const {\n inputRef,\n collectionRef,\n filter,\n collectionId: collectionIdProp,\n collectionAriaLabel,\n disableAutoFocusFirst = false,\n disableVirtualFocus = false,\n } = props;\n\n const collectionId = collectionIdProp ?? createId();\n const [shouldUseVirtualFocus] = createSignal(!disableVirtualFocus);\n // When the first item should be focused but the collection hasn't mounted yet,\n // defer it by threading autoFocus through collectionProps so the collection\n // focuses on mount (mirrors upstream's autoFocusOnMount state).\n const [autoFocusOnMount, setAutoFocusOnMount] = createSignal(false);\n let lastInputType = \"\";\n\n // Reverse-focus (collection → input aria-activedescendant) bookkeeping, mirroring\n // useAutocomplete's queuedActiveDescendant / delayNextActiveDescendant / timeout\n // refs. `queuedActiveDescendant` is the id of the option the collection has\n // virtually focused — tracked synchronously on every collection `focusin`, even\n // while the visible aria-activedescendant reflection is deferred, so key\n // navigation always dispatches to the right item. When typing forward,\n // `delayNextActiveDescendant` defers the next reflection behind a 500ms timeout so\n // a screen reader finishes announcing the just-typed letter before naming the\n // active option; every new focusin (and teardown) clears the pending timeout.\n let queuedActiveDescendant: string | null = null;\n let delayNextActiveDescendant = false;\n let activeDescendantTimeout: ReturnType<typeof setTimeout> | undefined;\n const ACTIVE_DESCENDANT_DELAY = 500;\n\n // Track the input type for determining focus behavior. We listen to\n // `beforeinput` (not `input`) so `lastInputType` is set before the input's\n // `onChange` runs, mirroring @react-aria/autocomplete.\n const handleBeforeInput = (e: Event) => {\n const inputEvent = e as InputEvent;\n lastInputType = inputEvent.inputType || \"\";\n };\n\n // Set up beforeinput event listener\n createEffect(() => {\n const input = inputRef();\n if (input) {\n input.addEventListener(\"beforeinput\", handleBeforeInput);\n onCleanup(() => {\n input.removeEventListener(\"beforeinput\", handleBeforeInput);\n });\n }\n });\n\n // Focus first item in collection\n const focusFirstItem = () => {\n // Typing forward focuses the first filtered row, but the resulting\n // aria-activedescendant reflection must wait so the SR announces the typed\n // letter first (see updateActiveDescendant). Mirrors useAutocomplete\n // setting delayNextActiveDescendant.current = true before the focus event.\n delayNextActiveDescendant = true;\n const collection = collectionRef();\n if (!collection) {\n // Collection isn't mounted yet — defer focusing the first item to mount\n // time via collectionProps.autoFocus instead of firing a focus event into\n // a not-yet-rendered collection.\n setAutoFocusOnMount(true);\n return;\n }\n collection.dispatchEvent(\n new CustomEvent(FOCUS_EVENT, {\n cancelable: true,\n bubbles: true,\n detail: { focusStrategy: \"first\" },\n }),\n );\n };\n\n // Clear virtual focus\n const clearVirtualFocus = (clearFocusKey = false) => {\n setAutoFocusOnMount(false);\n // Drop any pending delayed reflection and the tracked item alongside the\n // active descendant so a subsequent focusin can't resurrect a stale id\n // (mirrors useAutocomplete clearing queuedActiveDescendant / the timeout /\n // delayNextActiveDescendant in clearVirtualFocus).\n clearTimeout(activeDescendantTimeout);\n queuedActiveDescendant = null;\n delayNextActiveDescendant = false;\n state.setFocusedNodeId(null);\n const collection = collectionRef();\n if (collection) {\n collection.dispatchEvent(\n new CustomEvent(CLEAR_FOCUS_EVENT, {\n cancelable: true,\n bubbles: true,\n detail: { clearFocusKey },\n }),\n );\n }\n };\n\n // Handle input value changes\n const onChange = (value: string) => {\n // Focus first item when typing forward, clear when backspacing/pasting.\n // IME composition (e.g. CJK input) reports 'insertCompositionText'/\n // 'insertFromComposition' instead of 'insertText'; treat those as forward\n // typing so the first item still gets virtual focus.\n if (\n (lastInputType === \"insertText\" ||\n lastInputType === \"insertCompositionText\" ||\n lastInputType === \"insertFromComposition\") &&\n !disableAutoFocusFirst\n ) {\n focusFirstItem();\n } else if (\n lastInputType &&\n (lastInputType.includes(\"insert\") ||\n lastInputType.includes(\"delete\") ||\n lastInputType.includes(\"history\"))\n ) {\n clearVirtualFocus(true);\n }\n\n state.setInputValue(value);\n };\n\n // Handle keyboard navigation\n const onKeyDown = (e: KeyboardEvent) => {\n if (\"isComposing\" in e && e.isComposing) {\n return;\n }\n\n // Navigate against the id the collection has virtually focused, tracked\n // synchronously on every focusin, NOT the (possibly still-delayed)\n // aria-activedescendant reflection in state — mirrors useAutocomplete\n // reading queuedActiveDescendant.current here. Reading state.focusedNodeId()\n // would be null during the post-type reflection delay, so arrows would\n // re-focus the first row instead of advancing.\n const focusedNodeId = queuedActiveDescendant;\n const collection = collectionRef();\n const ownerDocument = getOwnerDocument(inputRef() ?? collection);\n\n switch (e.key) {\n case \"Escape\":\n // Let the input handle Escape (e.g., clear value)\n if (e.defaultPrevented) {\n return;\n }\n break;\n\n case \" \":\n // Space shouldn't trigger item action\n return;\n\n case \"Tab\":\n // Let Tab propagate normally for focus management\n return;\n\n case \"ArrowUp\":\n case \"ArrowDown\":\n case \"Home\":\n case \"End\":\n case \"PageUp\":\n case \"PageDown\": {\n // Prevent cursor movement in input\n e.preventDefault();\n\n // Dispatch focus event to collection\n if (collection) {\n collection.dispatchEvent(\n new CustomEvent(FOCUS_EVENT, {\n cancelable: true,\n bubbles: true,\n }),\n );\n }\n break;\n }\n\n case \"ArrowLeft\":\n case \"ArrowRight\":\n // With no virtual focus, just let the arrow move the text cursor; stop\n // propagation so it doesn't leak into the collection. With virtual focus,\n // fall through to dispatch the key to the focused item below, then clear\n // the active descendant only after the item has seen it.\n if (!focusedNodeId) {\n e.stopPropagation();\n return;\n }\n break;\n\n case \"Enter\":\n // Trigger click on focused item\n if (focusedNodeId) {\n const item = ownerDocument?.getElementById(focusedNodeId);\n if (item) {\n item.click();\n e.preventDefault();\n }\n }\n return;\n }\n\n // Forward keyboard events to the focused item (or the collection when there\n // is no virtual focus) so the collection can act on them, e.g. onAction.\n // Gated only on the collection existing — NOT on e.defaultPrevented: arrows\n // call preventDefault above to stop the input's text cursor moving, but the\n // key must still be re-dispatched to the row so navigation advances (mirrors\n // useAutocomplete forwarding whenever collectionRef.current !== null).\n if (collection) {\n e.stopPropagation();\n\n let shouldPerformDefaultAction = true;\n if (focusedNodeId) {\n const item = ownerDocument?.getElementById(focusedNodeId);\n if (item) {\n shouldPerformDefaultAction = item.dispatchEvent(\n new KeyboardEvent(e.type, toKeyboardEventInit(e)),\n );\n }\n } else {\n shouldPerformDefaultAction = collection.dispatchEvent(\n new KeyboardEvent(e.type, toKeyboardEventInit(e)),\n );\n }\n\n // After the focused item has seen ArrowLeft/ArrowRight, clear the active\n // descendant so SR announcements aren't interrupted, but retain the\n // collection's focused key so navigation restarts where it left off. If\n // the item consumed the key (e.g. an expandable row collapsing), it\n // cancels the event and we keep virtual focus.\n if (shouldPerformDefaultAction && (e.key === \"ArrowLeft\" || e.key === \"ArrowRight\")) {\n clearVirtualFocus();\n }\n }\n };\n\n // Handle focus events\n const onFocus = (e: FocusEvent) => {\n if (!e.isTrusted) return;\n\n // Restore virtual focus when refocusing input\n const focusedNodeId = state.focusedNodeId();\n if (focusedNodeId) {\n const item = document.getElementById(focusedNodeId);\n if (item) {\n // Item still exists, keep focus on it\n }\n }\n };\n\n const onBlur = (e: FocusEvent) => {\n if (!e.isTrusted) return;\n // Virtual focus blur handling would go here\n };\n\n // Reverse path: mirror the collection's virtually-focused option into the\n // input's aria-activedescendant. The collection dispatches a synthetic,\n // bubbling `focusin` (via moveVirtualFocus) on its focused option; we read the\n // target id, track it in `queuedActiveDescendant` synchronously (so key nav\n // always dispatches to the right item), and reflect it into AutocompleteState\n // — immediately, or, when `delayNextActiveDescendant` was set by forward\n // typing, behind a 500ms timeout so the SR finishes announcing the typed\n // letter before naming the active option. Faithful port of useAutocomplete's\n // updateActiveDescendantEvent.\n const updateActiveDescendant = (e: Event) => {\n const input = inputRef();\n // If the user clicks/taps an option directly (non-touch), pull real focus\n // back to the input so typing keeps working.\n if (\n !e.isTrusted &&\n shouldUseVirtualFocus() &&\n input &&\n getActiveElement(getOwnerDocument(input)) !== input &&\n getPointerType() !== \"touch\"\n ) {\n input.focus();\n }\n\n const target = getEventTarget<Element>(e);\n if (e.isTrusted || !target || queuedActiveDescendant === target.id) {\n return;\n }\n\n const collection = collectionRef();\n // A new focusin supersedes any pending delayed reflection.\n clearTimeout(activeDescendantTimeout);\n if (target !== collection) {\n const targetId = target.id;\n if (delayNextActiveDescendant) {\n // Forward typing just focused the first row: track it now, but defer the\n // aria-activedescendant reflection so the just-typed letter is announced\n // before the active option.\n queuedActiveDescendant = targetId;\n activeDescendantTimeout = setTimeout(() => {\n state.setFocusedNodeId(targetId);\n }, ACTIVE_DESCENDANT_DELAY);\n } else {\n queuedActiveDescendant = targetId;\n state.setFocusedNodeId(targetId);\n }\n } else if (\n queuedActiveDescendant &&\n !getOwnerDocument(collection)?.getElementById(queuedActiveDescendant)\n ) {\n // Refocus of the collection itself while the previously focused node no\n // longer exists (filtered away, or the input was refocused): clear the\n // tracked item and the active descendant.\n queuedActiveDescendant = null;\n state.setFocusedNodeId(null);\n }\n // Reflection delay is a one-shot; the next focusin reflects immediately\n // unless forward typing sets the flag again.\n delayNextActiveDescendant = false;\n };\n\n createEffect(() => {\n if (!shouldUseVirtualFocus()) return;\n const collection = collectionRef();\n if (collection) {\n collection.addEventListener(\"focusin\", updateActiveDescendant);\n onCleanup(() => {\n collection.removeEventListener(\"focusin\", updateActiveDescendant);\n clearTimeout(activeDescendantTimeout);\n });\n }\n });\n\n // Create filter function. The user's 3-arg predicate (textValue, inputValue,\n // node) is closed over the current input value and exposed to the collection\n // as the 2-arg (nodeTextValue, node) shape that UNSTABLE_useFilteredListState\n // (createFilteredListState) expects.\n const filterFn = filter\n ? (nodeTextValue: string, node: CollectionNode<T>) =>\n filter(nodeTextValue, state.inputValue(), node)\n : undefined;\n\n return {\n inputProps: {\n value: state.inputValue,\n onChange,\n onKeyDown,\n onFocus,\n onBlur,\n get \"aria-activedescendant\"() {\n return () => (shouldUseVirtualFocus() ? (state.focusedNodeId() ?? undefined) : undefined);\n },\n \"aria-controls\": collectionId,\n \"aria-autocomplete\": \"list\",\n enterKeyHint: \"go\",\n autoCorrect: \"off\",\n spellCheck: \"false\",\n autoComplete: \"off\",\n },\n collectionProps: {\n id: collectionId,\n \"aria-label\": collectionAriaLabel,\n shouldUseVirtualFocus: shouldUseVirtualFocus(),\n disallowTypeAhead: shouldUseVirtualFocus(),\n get autoFocus() {\n return autoFocusOnMount() ? \"first\" : false;\n },\n },\n filter: filterFn,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { a as getEventTarget, i as getActiveElement } from "./focus.jsx";
|
|
2
|
+
import { s as getPointerType } from "./createInteractionModality.jsx";
|
|
1
3
|
import { createId, getOwnerDocument } from "../ssr/index.jsx";
|
|
4
|
+
import { n as FOCUS_EVENT, t as CLEAR_FOCUS_EVENT } from "./constants.jsx";
|
|
2
5
|
import { createEffect, createSignal, onCleanup } from "solid-js";
|
|
3
6
|
//#region src/autocomplete/createAutocomplete.ts
|
|
4
7
|
/**
|
|
@@ -24,8 +27,10 @@ function toKeyboardEventInit(e) {
|
|
|
24
27
|
cancelable: e.cancelable
|
|
25
28
|
};
|
|
26
29
|
}
|
|
27
|
-
|
|
28
|
-
const
|
|
30
|
+
/** @deprecated Use {@link FOCUS_EVENT} from the selection layer. */
|
|
31
|
+
const AUTOCOMPLETE_FOCUS_EVENT = FOCUS_EVENT;
|
|
32
|
+
/** @deprecated Use {@link CLEAR_FOCUS_EVENT} from the selection layer. */
|
|
33
|
+
const AUTOCOMPLETE_CLEAR_FOCUS_EVENT = CLEAR_FOCUS_EVENT;
|
|
29
34
|
/**
|
|
30
35
|
* Provides the behavior and accessibility implementation for an autocomplete component.
|
|
31
36
|
* An autocomplete combines a text input with a collection, allowing users to filter
|
|
@@ -61,6 +66,10 @@ function createAutocomplete(props, state) {
|
|
|
61
66
|
const [shouldUseVirtualFocus] = createSignal(!disableVirtualFocus);
|
|
62
67
|
const [autoFocusOnMount, setAutoFocusOnMount] = createSignal(false);
|
|
63
68
|
let lastInputType = "";
|
|
69
|
+
let queuedActiveDescendant = null;
|
|
70
|
+
let delayNextActiveDescendant = false;
|
|
71
|
+
let activeDescendantTimeout;
|
|
72
|
+
const ACTIVE_DESCENDANT_DELAY = 500;
|
|
64
73
|
const handleBeforeInput = (e) => {
|
|
65
74
|
lastInputType = e.inputType || "";
|
|
66
75
|
};
|
|
@@ -74,12 +83,13 @@ function createAutocomplete(props, state) {
|
|
|
74
83
|
}
|
|
75
84
|
});
|
|
76
85
|
const focusFirstItem = () => {
|
|
86
|
+
delayNextActiveDescendant = true;
|
|
77
87
|
const collection = collectionRef();
|
|
78
88
|
if (!collection) {
|
|
79
89
|
setAutoFocusOnMount(true);
|
|
80
90
|
return;
|
|
81
91
|
}
|
|
82
|
-
collection.dispatchEvent(new CustomEvent(
|
|
92
|
+
collection.dispatchEvent(new CustomEvent(FOCUS_EVENT, {
|
|
83
93
|
cancelable: true,
|
|
84
94
|
bubbles: true,
|
|
85
95
|
detail: { focusStrategy: "first" }
|
|
@@ -87,9 +97,12 @@ function createAutocomplete(props, state) {
|
|
|
87
97
|
};
|
|
88
98
|
const clearVirtualFocus = (clearFocusKey = false) => {
|
|
89
99
|
setAutoFocusOnMount(false);
|
|
100
|
+
clearTimeout(activeDescendantTimeout);
|
|
101
|
+
queuedActiveDescendant = null;
|
|
102
|
+
delayNextActiveDescendant = false;
|
|
90
103
|
state.setFocusedNodeId(null);
|
|
91
104
|
const collection = collectionRef();
|
|
92
|
-
if (collection) collection.dispatchEvent(new CustomEvent(
|
|
105
|
+
if (collection) collection.dispatchEvent(new CustomEvent(CLEAR_FOCUS_EVENT, {
|
|
93
106
|
cancelable: true,
|
|
94
107
|
bubbles: true,
|
|
95
108
|
detail: { clearFocusKey }
|
|
@@ -102,7 +115,7 @@ function createAutocomplete(props, state) {
|
|
|
102
115
|
};
|
|
103
116
|
const onKeyDown = (e) => {
|
|
104
117
|
if ("isComposing" in e && e.isComposing) return;
|
|
105
|
-
const focusedNodeId =
|
|
118
|
+
const focusedNodeId = queuedActiveDescendant;
|
|
106
119
|
const collection = collectionRef();
|
|
107
120
|
const ownerDocument = getOwnerDocument(inputRef() ?? collection);
|
|
108
121
|
switch (e.key) {
|
|
@@ -118,7 +131,7 @@ function createAutocomplete(props, state) {
|
|
|
118
131
|
case "PageUp":
|
|
119
132
|
case "PageDown":
|
|
120
133
|
e.preventDefault();
|
|
121
|
-
if (collection) collection.dispatchEvent(new CustomEvent(
|
|
134
|
+
if (collection) collection.dispatchEvent(new CustomEvent(FOCUS_EVENT, {
|
|
122
135
|
cancelable: true,
|
|
123
136
|
bubbles: true
|
|
124
137
|
}));
|
|
@@ -140,7 +153,7 @@ function createAutocomplete(props, state) {
|
|
|
140
153
|
}
|
|
141
154
|
return;
|
|
142
155
|
}
|
|
143
|
-
if (
|
|
156
|
+
if (collection) {
|
|
144
157
|
e.stopPropagation();
|
|
145
158
|
let shouldPerformDefaultAction = true;
|
|
146
159
|
if (focusedNodeId) {
|
|
@@ -160,7 +173,42 @@ function createAutocomplete(props, state) {
|
|
|
160
173
|
const onBlur = (e) => {
|
|
161
174
|
if (!e.isTrusted) return;
|
|
162
175
|
};
|
|
163
|
-
const
|
|
176
|
+
const updateActiveDescendant = (e) => {
|
|
177
|
+
const input = inputRef();
|
|
178
|
+
if (!e.isTrusted && shouldUseVirtualFocus() && input && getActiveElement(getOwnerDocument(input)) !== input && getPointerType() !== "touch") input.focus();
|
|
179
|
+
const target = getEventTarget(e);
|
|
180
|
+
if (e.isTrusted || !target || queuedActiveDescendant === target.id) return;
|
|
181
|
+
const collection = collectionRef();
|
|
182
|
+
clearTimeout(activeDescendantTimeout);
|
|
183
|
+
if (target !== collection) {
|
|
184
|
+
const targetId = target.id;
|
|
185
|
+
if (delayNextActiveDescendant) {
|
|
186
|
+
queuedActiveDescendant = targetId;
|
|
187
|
+
activeDescendantTimeout = setTimeout(() => {
|
|
188
|
+
state.setFocusedNodeId(targetId);
|
|
189
|
+
}, ACTIVE_DESCENDANT_DELAY);
|
|
190
|
+
} else {
|
|
191
|
+
queuedActiveDescendant = targetId;
|
|
192
|
+
state.setFocusedNodeId(targetId);
|
|
193
|
+
}
|
|
194
|
+
} else if (queuedActiveDescendant && !getOwnerDocument(collection)?.getElementById(queuedActiveDescendant)) {
|
|
195
|
+
queuedActiveDescendant = null;
|
|
196
|
+
state.setFocusedNodeId(null);
|
|
197
|
+
}
|
|
198
|
+
delayNextActiveDescendant = false;
|
|
199
|
+
};
|
|
200
|
+
createEffect(() => {
|
|
201
|
+
if (!shouldUseVirtualFocus()) return;
|
|
202
|
+
const collection = collectionRef();
|
|
203
|
+
if (collection) {
|
|
204
|
+
collection.addEventListener("focusin", updateActiveDescendant);
|
|
205
|
+
onCleanup(() => {
|
|
206
|
+
collection.removeEventListener("focusin", updateActiveDescendant);
|
|
207
|
+
clearTimeout(activeDescendantTimeout);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
const filterFn = filter ? (nodeTextValue, node) => filter(nodeTextValue, state.inputValue(), node) : void 0;
|
|
164
212
|
return {
|
|
165
213
|
inputProps: {
|
|
166
214
|
value: state.inputValue,
|