@proyecto-viviana/solidaria 0.2.4 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/actiongroup/createActionGroup.d.ts +29 -0
- package/dist/actiongroup/createActionGroup.d.ts.map +1 -0
- package/dist/actiongroup/index.d.ts +2 -0
- package/dist/actiongroup/index.d.ts.map +1 -0
- package/dist/autocomplete/createAutocomplete.d.ts +6 -2
- package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
- package/dist/breadcrumbs/createBreadcrumbs.d.ts +2 -0
- package/dist/breadcrumbs/createBreadcrumbs.d.ts.map +1 -1
- package/dist/button/createToggleButtonGroup.d.ts +32 -0
- package/dist/button/createToggleButtonGroup.d.ts.map +1 -0
- package/dist/button/index.d.ts +2 -0
- package/dist/button/index.d.ts.map +1 -1
- package/dist/calendar/createCalendarCell.d.ts +2 -0
- package/dist/calendar/createCalendarCell.d.ts.map +1 -1
- package/dist/calendar/createCalendarGrid.d.ts.map +1 -1
- package/dist/calendar/createRangeCalendarCell.d.ts +3 -1
- package/dist/calendar/createRangeCalendarCell.d.ts.map +1 -1
- package/dist/checkbox/createCheckboxGroup.d.ts +5 -1
- package/dist/checkbox/createCheckboxGroup.d.ts.map +1 -1
- package/dist/collections/index.d.ts +56 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/color/createColorArea.d.ts.map +1 -1
- package/dist/color/createColorSlider.d.ts.map +1 -1
- package/dist/color/createColorWheel.d.ts.map +1 -1
- package/dist/combobox/createComboBox.d.ts +6 -0
- package/dist/combobox/createComboBox.d.ts.map +1 -1
- package/dist/datepicker/createDatePicker.d.ts +6 -0
- package/dist/datepicker/createDatePicker.d.ts.map +1 -1
- package/dist/datepicker/createDateRangePicker.d.ts +40 -0
- package/dist/datepicker/createDateRangePicker.d.ts.map +1 -0
- package/dist/datepicker/createDateSegment.d.ts +1 -1
- package/dist/datepicker/createDateSegment.d.ts.map +1 -1
- package/dist/datepicker/createTimeSegment.d.ts +29 -0
- package/dist/datepicker/createTimeSegment.d.ts.map +1 -0
- package/dist/datepicker/index.d.ts +2 -0
- package/dist/datepicker/index.d.ts.map +1 -1
- package/dist/disclosure/createDisclosureGroup.d.ts +2 -1
- package/dist/disclosure/createDisclosureGroup.d.ts.map +1 -1
- package/dist/dnd/createDrag.d.ts.map +1 -1
- package/dist/dnd/createDraggableCollection.d.ts +4 -0
- package/dist/dnd/createDraggableCollection.d.ts.map +1 -1
- package/dist/dnd/createDraggableItem.d.ts.map +1 -1
- package/dist/dnd/createDrop.d.ts.map +1 -1
- package/dist/dnd/createDroppableCollection.d.ts +32 -1
- package/dist/dnd/createDroppableCollection.d.ts.map +1 -1
- package/dist/dnd/createDroppableItem.d.ts.map +1 -1
- package/dist/dnd/index.d.ts +1 -1
- package/dist/dnd/index.d.ts.map +1 -1
- package/dist/grid/createGrid.d.ts.map +1 -1
- package/dist/gridlist/createGridList.d.ts.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4659 -3452
- package/dist/index.js.map +1 -7
- package/dist/index.ssr.js +4659 -3452
- package/dist/index.ssr.js.map +1 -7
- package/dist/interactions/createFocus.d.ts.map +1 -1
- package/dist/interactions/createFocusWithin.d.ts.map +1 -1
- package/dist/link/createLink.d.ts +10 -0
- package/dist/link/createLink.d.ts.map +1 -1
- package/dist/listbox/createListBox.d.ts +1 -0
- package/dist/listbox/createListBox.d.ts.map +1 -1
- package/dist/listbox/createOption.d.ts.map +1 -1
- package/dist/menu/createMenu.d.ts +1 -0
- package/dist/menu/createMenu.d.ts.map +1 -1
- package/dist/meter/createMeter.d.ts.map +1 -1
- package/dist/numberfield/createNumberField.d.ts +18 -0
- package/dist/numberfield/createNumberField.d.ts.map +1 -1
- package/dist/overlays/createModal.d.ts +16 -0
- package/dist/overlays/createModal.d.ts.map +1 -1
- package/dist/overlays/createOverlay.d.ts.map +1 -1
- package/dist/overlays/index.d.ts +1 -1
- package/dist/overlays/index.d.ts.map +1 -1
- package/dist/popover/createOverlayPosition.d.ts.map +1 -1
- package/dist/popover/createPopover.d.ts.map +1 -1
- package/dist/progress/createProgressBar.d.ts.map +1 -1
- package/dist/radio/createRadioGroup.d.ts +2 -2
- package/dist/radio/createRadioGroup.d.ts.map +1 -1
- package/dist/searchfield/createSearchField.d.ts.map +1 -1
- package/dist/select/createHiddenSelect.d.ts.map +1 -1
- package/dist/select/createSelect.d.ts.map +1 -1
- package/dist/slider/createSlider.d.ts.map +1 -1
- package/dist/table/createTable.d.ts.map +1 -1
- package/dist/tabs/createTabs.d.ts +1 -1
- package/dist/tabs/createTabs.d.ts.map +1 -1
- package/dist/tag/createTag.d.ts.map +1 -1
- package/dist/tag/createTagGroup.d.ts.map +1 -1
- package/dist/toast/createToast.d.ts +4 -0
- package/dist/toast/createToast.d.ts.map +1 -1
- package/dist/toast/createToastRegion.d.ts.map +1 -1
- package/dist/toolbar/createToolbar.d.ts.map +1 -1
- package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
- package/dist/tree/createTree.d.ts.map +1 -1
- package/dist/tree/createTreeItem.d.ts.map +1 -1
- package/dist/tree/types.d.ts +4 -0
- package/dist/tree/types.d.ts.map +1 -1
- package/dist/utils/env.d.ts +1 -1
- package/dist/utils/env.d.ts.map +1 -1
- package/dist/utils/platform.d.ts.map +1 -1
- package/dist/visually-hidden/createVisuallyHidden.d.ts.map +1 -1
- package/package.json +8 -6
- package/src/actiongroup/createActionGroup.ts +324 -0
- package/src/actiongroup/index.ts +8 -0
- package/src/autocomplete/createAutocomplete.ts +32 -9
- package/src/breadcrumbs/createBreadcrumbs.ts +10 -15
- package/src/button/createButton.ts +1 -1
- package/src/button/createToggleButtonGroup.ts +128 -0
- package/src/button/index.ts +9 -0
- package/src/calendar/createCalendarCell.ts +6 -4
- package/src/calendar/createCalendarGrid.ts +27 -18
- package/src/calendar/createRangeCalendarCell.ts +26 -9
- package/src/checkbox/createCheckboxGroup.ts +21 -4
- package/src/collections/index.ts +242 -0
- package/src/color/createColorArea.ts +380 -314
- package/src/color/createColorField.ts +137 -137
- package/src/color/createColorSlider.ts +286 -197
- package/src/color/createColorSwatch.ts +40 -40
- package/src/color/createColorWheel.ts +218 -208
- package/src/color/index.ts +24 -24
- package/src/color/types.ts +116 -116
- package/src/combobox/createComboBox.ts +670 -647
- package/src/combobox/index.ts +6 -6
- package/src/datepicker/createDatePicker.ts +54 -16
- package/src/datepicker/createDateRangePicker.ts +246 -0
- package/src/datepicker/createDateSegment.ts +185 -31
- package/src/datepicker/createTimeSegment.ts +370 -0
- package/src/datepicker/index.ts +14 -0
- package/src/dialog/createDialog.ts +120 -120
- package/src/dialog/index.ts +2 -2
- package/src/dialog/types.ts +19 -19
- package/src/disclosure/createDisclosureGroup.ts +5 -2
- package/src/dnd/createDrag.ts +224 -209
- package/src/dnd/createDraggableCollection.ts +96 -63
- package/src/dnd/createDraggableItem.ts +259 -243
- package/src/dnd/createDrop.ts +322 -321
- package/src/dnd/createDroppableCollection.ts +682 -293
- package/src/dnd/createDroppableItem.ts +215 -213
- package/src/dnd/index.ts +55 -47
- package/src/dnd/types.ts +89 -89
- package/src/dnd/utils.ts +294 -294
- package/src/focus/createAutoFocus.ts +321 -321
- package/src/focus/createFocusRestore.ts +313 -313
- package/src/focus/createVirtualFocus.ts +396 -396
- package/src/form/createFormValidation.ts +224 -224
- package/src/form/index.ts +11 -11
- package/src/grid/createGrid.ts +3 -1
- package/src/gridlist/createGridList.ts +16 -0
- package/src/gridlist/createGridListItem.ts +1 -1
- package/src/i18n/NumberFormatter.ts +266 -266
- package/src/i18n/createCollator.ts +79 -79
- package/src/i18n/createDateFormatter.ts +83 -83
- package/src/i18n/createFilter.ts +131 -131
- package/src/i18n/createNumberFormatter.ts +52 -52
- package/src/i18n/index.ts +40 -40
- package/src/i18n/locale.tsx +188 -188
- package/src/i18n/utils.ts +99 -99
- package/src/index.ts +51 -0
- package/src/interactions/createFocus.ts +6 -5
- package/src/interactions/createFocusWithin.ts +6 -5
- package/src/interactions/createLongPress.ts +174 -174
- package/src/interactions/createMove.ts +289 -289
- package/src/interactions/createPress.ts +5 -5
- package/src/landmark/createLandmark.ts +377 -377
- package/src/landmark/index.ts +8 -8
- package/src/link/createLink.ts +23 -8
- package/src/listbox/createListBox.ts +308 -269
- package/src/listbox/createOption.ts +162 -151
- package/src/listbox/index.ts +12 -12
- package/src/live-announcer/announce.ts +322 -322
- package/src/live-announcer/index.ts +9 -9
- package/src/menu/createMenu.ts +405 -396
- package/src/menu/createMenuItem.ts +149 -149
- package/src/menu/createMenuTrigger.ts +88 -88
- package/src/menu/index.ts +18 -18
- package/src/meter/createMeter.ts +1 -6
- package/src/numberfield/createNumberField.ts +311 -268
- package/src/numberfield/index.ts +5 -5
- package/src/overlays/ariaHideOutside.ts +219 -219
- package/src/overlays/createInteractOutside.ts +149 -149
- package/src/overlays/createModal.tsx +238 -202
- package/src/overlays/createOverlay.ts +165 -155
- package/src/overlays/createOverlayTrigger.ts +85 -85
- package/src/overlays/createPreventScroll.ts +266 -266
- package/src/overlays/index.ts +48 -44
- package/src/popover/calculatePosition.ts +6 -6
- package/src/popover/createOverlayPosition.ts +7 -4
- package/src/popover/createPopover.ts +21 -7
- package/src/progress/createProgressBar.ts +6 -1
- package/src/radio/createRadioGroup.ts +88 -14
- package/src/searchfield/createSearchField.ts +241 -186
- package/src/searchfield/index.ts +2 -2
- package/src/select/createHiddenSelect.tsx +263 -236
- package/src/select/createSelect.ts +373 -395
- package/src/select/index.ts +14 -14
- package/src/slider/createSlider.ts +364 -349
- package/src/slider/index.ts +2 -2
- package/src/ssr/index.tsx +370 -370
- package/src/table/createTable.ts +3 -1
- package/src/table/createTableColumnHeader.ts +1 -1
- package/src/table/createTableRow.ts +1 -1
- package/src/tabs/createTabs.ts +80 -51
- package/src/tag/createTag.ts +135 -6
- package/src/tag/createTagGroup.ts +7 -2
- package/src/toast/createToast.ts +8 -2
- package/src/toast/createToastRegion.ts +0 -1
- package/src/toolbar/createToolbar.ts +75 -1
- package/src/tooltip/createTooltip.ts +79 -79
- package/src/tooltip/createTooltipTrigger.ts +226 -222
- package/src/tooltip/index.ts +6 -6
- package/src/tree/createTree.ts +261 -246
- package/src/tree/createTreeItem.ts +282 -233
- package/src/tree/createTreeSelectionCheckbox.ts +68 -68
- package/src/tree/index.ts +16 -16
- package/src/tree/types.ts +91 -87
- package/src/utils/env.ts +55 -54
- package/src/utils/platform.ts +16 -6
- package/src/visually-hidden/createVisuallyHidden.ts +139 -124
- package/src/visually-hidden/index.ts +6 -6
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { onCleanup, type JSX, type Accessor } from 'solid-js';
|
|
2
|
+
import { createButton } from '../button';
|
|
3
|
+
import { filterDOMProps, getEventTarget, mergeProps, nodeContains, isFocusable, focusSafely } from '../utils';
|
|
4
|
+
import { useLocale } from '../i18n';
|
|
5
|
+
import type { Orientation } from '../toolbar';
|
|
6
|
+
import type { Key, ListState } from '@proyecto-viviana/solid-stately';
|
|
7
|
+
|
|
8
|
+
export interface AriaActionGroupProps<T = unknown> {
|
|
9
|
+
/** List items (optional, parity with React Aria prop shape). */
|
|
10
|
+
items?: T[];
|
|
11
|
+
/** Whether the whole action group is disabled. */
|
|
12
|
+
isDisabled?: boolean;
|
|
13
|
+
/** Group orientation. */
|
|
14
|
+
orientation?: Orientation;
|
|
15
|
+
/** Accessible label. */
|
|
16
|
+
'aria-label'?: string;
|
|
17
|
+
/** Labelled-by id. */
|
|
18
|
+
'aria-labelledby'?: string;
|
|
19
|
+
/** Handler called when an item action is triggered. */
|
|
20
|
+
onAction?: (key: Key) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ActionGroupAria {
|
|
24
|
+
actionGroupProps: JSX.HTMLAttributes<HTMLElement>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AriaActionGroupItemProps {
|
|
28
|
+
key: Key;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ActionGroupItemAria {
|
|
32
|
+
buttonProps: JSX.HTMLAttributes<HTMLElement>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ActionGroupData {
|
|
36
|
+
onAction?: (key: Key) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const actionGroupData = new WeakMap<object, ActionGroupData>();
|
|
40
|
+
|
|
41
|
+
const GROUP_ROLE_BY_MODE = {
|
|
42
|
+
none: 'toolbar',
|
|
43
|
+
single: 'radiogroup',
|
|
44
|
+
multiple: 'toolbar',
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
const ITEM_ROLE_BY_MODE = {
|
|
48
|
+
none: undefined,
|
|
49
|
+
single: 'radio',
|
|
50
|
+
multiple: 'checkbox',
|
|
51
|
+
} as const;
|
|
52
|
+
|
|
53
|
+
function isActionGroupDisabled<T>(props: AriaActionGroupProps<T>, state: ListState<T>): boolean {
|
|
54
|
+
if (props.isDisabled) return true;
|
|
55
|
+
const keys = [...state.collection().getKeys()];
|
|
56
|
+
if (keys.length === 0) return true;
|
|
57
|
+
return !keys.some((key) => !state.isDisabled(key));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createActionGroup<T>(
|
|
61
|
+
props: AriaActionGroupProps<T>,
|
|
62
|
+
state: ListState<T>,
|
|
63
|
+
_ref?: Accessor<HTMLElement | null>
|
|
64
|
+
): ActionGroupAria {
|
|
65
|
+
const locale = useLocale();
|
|
66
|
+
let groupRef: HTMLElement | undefined;
|
|
67
|
+
const applyRoleAttributes = (): void => {
|
|
68
|
+
if (!groupRef) return;
|
|
69
|
+
const selectionMode = state.selectionMode();
|
|
70
|
+
const mappedRole = GROUP_ROLE_BY_MODE[selectionMode];
|
|
71
|
+
const nestedToolbar = Boolean(groupRef.parentElement?.closest('[role="toolbar"]'));
|
|
72
|
+
const role = mappedRole === 'toolbar' && nestedToolbar ? 'group' : mappedRole;
|
|
73
|
+
groupRef.setAttribute('role', role);
|
|
74
|
+
if (mappedRole === 'toolbar' && !nestedToolbar) {
|
|
75
|
+
groupRef.setAttribute('aria-orientation', props.orientation ?? 'horizontal');
|
|
76
|
+
} else {
|
|
77
|
+
groupRef.removeAttribute('aria-orientation');
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const getFocusableItems = (root: HTMLElement): HTMLElement[] => {
|
|
82
|
+
const out: HTMLElement[] = [];
|
|
83
|
+
const pushIfFocusable = (el: Element | null | undefined): void => {
|
|
84
|
+
if (!el || !(el instanceof HTMLElement)) return;
|
|
85
|
+
if (isFocusable(el) && el.getAttribute('aria-disabled') !== 'true') {
|
|
86
|
+
out.push(el);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
pushIfFocusable(root);
|
|
91
|
+
for (const node of root.querySelectorAll('*')) {
|
|
92
|
+
pushIfFocusable(node);
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const focusRelative = (root: HTMLElement, direction: 'next' | 'previous' | 'first' | 'last'): HTMLElement | null => {
|
|
98
|
+
const focusables = getFocusableItems(root);
|
|
99
|
+
if (focusables.length === 0) return null;
|
|
100
|
+
|
|
101
|
+
if (direction === 'first') {
|
|
102
|
+
const first = focusables[0];
|
|
103
|
+
focusSafely(first);
|
|
104
|
+
return first;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (direction === 'last') {
|
|
108
|
+
const last = focusables[focusables.length - 1];
|
|
109
|
+
focusSafely(last);
|
|
110
|
+
return last;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const active = root.ownerDocument.activeElement as HTMLElement | null;
|
|
114
|
+
const currentIndex = active ? focusables.indexOf(active) : -1;
|
|
115
|
+
const delta = direction === 'next' ? 1 : -1;
|
|
116
|
+
const nextIndex = currentIndex === -1
|
|
117
|
+
? (direction === 'next' ? 0 : focusables.length - 1)
|
|
118
|
+
: (currentIndex + delta + focusables.length) % focusables.length;
|
|
119
|
+
const next = focusables[nextIndex];
|
|
120
|
+
focusSafely(next);
|
|
121
|
+
return next;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const resolveKeyFromElement = (element: HTMLElement | null): Key | null => {
|
|
125
|
+
if (!element) return null;
|
|
126
|
+
const keyedElement = element.closest('[data-key]');
|
|
127
|
+
if (!(keyedElement instanceof HTMLElement)) return null;
|
|
128
|
+
const rawKey = keyedElement.getAttribute('data-key');
|
|
129
|
+
if (!rawKey) return null;
|
|
130
|
+
for (const item of state.collection()) {
|
|
131
|
+
if (String(item.key) === rawKey) {
|
|
132
|
+
return item.key;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const handleFocusMove = (movedTo: HTMLElement | null): void => {
|
|
139
|
+
const key = resolveKeyFromElement(movedTo);
|
|
140
|
+
if (key == null) return;
|
|
141
|
+
state.setFocusedKey(key);
|
|
142
|
+
if (state.selectionMode() === 'single') {
|
|
143
|
+
state.replaceSelection(key);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const onKeyDown: JSX.EventHandler<HTMLElement, KeyboardEvent> = (e) => {
|
|
148
|
+
const root = groupRef;
|
|
149
|
+
if (!root || isActionGroupDisabled(props, state)) return;
|
|
150
|
+
if (!nodeContains(e.currentTarget, getEventTarget(e))) return;
|
|
151
|
+
|
|
152
|
+
const orientation = props.orientation ?? 'horizontal';
|
|
153
|
+
const isHorizontal = orientation === 'horizontal';
|
|
154
|
+
const isRTL = locale().direction === 'rtl' && isHorizontal;
|
|
155
|
+
|
|
156
|
+
switch (e.key) {
|
|
157
|
+
case 'ArrowRight':
|
|
158
|
+
case 'ArrowDown': {
|
|
159
|
+
if (e.key === 'ArrowRight' && isHorizontal && isRTL) {
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
e.stopPropagation();
|
|
162
|
+
handleFocusMove(focusRelative(root, 'previous'));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if ((e.key === 'ArrowRight' && isHorizontal) || (e.key === 'ArrowDown' && !isHorizontal)) {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
e.stopPropagation();
|
|
168
|
+
handleFocusMove(focusRelative(root, 'next'));
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
case 'ArrowLeft':
|
|
173
|
+
case 'ArrowUp': {
|
|
174
|
+
if (e.key === 'ArrowLeft' && isHorizontal && isRTL) {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
e.stopPropagation();
|
|
177
|
+
handleFocusMove(focusRelative(root, 'next'));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if ((e.key === 'ArrowLeft' && isHorizontal) || (e.key === 'ArrowUp' && !isHorizontal)) {
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
e.stopPropagation();
|
|
183
|
+
handleFocusMove(focusRelative(root, 'previous'));
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
case 'Home': {
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
e.stopPropagation();
|
|
190
|
+
handleFocusMove(focusRelative(root, 'first'));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
case 'End': {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
e.stopPropagation();
|
|
196
|
+
handleFocusMove(focusRelative(root, 'last'));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const actionGroupProps: JSX.HTMLAttributes<HTMLElement> = mergeProps(
|
|
203
|
+
filterDOMProps(props as Record<string, unknown>, { labelable: true }),
|
|
204
|
+
{
|
|
205
|
+
ref: (el: HTMLElement) => {
|
|
206
|
+
groupRef = el;
|
|
207
|
+
applyRoleAttributes();
|
|
208
|
+
queueMicrotask(() => {
|
|
209
|
+
if (!groupRef) return;
|
|
210
|
+
applyRoleAttributes();
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
onKeyDown,
|
|
214
|
+
get 'aria-label'() {
|
|
215
|
+
return props['aria-label'];
|
|
216
|
+
},
|
|
217
|
+
get 'aria-labelledby'() {
|
|
218
|
+
return props['aria-label'] ? undefined : props['aria-labelledby'];
|
|
219
|
+
},
|
|
220
|
+
get 'aria-disabled'() {
|
|
221
|
+
return isActionGroupDisabled(props, state) || undefined;
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
actionGroupData.set(state, {
|
|
227
|
+
get onAction() {
|
|
228
|
+
return props.onAction;
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
onCleanup(() => {
|
|
233
|
+
actionGroupData.delete(state);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return { actionGroupProps };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function createActionGroupItem<T>(
|
|
240
|
+
props: AriaActionGroupItemProps,
|
|
241
|
+
state: ListState<T>
|
|
242
|
+
): ActionGroupItemAria {
|
|
243
|
+
const button = createButton({
|
|
244
|
+
elementType: 'button',
|
|
245
|
+
isDisabled: state.isDisabled(props.key),
|
|
246
|
+
onPress: () => {
|
|
247
|
+
state.setFocusedKey(props.key);
|
|
248
|
+
actionGroupData.get(state)?.onAction?.(props.key);
|
|
249
|
+
if (state.selectionMode() !== 'none') {
|
|
250
|
+
state.select(props.key);
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const isFocused = () => props.key === state.focusedKey();
|
|
256
|
+
const getFirstEnabledKey = (): Key | null => {
|
|
257
|
+
const collection = state.collection();
|
|
258
|
+
let key = collection.getFirstKey();
|
|
259
|
+
while (key != null && state.isDisabled(key)) {
|
|
260
|
+
key = collection.getKeyAfter(key);
|
|
261
|
+
}
|
|
262
|
+
return key;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const getDefaultTabStopKey = (): Key | null => {
|
|
266
|
+
const selectionMode = state.selectionMode();
|
|
267
|
+
if (selectionMode !== 'none') {
|
|
268
|
+
const selectedKeys = state.selectedKeys();
|
|
269
|
+
if (selectedKeys === 'all') {
|
|
270
|
+
return getFirstEnabledKey();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
for (const item of state.collection()) {
|
|
274
|
+
if (!state.isDisabled(item.key) && selectedKeys.has(item.key)) {
|
|
275
|
+
return item.key;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return getFirstEnabledKey();
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
onCleanup(() => {
|
|
284
|
+
if (isFocused()) {
|
|
285
|
+
state.setFocusedKey(null);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const buttonProps: JSX.HTMLAttributes<HTMLElement> = mergeProps(
|
|
290
|
+
button.buttonProps,
|
|
291
|
+
{
|
|
292
|
+
get role() {
|
|
293
|
+
return ITEM_ROLE_BY_MODE[state.selectionMode()];
|
|
294
|
+
},
|
|
295
|
+
get 'aria-checked'() {
|
|
296
|
+
const mode = state.selectionMode();
|
|
297
|
+
if (mode === 'none') return undefined;
|
|
298
|
+
return state.isSelected(props.key);
|
|
299
|
+
},
|
|
300
|
+
get tabIndex() {
|
|
301
|
+
if (state.isDisabled(props.key)) {
|
|
302
|
+
return -1;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (isFocused()) {
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (state.focusedKey() != null) {
|
|
310
|
+
return -1;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const defaultTabStopKey = getDefaultTabStopKey();
|
|
314
|
+
return defaultTabStopKey === props.key ? 0 : -1;
|
|
315
|
+
},
|
|
316
|
+
'data-key': String(props.key),
|
|
317
|
+
onFocus: () => {
|
|
318
|
+
state.setFocusedKey(props.key);
|
|
319
|
+
},
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
return { buttonProps };
|
|
324
|
+
}
|
|
@@ -58,11 +58,15 @@ export interface AutocompleteInputProps {
|
|
|
58
58
|
autoComplete: 'off'
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export interface AriaAutocompleteOptions<
|
|
61
|
+
export interface AriaAutocompleteOptions<_T = unknown> {
|
|
62
62
|
/** Ref accessor for the input element. */
|
|
63
63
|
inputRef: Accessor<HTMLInputElement | undefined>
|
|
64
64
|
/** Ref accessor for the collection element. */
|
|
65
65
|
collectionRef: Accessor<HTMLElement | undefined>
|
|
66
|
+
/** Optional id override for the controlled collection element. */
|
|
67
|
+
collectionId?: string
|
|
68
|
+
/** Optional accessible name for the controlled collection element. */
|
|
69
|
+
collectionAriaLabel?: string
|
|
66
70
|
/**
|
|
67
71
|
* An optional filter function used to determine if an option should be included.
|
|
68
72
|
* @param textValue - The text value of the item
|
|
@@ -81,7 +85,7 @@ export interface AriaAutocompleteOptions<T = unknown> {
|
|
|
81
85
|
disableVirtualFocus?: boolean
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
export interface AutocompleteAria<
|
|
88
|
+
export interface AutocompleteAria<_T = unknown> {
|
|
85
89
|
/** Props for the autocomplete input element. */
|
|
86
90
|
inputProps: AutocompleteInputProps
|
|
87
91
|
/** Props for the collection (ListBox/Menu). */
|
|
@@ -90,6 +94,22 @@ export interface AutocompleteAria<T = unknown> {
|
|
|
90
94
|
filter?: (textValue: string) => boolean
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
function toKeyboardEventInit(e: KeyboardEvent): KeyboardEventInit {
|
|
98
|
+
return {
|
|
99
|
+
key: e.key,
|
|
100
|
+
code: e.code,
|
|
101
|
+
location: e.location,
|
|
102
|
+
repeat: e.repeat,
|
|
103
|
+
isComposing: e.isComposing,
|
|
104
|
+
ctrlKey: e.ctrlKey,
|
|
105
|
+
shiftKey: e.shiftKey,
|
|
106
|
+
altKey: e.altKey,
|
|
107
|
+
metaKey: e.metaKey,
|
|
108
|
+
bubbles: e.bubbles,
|
|
109
|
+
cancelable: e.cancelable,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
93
113
|
// ============================================
|
|
94
114
|
// CONSTANTS
|
|
95
115
|
// ============================================
|
|
@@ -139,11 +159,13 @@ export function createAutocomplete<T = unknown>(
|
|
|
139
159
|
inputRef,
|
|
140
160
|
collectionRef,
|
|
141
161
|
filter,
|
|
162
|
+
collectionId: collectionIdProp,
|
|
163
|
+
collectionAriaLabel,
|
|
142
164
|
disableAutoFocusFirst = false,
|
|
143
165
|
disableVirtualFocus = false,
|
|
144
166
|
} = props
|
|
145
167
|
|
|
146
|
-
const collectionId = createId()
|
|
168
|
+
const collectionId = collectionIdProp ?? createId()
|
|
147
169
|
const [shouldUseVirtualFocus] = createSignal(!disableVirtualFocus)
|
|
148
170
|
let lastInputType = ''
|
|
149
171
|
|
|
@@ -212,12 +234,13 @@ export function createAutocomplete<T = unknown>(
|
|
|
212
234
|
|
|
213
235
|
// Handle keyboard navigation
|
|
214
236
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
215
|
-
if (
|
|
237
|
+
if ('isComposing' in e && e.isComposing) {
|
|
216
238
|
return
|
|
217
239
|
}
|
|
218
240
|
|
|
219
241
|
const focusedNodeId = state.focusedNodeId()
|
|
220
242
|
const collection = collectionRef()
|
|
243
|
+
const ownerDocument = getOwnerDocument(inputRef() ?? collection)
|
|
221
244
|
|
|
222
245
|
switch (e.key) {
|
|
223
246
|
case 'Escape':
|
|
@@ -265,7 +288,7 @@ export function createAutocomplete<T = unknown>(
|
|
|
265
288
|
case 'Enter':
|
|
266
289
|
// Trigger click on focused item
|
|
267
290
|
if (focusedNodeId) {
|
|
268
|
-
const item =
|
|
291
|
+
const item = ownerDocument?.getElementById(focusedNodeId)
|
|
269
292
|
if (item) {
|
|
270
293
|
item.click()
|
|
271
294
|
e.preventDefault()
|
|
@@ -279,12 +302,12 @@ export function createAutocomplete<T = unknown>(
|
|
|
279
302
|
e.stopPropagation()
|
|
280
303
|
|
|
281
304
|
if (focusedNodeId) {
|
|
282
|
-
const item =
|
|
305
|
+
const item = ownerDocument?.getElementById(focusedNodeId)
|
|
283
306
|
if (item) {
|
|
284
|
-
item.dispatchEvent(new KeyboardEvent(e.type, e))
|
|
307
|
+
item.dispatchEvent(new KeyboardEvent(e.type, toKeyboardEventInit(e)))
|
|
285
308
|
}
|
|
286
309
|
} else {
|
|
287
|
-
collection.dispatchEvent(new KeyboardEvent(e.type, e))
|
|
310
|
+
collection.dispatchEvent(new KeyboardEvent(e.type, toKeyboardEventInit(e)))
|
|
288
311
|
}
|
|
289
312
|
}
|
|
290
313
|
}
|
|
@@ -332,7 +355,7 @@ export function createAutocomplete<T = unknown>(
|
|
|
332
355
|
},
|
|
333
356
|
collectionProps: {
|
|
334
357
|
id: collectionId,
|
|
335
|
-
'aria-label':
|
|
358
|
+
'aria-label': collectionAriaLabel,
|
|
336
359
|
shouldUseVirtualFocus: shouldUseVirtualFocus(),
|
|
337
360
|
disallowTypeAhead: shouldUseVirtualFocus(),
|
|
338
361
|
},
|
|
@@ -31,6 +31,8 @@ export interface BreadcrumbsAria {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export interface AriaBreadcrumbItemProps extends Omit<AriaLinkProps, 'aria-current'> {
|
|
34
|
+
/** The DOM id for the breadcrumb item. */
|
|
35
|
+
id?: string;
|
|
34
36
|
/** Whether this is the current/last item in the breadcrumb trail. */
|
|
35
37
|
isCurrent?: boolean;
|
|
36
38
|
/** The type of current location for aria-current. @default 'page' */
|
|
@@ -59,8 +61,9 @@ export function createBreadcrumbs(
|
|
|
59
61
|
const getNavProps = (): Record<string, unknown> => {
|
|
60
62
|
const p = getProps();
|
|
61
63
|
|
|
62
|
-
//
|
|
63
|
-
const ariaLabel =
|
|
64
|
+
// Only apply a default label when no other label source exists.
|
|
65
|
+
const ariaLabel =
|
|
66
|
+
p['aria-label'] ?? (p['aria-labelledby'] ? undefined : 'Breadcrumbs');
|
|
64
67
|
|
|
65
68
|
return mergeProps(
|
|
66
69
|
filterDOMProps(p as Record<string, unknown>, { labelable: true }),
|
|
@@ -89,9 +92,6 @@ export function createBreadcrumbItem(
|
|
|
89
92
|
const isDisabled = () => getProps().isDisabled ?? false;
|
|
90
93
|
const elementType = () => getProps().elementType ?? 'a';
|
|
91
94
|
|
|
92
|
-
// Check if element is a heading
|
|
93
|
-
const isHeading = () => /^h[1-6]$/.test(elementType());
|
|
94
|
-
|
|
95
95
|
// Use createLink for base link behavior
|
|
96
96
|
// Current items are treated as disabled (can't navigate to current page)
|
|
97
97
|
const { linkProps, isPressed } = createLink({
|
|
@@ -102,7 +102,7 @@ export function createBreadcrumbItem(
|
|
|
102
102
|
return elementType();
|
|
103
103
|
},
|
|
104
104
|
get href() {
|
|
105
|
-
return getProps().href;
|
|
105
|
+
return isCurrent() ? undefined : getProps().href;
|
|
106
106
|
},
|
|
107
107
|
get target() {
|
|
108
108
|
return getProps().target;
|
|
@@ -155,8 +155,10 @@ export function createBreadcrumbItem(
|
|
|
155
155
|
const p = getProps();
|
|
156
156
|
const current = isCurrent();
|
|
157
157
|
|
|
158
|
-
// Start with link props
|
|
159
|
-
let baseProps: Record<string, unknown> =
|
|
158
|
+
// Start with link props, forwarding id if provided
|
|
159
|
+
let baseProps: Record<string, unknown> = p.id
|
|
160
|
+
? mergeProps(linkProps, { id: p.id })
|
|
161
|
+
: linkProps;
|
|
160
162
|
|
|
161
163
|
// Add aria-current for current page
|
|
162
164
|
if (current) {
|
|
@@ -165,13 +167,6 @@ export function createBreadcrumbItem(
|
|
|
165
167
|
'aria-current': ariaCurrent,
|
|
166
168
|
});
|
|
167
169
|
|
|
168
|
-
// Adjust tabIndex for current item
|
|
169
|
-
// If autoFocus is true, we want the item focusable
|
|
170
|
-
if (p.autoFocus) {
|
|
171
|
-
baseProps = mergeProps(baseProps, {
|
|
172
|
-
tabIndex: -1,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
170
|
}
|
|
176
171
|
|
|
177
172
|
// Add aria-disabled for disabled items
|
|
@@ -93,7 +93,7 @@ export function createButton(props: AriaButtonProps = {}): ButtonAria {
|
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
if (isLink) {
|
|
96
|
-
additionalProps.href = props.href;
|
|
96
|
+
additionalProps.href = disabled ? undefined : props.href;
|
|
97
97
|
additionalProps.target = props.target;
|
|
98
98
|
additionalProps.rel = props.rel;
|
|
99
99
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { JSX } from 'solid-js';
|
|
2
|
+
import type {
|
|
3
|
+
Key,
|
|
4
|
+
ToggleGroupProps,
|
|
5
|
+
ToggleGroupState,
|
|
6
|
+
} from '@proyecto-viviana/solid-stately';
|
|
7
|
+
import { createToolbar, type Orientation } from '../toolbar';
|
|
8
|
+
import { mergeProps } from '../utils';
|
|
9
|
+
import {
|
|
10
|
+
createToggleButton,
|
|
11
|
+
type AriaToggleButtonProps,
|
|
12
|
+
type ToggleButtonAria,
|
|
13
|
+
} from './createToggleButton';
|
|
14
|
+
|
|
15
|
+
export interface AriaToggleButtonGroupProps extends ToggleGroupProps {
|
|
16
|
+
/**
|
|
17
|
+
* The orientation of the toggle button group.
|
|
18
|
+
* @default 'horizontal'
|
|
19
|
+
*/
|
|
20
|
+
orientation?: Orientation;
|
|
21
|
+
/** Accessible label. */
|
|
22
|
+
'aria-label'?: string;
|
|
23
|
+
/** Labelled-by id. */
|
|
24
|
+
'aria-labelledby'?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ToggleButtonGroupAria {
|
|
28
|
+
/** Props for the group container. */
|
|
29
|
+
groupProps: JSX.HTMLAttributes<HTMLElement>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AriaToggleButtonGroupItemProps
|
|
33
|
+
extends Omit<AriaToggleButtonProps, 'children'> {
|
|
34
|
+
/** Key used in the group selection state. */
|
|
35
|
+
id: Key;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isDisabledValue(
|
|
39
|
+
isDisabled: AriaToggleButtonProps['isDisabled']
|
|
40
|
+
): boolean {
|
|
41
|
+
if (typeof isDisabled === 'function') {
|
|
42
|
+
return isDisabled();
|
|
43
|
+
}
|
|
44
|
+
return !!isDisabled;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Provides ARIA behavior for a toggle button group container.
|
|
49
|
+
*/
|
|
50
|
+
export function createToggleButtonGroup(
|
|
51
|
+
props: AriaToggleButtonGroupProps,
|
|
52
|
+
state: ToggleGroupState
|
|
53
|
+
): ToggleButtonGroupAria {
|
|
54
|
+
const { toolbarProps } = createToolbar({
|
|
55
|
+
get orientation() {
|
|
56
|
+
return props.orientation;
|
|
57
|
+
},
|
|
58
|
+
get 'aria-label'() {
|
|
59
|
+
return props['aria-label'];
|
|
60
|
+
},
|
|
61
|
+
get 'aria-labelledby'() {
|
|
62
|
+
return props['aria-labelledby'];
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const groupProps = mergeProps(toolbarProps as Record<string, unknown>, {
|
|
67
|
+
get role() {
|
|
68
|
+
return state.selectionMode === 'single' ? 'radiogroup' : toolbarProps.role;
|
|
69
|
+
},
|
|
70
|
+
get 'aria-disabled'() {
|
|
71
|
+
return props.isDisabled || undefined;
|
|
72
|
+
},
|
|
73
|
+
}) as JSX.HTMLAttributes<HTMLElement>;
|
|
74
|
+
|
|
75
|
+
return { groupProps };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Provides ARIA behavior for an item within a toggle button group.
|
|
80
|
+
*/
|
|
81
|
+
export function createToggleButtonGroupItem(
|
|
82
|
+
props: AriaToggleButtonGroupItemProps,
|
|
83
|
+
state: ToggleGroupState
|
|
84
|
+
): ToggleButtonAria {
|
|
85
|
+
const { id: _id, ...toggleProps } = props;
|
|
86
|
+
|
|
87
|
+
const toggleButton = createToggleButton({
|
|
88
|
+
...toggleProps,
|
|
89
|
+
get isSelected() {
|
|
90
|
+
return state.selectedKeys.has(props.id);
|
|
91
|
+
},
|
|
92
|
+
onChange(isSelected) {
|
|
93
|
+
state.setSelected(props.id, isSelected);
|
|
94
|
+
props.onChange?.(isSelected);
|
|
95
|
+
},
|
|
96
|
+
get isDisabled() {
|
|
97
|
+
return isDisabledValue(props.isDisabled) || state.isDisabled;
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const baseButtonProps = toggleButton.buttonProps as Record<string, unknown>;
|
|
102
|
+
const buttonProps: Record<string, unknown> = {
|
|
103
|
+
...baseButtonProps,
|
|
104
|
+
get role() {
|
|
105
|
+
if (state.selectionMode === 'single') {
|
|
106
|
+
return 'radio';
|
|
107
|
+
}
|
|
108
|
+
return baseButtonProps.role as string | undefined;
|
|
109
|
+
},
|
|
110
|
+
get 'aria-checked'() {
|
|
111
|
+
if (state.selectionMode !== 'single') {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
return state.selectedKeys.has(props.id);
|
|
115
|
+
},
|
|
116
|
+
get 'aria-pressed'() {
|
|
117
|
+
if (state.selectionMode === 'single') {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
return baseButtonProps['aria-pressed'];
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
...toggleButton,
|
|
126
|
+
buttonProps,
|
|
127
|
+
};
|
|
128
|
+
}
|
package/src/button/index.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
export { createButton } from './createButton';
|
|
2
2
|
export { createToggleButton } from './createToggleButton';
|
|
3
|
+
export {
|
|
4
|
+
createToggleButtonGroup,
|
|
5
|
+
createToggleButtonGroupItem,
|
|
6
|
+
} from './createToggleButtonGroup';
|
|
3
7
|
export type { AriaButtonProps, ButtonAria } from './types';
|
|
4
8
|
export type { AriaToggleButtonProps, ToggleButtonAria } from './createToggleButton';
|
|
9
|
+
export type {
|
|
10
|
+
AriaToggleButtonGroupProps,
|
|
11
|
+
ToggleButtonGroupAria,
|
|
12
|
+
AriaToggleButtonGroupItemProps,
|
|
13
|
+
} from './createToggleButtonGroup';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Based on @react-aria/calendar useCalendarCell
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { createSignal, createMemo,
|
|
8
|
+
import { createSignal, createMemo, createEffect } from 'solid-js';
|
|
9
9
|
import { access, type MaybeAccessor } from '../utils/reactivity';
|
|
10
10
|
import type { CalendarState, CalendarDate, DateValue } from '@proyecto-viviana/solid-stately';
|
|
11
11
|
import { isToday as isTodayUtil, DateFormatter, getLocalTimeZone } from '@internationalized/date';
|
|
@@ -19,6 +19,8 @@ export interface AriaCalendarCellProps {
|
|
|
19
19
|
date: DateValue;
|
|
20
20
|
/** Whether the cell is disabled. */
|
|
21
21
|
isDisabled?: boolean;
|
|
22
|
+
/** Whether the date is outside the current month grid. */
|
|
23
|
+
isOutsideMonth?: boolean;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export interface CalendarCellAria {
|
|
@@ -70,7 +72,7 @@ export function createCalendarCell<T extends CalendarState>(
|
|
|
70
72
|
return getProps().isDisabled || state.isCellDisabled(date());
|
|
71
73
|
});
|
|
72
74
|
const isUnavailable = createMemo(() => state.isCellUnavailable(date()));
|
|
73
|
-
const isOutsideMonth = createMemo(() => state.isOutsideVisibleRange(date()));
|
|
75
|
+
const isOutsideMonth = createMemo(() => getProps().isOutsideMonth ?? state.isOutsideVisibleRange(date()));
|
|
74
76
|
const isToday = createMemo(() => isTodayUtil(date(), timeZone));
|
|
75
77
|
|
|
76
78
|
// Format the date for display
|
|
@@ -105,8 +107,8 @@ export function createCalendarCell<T extends CalendarState>(
|
|
|
105
107
|
setIsPressed(false);
|
|
106
108
|
};
|
|
107
109
|
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
+
// Keep DOM focus synchronized with focused date updates.
|
|
111
|
+
createEffect(() => {
|
|
110
112
|
const element = ref?.();
|
|
111
113
|
if (element && isFocused()) {
|
|
112
114
|
element.focus();
|