@proyecto-viviana/solidaria 0.2.4 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/actiongroup/createActionGroup.d.ts +29 -0
- package/dist/actiongroup/createActionGroup.d.ts.map +1 -0
- package/dist/actiongroup/index.d.ts +2 -0
- package/dist/actiongroup/index.d.ts.map +1 -0
- package/dist/autocomplete/createAutocomplete.d.ts +6 -2
- package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
- package/dist/breadcrumbs/createBreadcrumbs.d.ts +2 -0
- package/dist/breadcrumbs/createBreadcrumbs.d.ts.map +1 -1
- package/dist/button/createToggleButtonGroup.d.ts +32 -0
- package/dist/button/createToggleButtonGroup.d.ts.map +1 -0
- package/dist/button/index.d.ts +2 -0
- package/dist/button/index.d.ts.map +1 -1
- package/dist/calendar/createCalendarCell.d.ts +2 -0
- package/dist/calendar/createCalendarCell.d.ts.map +1 -1
- package/dist/calendar/createCalendarGrid.d.ts.map +1 -1
- package/dist/calendar/createRangeCalendarCell.d.ts +3 -1
- package/dist/calendar/createRangeCalendarCell.d.ts.map +1 -1
- package/dist/checkbox/createCheckboxGroup.d.ts +5 -1
- package/dist/checkbox/createCheckboxGroup.d.ts.map +1 -1
- package/dist/collections/index.d.ts +56 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/color/createColorArea.d.ts.map +1 -1
- package/dist/color/createColorSlider.d.ts.map +1 -1
- package/dist/color/createColorWheel.d.ts.map +1 -1
- package/dist/combobox/createComboBox.d.ts +6 -0
- package/dist/combobox/createComboBox.d.ts.map +1 -1
- package/dist/datepicker/createDatePicker.d.ts +6 -0
- package/dist/datepicker/createDatePicker.d.ts.map +1 -1
- package/dist/datepicker/createDateRangePicker.d.ts +40 -0
- package/dist/datepicker/createDateRangePicker.d.ts.map +1 -0
- package/dist/datepicker/createDateSegment.d.ts +1 -1
- package/dist/datepicker/createDateSegment.d.ts.map +1 -1
- package/dist/datepicker/createTimeSegment.d.ts +29 -0
- package/dist/datepicker/createTimeSegment.d.ts.map +1 -0
- package/dist/datepicker/index.d.ts +2 -0
- package/dist/datepicker/index.d.ts.map +1 -1
- package/dist/disclosure/createDisclosureGroup.d.ts +2 -1
- package/dist/disclosure/createDisclosureGroup.d.ts.map +1 -1
- package/dist/dnd/createDrag.d.ts.map +1 -1
- package/dist/dnd/createDraggableCollection.d.ts +4 -0
- package/dist/dnd/createDraggableCollection.d.ts.map +1 -1
- package/dist/dnd/createDraggableItem.d.ts.map +1 -1
- package/dist/dnd/createDrop.d.ts.map +1 -1
- package/dist/dnd/createDroppableCollection.d.ts +32 -1
- package/dist/dnd/createDroppableCollection.d.ts.map +1 -1
- package/dist/dnd/createDroppableItem.d.ts.map +1 -1
- package/dist/dnd/index.d.ts +1 -1
- package/dist/dnd/index.d.ts.map +1 -1
- package/dist/grid/createGrid.d.ts.map +1 -1
- package/dist/gridlist/createGridList.d.ts.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4659 -3452
- package/dist/index.js.map +1 -7
- package/dist/index.ssr.js +4659 -3452
- package/dist/index.ssr.js.map +1 -7
- package/dist/interactions/createFocus.d.ts.map +1 -1
- package/dist/interactions/createFocusWithin.d.ts.map +1 -1
- package/dist/link/createLink.d.ts +10 -0
- package/dist/link/createLink.d.ts.map +1 -1
- package/dist/listbox/createListBox.d.ts +1 -0
- package/dist/listbox/createListBox.d.ts.map +1 -1
- package/dist/listbox/createOption.d.ts.map +1 -1
- package/dist/menu/createMenu.d.ts +1 -0
- package/dist/menu/createMenu.d.ts.map +1 -1
- package/dist/meter/createMeter.d.ts.map +1 -1
- package/dist/numberfield/createNumberField.d.ts +18 -0
- package/dist/numberfield/createNumberField.d.ts.map +1 -1
- package/dist/overlays/createModal.d.ts +16 -0
- package/dist/overlays/createModal.d.ts.map +1 -1
- package/dist/overlays/createOverlay.d.ts.map +1 -1
- package/dist/overlays/index.d.ts +1 -1
- package/dist/overlays/index.d.ts.map +1 -1
- package/dist/popover/createOverlayPosition.d.ts.map +1 -1
- package/dist/popover/createPopover.d.ts.map +1 -1
- package/dist/progress/createProgressBar.d.ts.map +1 -1
- package/dist/radio/createRadioGroup.d.ts +2 -2
- package/dist/radio/createRadioGroup.d.ts.map +1 -1
- package/dist/searchfield/createSearchField.d.ts.map +1 -1
- package/dist/select/createHiddenSelect.d.ts.map +1 -1
- package/dist/select/createSelect.d.ts.map +1 -1
- package/dist/slider/createSlider.d.ts.map +1 -1
- package/dist/table/createTable.d.ts.map +1 -1
- package/dist/tabs/createTabs.d.ts +1 -1
- package/dist/tabs/createTabs.d.ts.map +1 -1
- package/dist/tag/createTag.d.ts.map +1 -1
- package/dist/tag/createTagGroup.d.ts.map +1 -1
- package/dist/toast/createToast.d.ts +4 -0
- package/dist/toast/createToast.d.ts.map +1 -1
- package/dist/toast/createToastRegion.d.ts.map +1 -1
- package/dist/toolbar/createToolbar.d.ts.map +1 -1
- package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
- package/dist/tree/createTree.d.ts.map +1 -1
- package/dist/tree/createTreeItem.d.ts.map +1 -1
- package/dist/tree/types.d.ts +4 -0
- package/dist/tree/types.d.ts.map +1 -1
- package/dist/utils/env.d.ts +1 -1
- package/dist/utils/env.d.ts.map +1 -1
- package/dist/utils/platform.d.ts.map +1 -1
- package/dist/visually-hidden/createVisuallyHidden.d.ts.map +1 -1
- package/package.json +8 -6
- package/src/actiongroup/createActionGroup.ts +324 -0
- package/src/actiongroup/index.ts +8 -0
- package/src/autocomplete/createAutocomplete.ts +32 -9
- package/src/breadcrumbs/createBreadcrumbs.ts +10 -15
- package/src/button/createButton.ts +1 -1
- package/src/button/createToggleButtonGroup.ts +128 -0
- package/src/button/index.ts +9 -0
- package/src/calendar/createCalendarCell.ts +6 -4
- package/src/calendar/createCalendarGrid.ts +27 -18
- package/src/calendar/createRangeCalendarCell.ts +26 -9
- package/src/checkbox/createCheckboxGroup.ts +21 -4
- package/src/collections/index.ts +242 -0
- package/src/color/createColorArea.ts +380 -314
- package/src/color/createColorField.ts +137 -137
- package/src/color/createColorSlider.ts +286 -197
- package/src/color/createColorSwatch.ts +40 -40
- package/src/color/createColorWheel.ts +218 -208
- package/src/color/index.ts +24 -24
- package/src/color/types.ts +116 -116
- package/src/combobox/createComboBox.ts +670 -647
- package/src/combobox/index.ts +6 -6
- package/src/datepicker/createDatePicker.ts +54 -16
- package/src/datepicker/createDateRangePicker.ts +246 -0
- package/src/datepicker/createDateSegment.ts +185 -31
- package/src/datepicker/createTimeSegment.ts +370 -0
- package/src/datepicker/index.ts +14 -0
- package/src/dialog/createDialog.ts +120 -120
- package/src/dialog/index.ts +2 -2
- package/src/dialog/types.ts +19 -19
- package/src/disclosure/createDisclosureGroup.ts +5 -2
- package/src/dnd/createDrag.ts +224 -209
- package/src/dnd/createDraggableCollection.ts +96 -63
- package/src/dnd/createDraggableItem.ts +259 -243
- package/src/dnd/createDrop.ts +322 -321
- package/src/dnd/createDroppableCollection.ts +682 -293
- package/src/dnd/createDroppableItem.ts +215 -213
- package/src/dnd/index.ts +55 -47
- package/src/dnd/types.ts +89 -89
- package/src/dnd/utils.ts +294 -294
- package/src/focus/createAutoFocus.ts +321 -321
- package/src/focus/createFocusRestore.ts +313 -313
- package/src/focus/createVirtualFocus.ts +396 -396
- package/src/form/createFormValidation.ts +224 -224
- package/src/form/index.ts +11 -11
- package/src/grid/createGrid.ts +3 -1
- package/src/gridlist/createGridList.ts +16 -0
- package/src/gridlist/createGridListItem.ts +1 -1
- package/src/i18n/NumberFormatter.ts +266 -266
- package/src/i18n/createCollator.ts +79 -79
- package/src/i18n/createDateFormatter.ts +83 -83
- package/src/i18n/createFilter.ts +131 -131
- package/src/i18n/createNumberFormatter.ts +52 -52
- package/src/i18n/index.ts +40 -40
- package/src/i18n/locale.tsx +188 -188
- package/src/i18n/utils.ts +99 -99
- package/src/index.ts +51 -0
- package/src/interactions/createFocus.ts +6 -5
- package/src/interactions/createFocusWithin.ts +6 -5
- package/src/interactions/createLongPress.ts +174 -174
- package/src/interactions/createMove.ts +289 -289
- package/src/interactions/createPress.ts +5 -5
- package/src/landmark/createLandmark.ts +377 -377
- package/src/landmark/index.ts +8 -8
- package/src/link/createLink.ts +23 -8
- package/src/listbox/createListBox.ts +308 -269
- package/src/listbox/createOption.ts +162 -151
- package/src/listbox/index.ts +12 -12
- package/src/live-announcer/announce.ts +322 -322
- package/src/live-announcer/index.ts +9 -9
- package/src/menu/createMenu.ts +405 -396
- package/src/menu/createMenuItem.ts +149 -149
- package/src/menu/createMenuTrigger.ts +88 -88
- package/src/menu/index.ts +18 -18
- package/src/meter/createMeter.ts +1 -6
- package/src/numberfield/createNumberField.ts +311 -268
- package/src/numberfield/index.ts +5 -5
- package/src/overlays/ariaHideOutside.ts +219 -219
- package/src/overlays/createInteractOutside.ts +149 -149
- package/src/overlays/createModal.tsx +238 -202
- package/src/overlays/createOverlay.ts +165 -155
- package/src/overlays/createOverlayTrigger.ts +85 -85
- package/src/overlays/createPreventScroll.ts +266 -266
- package/src/overlays/index.ts +48 -44
- package/src/popover/calculatePosition.ts +6 -6
- package/src/popover/createOverlayPosition.ts +7 -4
- package/src/popover/createPopover.ts +21 -7
- package/src/progress/createProgressBar.ts +6 -1
- package/src/radio/createRadioGroup.ts +88 -14
- package/src/searchfield/createSearchField.ts +241 -186
- package/src/searchfield/index.ts +2 -2
- package/src/select/createHiddenSelect.tsx +263 -236
- package/src/select/createSelect.ts +373 -395
- package/src/select/index.ts +14 -14
- package/src/slider/createSlider.ts +364 -349
- package/src/slider/index.ts +2 -2
- package/src/ssr/index.tsx +370 -370
- package/src/table/createTable.ts +3 -1
- package/src/table/createTableColumnHeader.ts +1 -1
- package/src/table/createTableRow.ts +1 -1
- package/src/tabs/createTabs.ts +80 -51
- package/src/tag/createTag.ts +135 -6
- package/src/tag/createTagGroup.ts +7 -2
- package/src/toast/createToast.ts +8 -2
- package/src/toast/createToastRegion.ts +0 -1
- package/src/toolbar/createToolbar.ts +75 -1
- package/src/tooltip/createTooltip.ts +79 -79
- package/src/tooltip/createTooltipTrigger.ts +226 -222
- package/src/tooltip/index.ts +6 -6
- package/src/tree/createTree.ts +261 -246
- package/src/tree/createTreeItem.ts +282 -233
- package/src/tree/createTreeSelectionCheckbox.ts +68 -68
- package/src/tree/index.ts +16 -16
- package/src/tree/types.ts +91 -87
- package/src/utils/env.ts +55 -54
- package/src/utils/platform.ts +16 -6
- package/src/visually-hidden/createVisuallyHidden.ts +139 -124
- package/src/visually-hidden/index.ts +6 -6
package/src/table/createTable.ts
CHANGED
|
@@ -504,7 +504,9 @@ export function createTable<T extends object>(
|
|
|
504
504
|
'aria-labelledby': p['aria-labelledby'],
|
|
505
505
|
'aria-describedby': p['aria-describedby'],
|
|
506
506
|
'aria-multiselectable': s.selectionMode === 'multiple' ? 'true' : undefined,
|
|
507
|
-
|
|
507
|
+
// Keep the grid itself tabbable so keyboard users can enter
|
|
508
|
+
// row/cell navigation without requiring a prior pointer interaction.
|
|
509
|
+
tabIndex: 0,
|
|
508
510
|
onKeyDown,
|
|
509
511
|
onFocus,
|
|
510
512
|
onBlur,
|
|
@@ -39,7 +39,7 @@ export function createTableColumnHeader<T extends object>(
|
|
|
39
39
|
const p = props();
|
|
40
40
|
const s = state();
|
|
41
41
|
|
|
42
|
-
if (p.allowsSorting && (e.key === 'Enter' || e.key === ' ')) {
|
|
42
|
+
if (p.allowsSorting && (e.key === 'Enter' || e.key === ' ' || e.key === 'Space' || e.key === 'Spacebar')) {
|
|
43
43
|
e.preventDefault();
|
|
44
44
|
s.sort(p.node.key);
|
|
45
45
|
}
|
|
@@ -76,7 +76,7 @@ export function createTableRow<T extends object>(
|
|
|
76
76
|
|
|
77
77
|
if (isDisabled()) return;
|
|
78
78
|
|
|
79
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
79
|
+
if (e.key === 'Enter' || e.key === ' ' || e.key === 'Space' || e.key === 'Spacebar') {
|
|
80
80
|
e.preventDefault();
|
|
81
81
|
|
|
82
82
|
// Get table metadata for actions
|
package/src/tabs/createTabs.ts
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* Based on @react-aria/tabs.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { type Accessor,
|
|
6
|
+
import { type Accessor, createEffect, createMemo } from 'solid-js';
|
|
7
7
|
import { createFocusRing } from '../interactions';
|
|
8
8
|
import { createPress } from '../interactions';
|
|
9
9
|
import { createHover } from '../interactions';
|
|
10
10
|
import { createId } from '../ssr';
|
|
11
|
+
import { useLocale } from '../i18n';
|
|
11
12
|
import type { Key, Collection, CollectionNode } from '@proyecto-viviana/solid-stately';
|
|
12
13
|
|
|
13
14
|
// ============================================
|
|
@@ -117,7 +118,7 @@ export interface TabPanelAria {
|
|
|
117
118
|
tabPanelProps: {
|
|
118
119
|
id: string;
|
|
119
120
|
role: 'tabpanel';
|
|
120
|
-
'aria-labelledby'
|
|
121
|
+
'aria-labelledby'?: string;
|
|
121
122
|
'aria-label'?: string;
|
|
122
123
|
'aria-describedby'?: string;
|
|
123
124
|
tabIndex: number;
|
|
@@ -227,6 +228,7 @@ export function createTabList<T>(
|
|
|
227
228
|
props: AriaTabListProps,
|
|
228
229
|
state: TabListState<T>
|
|
229
230
|
): TabListAria {
|
|
231
|
+
const locale = useLocale();
|
|
230
232
|
const orientation = () => props.orientation ?? state.orientation() ?? 'horizontal';
|
|
231
233
|
const keyboardActivation = () => props.keyboardActivation ?? state.keyboardActivation() ?? 'automatic';
|
|
232
234
|
|
|
@@ -238,17 +240,17 @@ export function createTabList<T>(
|
|
|
238
240
|
|
|
239
241
|
let nextKey: Key | null = null;
|
|
240
242
|
const isHorizontal = orientation() === 'horizontal';
|
|
243
|
+
const isRTL = locale().direction === 'rtl';
|
|
241
244
|
|
|
242
245
|
switch (e.key) {
|
|
243
246
|
case 'ArrowLeft':
|
|
244
247
|
if (isHorizontal) {
|
|
245
|
-
|
|
246
|
-
nextKey = getPreviousKey(state, currentKey);
|
|
248
|
+
nextKey = isRTL ? getNextKey(state, currentKey) : getPreviousKey(state, currentKey);
|
|
247
249
|
}
|
|
248
250
|
break;
|
|
249
251
|
case 'ArrowRight':
|
|
250
252
|
if (isHorizontal) {
|
|
251
|
-
nextKey = getNextKey(state, currentKey);
|
|
253
|
+
nextKey = isRTL ? getPreviousKey(state, currentKey) : getNextKey(state, currentKey);
|
|
252
254
|
}
|
|
253
255
|
break;
|
|
254
256
|
case 'ArrowUp':
|
|
@@ -282,11 +284,6 @@ export function createTabList<T>(
|
|
|
282
284
|
if (nextKey !== null) {
|
|
283
285
|
e.preventDefault();
|
|
284
286
|
state.setFocusedKey(nextKey);
|
|
285
|
-
|
|
286
|
-
// In automatic mode, selection follows focus
|
|
287
|
-
if (keyboardActivation() === 'automatic') {
|
|
288
|
-
state.setSelectedKey(nextKey);
|
|
289
|
-
}
|
|
290
287
|
}
|
|
291
288
|
};
|
|
292
289
|
|
|
@@ -348,8 +345,14 @@ export function createTab<T>(
|
|
|
348
345
|
return isDisabled();
|
|
349
346
|
},
|
|
350
347
|
onPress: () => {
|
|
351
|
-
|
|
352
|
-
state.
|
|
348
|
+
const tabKey = key();
|
|
349
|
+
const wasSelected = state.selectedKey() === tabKey;
|
|
350
|
+
|
|
351
|
+
state.setFocusedKey(tabKey);
|
|
352
|
+
|
|
353
|
+
if (state.keyboardActivation() === 'manual' || wasSelected) {
|
|
354
|
+
state.setSelectedKey(tabKey);
|
|
355
|
+
}
|
|
353
356
|
},
|
|
354
357
|
});
|
|
355
358
|
|
|
@@ -365,60 +368,71 @@ export function createTab<T>(
|
|
|
365
368
|
const tabPanelId = generateTabPanelId(state, key());
|
|
366
369
|
|
|
367
370
|
// Helper to safely call event handlers that may be bound tuples
|
|
368
|
-
const callHandler = <E extends Event>(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
handler
|
|
371
|
+
const callHandler = <E extends Event>(handler: unknown, event: E) => {
|
|
372
|
+
if (typeof handler === 'function') {
|
|
373
|
+
(handler as (e: E) => void)(event);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (
|
|
377
|
+
Array.isArray(handler) &&
|
|
378
|
+
handler.length >= 2 &&
|
|
379
|
+
typeof handler[1] === 'function'
|
|
380
|
+
) {
|
|
381
|
+
(handler[1] as (this: unknown, e: E) => void).call(handler[0], event);
|
|
377
382
|
}
|
|
378
383
|
};
|
|
379
384
|
|
|
380
385
|
// Focus management
|
|
381
386
|
const handleFocus = (e: FocusEvent) => {
|
|
382
387
|
state.setFocusedKey(key());
|
|
383
|
-
callHandler(focusProps.onFocus
|
|
388
|
+
callHandler(focusProps.onFocus, e);
|
|
384
389
|
};
|
|
385
390
|
|
|
386
391
|
// Combine all handlers
|
|
387
392
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
388
|
-
callHandler(pressProps.onKeyDown
|
|
393
|
+
callHandler(pressProps.onKeyDown, e);
|
|
389
394
|
};
|
|
390
395
|
|
|
391
396
|
const handleMouseDown = (e: MouseEvent) => {
|
|
392
|
-
callHandler(pressProps.onMouseDown
|
|
397
|
+
callHandler(pressProps.onMouseDown, e);
|
|
393
398
|
};
|
|
394
399
|
|
|
395
400
|
const handlePointerDown = (e: PointerEvent) => {
|
|
396
|
-
callHandler(pressProps.onPointerDown
|
|
401
|
+
callHandler(pressProps.onPointerDown, e);
|
|
397
402
|
};
|
|
398
403
|
|
|
399
404
|
const handleClick = (e: MouseEvent) => {
|
|
400
|
-
callHandler(pressProps.onClick
|
|
405
|
+
callHandler(pressProps.onClick, e);
|
|
401
406
|
};
|
|
402
407
|
|
|
403
|
-
//
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
408
|
+
// Keep DOM focus aligned with focusedKey updates from keyboard delegate.
|
|
409
|
+
createEffect(() => {
|
|
410
|
+
const element = ref?.();
|
|
411
|
+
if (!isFocused() || !element) return;
|
|
412
|
+
|
|
413
|
+
const activeElement = element.ownerDocument?.activeElement;
|
|
414
|
+
if (activeElement !== element) {
|
|
415
|
+
element.focus();
|
|
416
|
+
}
|
|
411
417
|
});
|
|
412
418
|
|
|
413
419
|
return {
|
|
414
420
|
tabProps: {
|
|
415
421
|
id: tabId,
|
|
416
422
|
role: 'tab',
|
|
417
|
-
'aria-selected'
|
|
418
|
-
|
|
419
|
-
|
|
423
|
+
get 'aria-selected'() {
|
|
424
|
+
return isSelected();
|
|
425
|
+
},
|
|
426
|
+
get 'aria-disabled'() {
|
|
427
|
+
return isDisabled() || undefined;
|
|
428
|
+
},
|
|
429
|
+
get 'aria-controls'() {
|
|
430
|
+
return isSelected() ? tabPanelId : undefined;
|
|
431
|
+
},
|
|
420
432
|
'aria-label': props['aria-label'],
|
|
421
|
-
tabIndex
|
|
433
|
+
get tabIndex() {
|
|
434
|
+
return isSelected() && !isDisabled() ? 0 : -1;
|
|
435
|
+
},
|
|
422
436
|
onKeyDown: handleKeyDown,
|
|
423
437
|
onMouseDown: handleMouseDown,
|
|
424
438
|
onPointerDown: handlePointerDown,
|
|
@@ -441,27 +455,42 @@ export function createTabPanel<T>(
|
|
|
441
455
|
props: AriaTabPanelProps,
|
|
442
456
|
state: TabListState<T> | null
|
|
443
457
|
): TabPanelAria {
|
|
444
|
-
|
|
458
|
+
const fallbackId = createId();
|
|
459
|
+
|
|
460
|
+
// Shared panel pattern: if no explicit id is provided, associate the panel
|
|
461
|
+
// with the currently selected tab.
|
|
462
|
+
const associatedKey = createMemo<Key | null>(() => {
|
|
463
|
+
if (state === null) return null;
|
|
464
|
+
return props.id ?? state.selectedKey();
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// If state is null, the panel is always visible (SSR fallback).
|
|
445
468
|
const isSelected = createMemo(() => {
|
|
446
469
|
if (state === null) return true;
|
|
447
|
-
if (props.id === undefined)
|
|
470
|
+
if (props.id === undefined) {
|
|
471
|
+
return state.selectedKey() !== null;
|
|
472
|
+
}
|
|
448
473
|
return state.selectedKey() === props.id;
|
|
449
474
|
});
|
|
450
475
|
|
|
451
|
-
// Generate IDs based on the associated tab key
|
|
452
|
-
const tabPanelId = state && props.id !== undefined
|
|
453
|
-
? generateTabPanelId(state, props.id)
|
|
454
|
-
: createId();
|
|
455
|
-
|
|
456
|
-
const tabId = state && props.id !== undefined
|
|
457
|
-
? generateTabId(state, props.id)
|
|
458
|
-
: '';
|
|
459
|
-
|
|
460
476
|
return {
|
|
461
477
|
tabPanelProps: {
|
|
462
|
-
id
|
|
478
|
+
get id() {
|
|
479
|
+
const key = associatedKey();
|
|
480
|
+
if (state && key !== null) {
|
|
481
|
+
return generateTabPanelId(state, key);
|
|
482
|
+
}
|
|
483
|
+
return fallbackId;
|
|
484
|
+
},
|
|
463
485
|
role: 'tabpanel',
|
|
464
|
-
|
|
486
|
+
get 'aria-labelledby'() {
|
|
487
|
+
if (props['aria-labelledby']) return props['aria-labelledby'];
|
|
488
|
+
const key = associatedKey();
|
|
489
|
+
if (state && key !== null) {
|
|
490
|
+
return generateTabId(state, key);
|
|
491
|
+
}
|
|
492
|
+
return undefined;
|
|
493
|
+
},
|
|
465
494
|
'aria-label': props['aria-label'],
|
|
466
495
|
'aria-describedby': props['aria-describedby'],
|
|
467
496
|
// Make panel focusable if no tabbable children
|
package/src/tag/createTag.ts
CHANGED
|
@@ -83,15 +83,92 @@ export function createTag<T>(
|
|
|
83
83
|
return state.isSelected(key());
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
+
const isSelectable = createMemo(() => state.selectionMode() !== 'none');
|
|
87
|
+
|
|
86
88
|
const isFocused = createMemo(() => {
|
|
87
89
|
return state.focusedKey() === key();
|
|
88
90
|
});
|
|
89
91
|
|
|
92
|
+
const getFirstFocusableKey = (): Key | null => {
|
|
93
|
+
const collection = state.collection();
|
|
94
|
+
let candidate = collection.getFirstKey();
|
|
95
|
+
while (candidate != null && state.isDisabled(candidate)) {
|
|
96
|
+
candidate = collection.getKeyAfter(candidate);
|
|
97
|
+
}
|
|
98
|
+
return candidate;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const getLastFocusableKey = (): Key | null => {
|
|
102
|
+
const collection = state.collection();
|
|
103
|
+
let candidate = collection.getLastKey();
|
|
104
|
+
while (candidate != null && state.isDisabled(candidate)) {
|
|
105
|
+
candidate = collection.getKeyBefore(candidate);
|
|
106
|
+
}
|
|
107
|
+
return candidate;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const getNextFocusableKey = (fromKey: Key): Key | null => {
|
|
111
|
+
const collection = state.collection();
|
|
112
|
+
let candidate = collection.getKeyAfter(fromKey);
|
|
113
|
+
while (candidate != null && state.isDisabled(candidate)) {
|
|
114
|
+
candidate = collection.getKeyAfter(candidate);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (candidate != null) {
|
|
118
|
+
return candidate;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return getFirstFocusableKey();
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const getPreviousFocusableKey = (fromKey: Key): Key | null => {
|
|
125
|
+
const collection = state.collection();
|
|
126
|
+
let candidate = collection.getKeyBefore(fromKey);
|
|
127
|
+
while (candidate != null && state.isDisabled(candidate)) {
|
|
128
|
+
candidate = collection.getKeyBefore(candidate);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (candidate != null) {
|
|
132
|
+
return candidate;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return getLastFocusableKey();
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const focusKey = (nextKey: Key | null) => {
|
|
139
|
+
if (nextKey == null) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
state.setFocusedKey(nextKey);
|
|
144
|
+
const currentElement = ref();
|
|
145
|
+
|
|
146
|
+
if (!currentElement) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (nextKey === key()) {
|
|
151
|
+
currentElement.focus();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const tagList = currentElement.parentElement;
|
|
156
|
+
if (!tagList) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const nextTag = Array.from(tagList.querySelectorAll<HTMLElement>('[role="option"]'))
|
|
161
|
+
.find((el) => el.getAttribute('data-key') === String(nextKey));
|
|
162
|
+
|
|
163
|
+
nextTag?.focus();
|
|
164
|
+
};
|
|
165
|
+
|
|
90
166
|
// Handle press for selection
|
|
91
167
|
const { pressProps, isPressed } = createPress({
|
|
92
168
|
isDisabled,
|
|
93
169
|
onPress: () => {
|
|
94
170
|
if (!isDisabled()) {
|
|
171
|
+
state.setFocusedKey(key());
|
|
95
172
|
state.toggleSelection(key());
|
|
96
173
|
}
|
|
97
174
|
},
|
|
@@ -100,12 +177,38 @@ export function createTag<T>(
|
|
|
100
177
|
// Handle focusable
|
|
101
178
|
const { focusableProps } = createFocusable({
|
|
102
179
|
isDisabled,
|
|
180
|
+
onFocus: () => {
|
|
181
|
+
state.setFocusedKey(key());
|
|
182
|
+
},
|
|
103
183
|
}, ref);
|
|
104
184
|
|
|
105
|
-
// Handle keyboard for removal
|
|
185
|
+
// Handle keyboard for navigation and removal
|
|
106
186
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
107
187
|
if (isDisabled()) return;
|
|
108
188
|
|
|
189
|
+
switch (e.key) {
|
|
190
|
+
case 'ArrowRight':
|
|
191
|
+
case 'ArrowDown':
|
|
192
|
+
e.preventDefault();
|
|
193
|
+
focusKey(getNextFocusableKey(key()));
|
|
194
|
+
return;
|
|
195
|
+
case 'ArrowLeft':
|
|
196
|
+
case 'ArrowUp':
|
|
197
|
+
e.preventDefault();
|
|
198
|
+
focusKey(getPreviousFocusableKey(key()));
|
|
199
|
+
return;
|
|
200
|
+
case 'Home':
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
focusKey(getFirstFocusableKey());
|
|
203
|
+
return;
|
|
204
|
+
case 'End':
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
focusKey(getLastFocusableKey());
|
|
207
|
+
return;
|
|
208
|
+
default:
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
|
|
109
212
|
if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
110
213
|
e.preventDefault();
|
|
111
214
|
const data = getData();
|
|
@@ -127,10 +230,35 @@ export function createTag<T>(
|
|
|
127
230
|
// Compute tabIndex
|
|
128
231
|
const tabIndex = createMemo(() => {
|
|
129
232
|
if (isDisabled()) return -1;
|
|
130
|
-
|
|
131
|
-
if (isFocused()
|
|
233
|
+
|
|
234
|
+
if (isFocused()) {
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (state.focusedKey() !== null) {
|
|
239
|
+
return -1;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const collection = state.collection();
|
|
243
|
+
let defaultTabStop: Key | null = null;
|
|
244
|
+
|
|
245
|
+
if (state.selectionMode() !== 'none') {
|
|
246
|
+
for (const item of collection) {
|
|
247
|
+
if (!state.isDisabled(item.key) && state.isSelected(item.key)) {
|
|
248
|
+
defaultTabStop = item.key;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (defaultTabStop == null) {
|
|
255
|
+
defaultTabStop = getFirstFocusableKey();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (key() === defaultTabStop) {
|
|
132
259
|
return 0;
|
|
133
260
|
}
|
|
261
|
+
|
|
134
262
|
return -1;
|
|
135
263
|
});
|
|
136
264
|
|
|
@@ -147,9 +275,10 @@ export function createTag<T>(
|
|
|
147
275
|
get rowProps() {
|
|
148
276
|
return mergeProps(domProps(), focusableProps as Record<string, unknown>, pressProps as Record<string, unknown>, {
|
|
149
277
|
id: rowId,
|
|
150
|
-
role: '
|
|
278
|
+
role: 'option',
|
|
151
279
|
tabIndex: tabIndex(),
|
|
152
|
-
'
|
|
280
|
+
'data-key': String(key()),
|
|
281
|
+
'aria-selected': isSelectable() ? isSelected() : undefined,
|
|
153
282
|
'aria-disabled': isDisabled() || undefined,
|
|
154
283
|
onKeyDown: handleKeyDown,
|
|
155
284
|
});
|
|
@@ -157,7 +286,7 @@ export function createTag<T>(
|
|
|
157
286
|
get gridCellProps() {
|
|
158
287
|
return {
|
|
159
288
|
id: cellId,
|
|
160
|
-
role: '
|
|
289
|
+
role: 'presentation',
|
|
161
290
|
'aria-describedby': allowsRemoving() ? removeButtonId : undefined,
|
|
162
291
|
};
|
|
163
292
|
},
|
|
@@ -82,6 +82,10 @@ export function createTagGroup<T>(
|
|
|
82
82
|
const id = createId(getProps().id);
|
|
83
83
|
const descriptionId = createId();
|
|
84
84
|
const errorMessageId = createId();
|
|
85
|
+
const getFallbackAriaLabel = () => {
|
|
86
|
+
const p = getProps();
|
|
87
|
+
return !p.label && !p['aria-label'] && !p['aria-labelledby'] ? 'Tag list' : undefined;
|
|
88
|
+
};
|
|
85
89
|
|
|
86
90
|
// Filter DOM props
|
|
87
91
|
const domProps = () => filterDOMProps(getProps() as unknown as Record<string, unknown>, { labelable: true });
|
|
@@ -89,7 +93,7 @@ export function createTagGroup<T>(
|
|
|
89
93
|
// Create label handling
|
|
90
94
|
const { labelProps, fieldProps } = createLabel({
|
|
91
95
|
get label() { return getProps().label; },
|
|
92
|
-
get 'aria-label'() { return getProps()['aria-label']; },
|
|
96
|
+
get 'aria-label'() { return getProps()['aria-label'] ?? getFallbackAriaLabel(); },
|
|
93
97
|
get 'aria-labelledby'() { return getProps()['aria-labelledby']; },
|
|
94
98
|
labelElementType: 'span',
|
|
95
99
|
});
|
|
@@ -130,7 +134,8 @@ export function createTagGroup<T>(
|
|
|
130
134
|
|
|
131
135
|
return mergeProps(domProps(), fieldProps as Record<string, unknown>, {
|
|
132
136
|
id,
|
|
133
|
-
role: hasItems ? '
|
|
137
|
+
role: hasItems ? 'listbox' : 'group',
|
|
138
|
+
'aria-multiselectable': hasItems && state.selectionMode() === 'multiple' ? true : undefined,
|
|
134
139
|
'aria-atomic': false,
|
|
135
140
|
'aria-relevant': 'additions',
|
|
136
141
|
'aria-describedby': getAriaDescribedBy(),
|
package/src/toast/createToast.ts
CHANGED
|
@@ -19,6 +19,10 @@ export interface AriaToastProps<T> {
|
|
|
19
19
|
toast: QueuedToast<T>;
|
|
20
20
|
/** The toast state from createToastState. */
|
|
21
21
|
state: ToastState<T>;
|
|
22
|
+
/** Whether the rendered toast includes a title element. */
|
|
23
|
+
hasTitle?: boolean;
|
|
24
|
+
/** Whether the rendered toast includes a description element. */
|
|
25
|
+
hasDescription?: boolean;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
export interface ToastAria {
|
|
@@ -70,6 +74,8 @@ export interface ToastAria {
|
|
|
70
74
|
export function createToast<T>(props: AriaToastProps<T>): ToastAria {
|
|
71
75
|
const titleId = createId();
|
|
72
76
|
const descriptionId = createId();
|
|
77
|
+
const hasTitle = props.hasTitle ?? true;
|
|
78
|
+
const hasDescription = props.hasDescription ?? true;
|
|
73
79
|
|
|
74
80
|
const close = () => {
|
|
75
81
|
props.state.close(props.toast.key);
|
|
@@ -79,8 +85,8 @@ export function createToast<T>(props: AriaToastProps<T>): ToastAria {
|
|
|
79
85
|
const toastProps = createMemo<JSX.HTMLAttributes<HTMLElement>>(() => ({
|
|
80
86
|
role: 'alertdialog',
|
|
81
87
|
'aria-modal': 'false',
|
|
82
|
-
'aria-labelledby': titleId,
|
|
83
|
-
'aria-describedby': descriptionId,
|
|
88
|
+
'aria-labelledby': hasTitle ? titleId : undefined,
|
|
89
|
+
'aria-describedby': hasDescription ? descriptionId : undefined,
|
|
84
90
|
'data-animation': props.toast.animation,
|
|
85
91
|
'data-key': props.toast.key,
|
|
86
92
|
}));
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
import { type JSX, createMemo } from 'solid-js';
|
|
11
11
|
import { type ToastState } from '@proyecto-viviana/solid-stately';
|
|
12
|
-
import { mergeProps } from '../utils';
|
|
13
12
|
import { createHover } from '../interactions/createHover';
|
|
14
13
|
|
|
15
14
|
// ============================================
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
createSignal,
|
|
10
|
-
createEffect,
|
|
11
10
|
onMount,
|
|
12
11
|
onCleanup,
|
|
13
12
|
type Accessor,
|
|
@@ -104,6 +103,47 @@ function getActiveElement(doc: Document): Element | null {
|
|
|
104
103
|
return activeElement
|
|
105
104
|
}
|
|
106
105
|
|
|
106
|
+
const TEXT_INPUT_TYPES = new Set([
|
|
107
|
+
'',
|
|
108
|
+
'text',
|
|
109
|
+
'search',
|
|
110
|
+
'url',
|
|
111
|
+
'tel',
|
|
112
|
+
'password',
|
|
113
|
+
'email',
|
|
114
|
+
'number',
|
|
115
|
+
'date',
|
|
116
|
+
'datetime-local',
|
|
117
|
+
'month',
|
|
118
|
+
'time',
|
|
119
|
+
'week',
|
|
120
|
+
])
|
|
121
|
+
|
|
122
|
+
function isTextInputLikeElement(target: EventTarget | null): boolean {
|
|
123
|
+
if (!(target instanceof HTMLElement)) {
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (target.isContentEditable || !!target.closest('[contenteditable="true"]')) {
|
|
128
|
+
return true
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (target.getAttribute('role') === 'textbox') {
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) {
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (target instanceof HTMLInputElement) {
|
|
140
|
+
const type = target.type.toLowerCase()
|
|
141
|
+
return TEXT_INPUT_TYPES.has(type)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
|
|
107
147
|
function createFocusManager(ref: Accessor<HTMLElement | undefined>): FocusManager {
|
|
108
148
|
return {
|
|
109
149
|
focusNext(opts: FocusManagerOptions = {}) {
|
|
@@ -258,9 +298,35 @@ export function createToolbar(props: AriaToolbarProps = {}): ToolbarAria {
|
|
|
258
298
|
|
|
259
299
|
// Keyboard event handler
|
|
260
300
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
301
|
+
const root = toolbarRef
|
|
302
|
+
if (!root) return
|
|
303
|
+
|
|
261
304
|
// Don't handle if nested toolbar (parent handles navigation)
|
|
262
305
|
if (isInToolbar()) return
|
|
263
306
|
|
|
307
|
+
const target = e.target
|
|
308
|
+
if (!(target instanceof Element) || !root.contains(target)) {
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Let modified shortcuts pass through.
|
|
313
|
+
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Text entry controls should keep arrow/home/end for caret/value navigation.
|
|
318
|
+
if (isTextInputLikeElement(target)) {
|
|
319
|
+
switch (e.key) {
|
|
320
|
+
case 'ArrowRight':
|
|
321
|
+
case 'ArrowLeft':
|
|
322
|
+
case 'ArrowDown':
|
|
323
|
+
case 'ArrowUp':
|
|
324
|
+
case 'Home':
|
|
325
|
+
case 'End':
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
264
330
|
const dir = locale().direction
|
|
265
331
|
const isRTL = dir === 'rtl'
|
|
266
332
|
const isHorizontal = orientation() === 'horizontal'
|
|
@@ -300,6 +366,14 @@ export function createToolbar(props: AriaToolbarProps = {}): ToolbarAria {
|
|
|
300
366
|
handled = true
|
|
301
367
|
}
|
|
302
368
|
break
|
|
369
|
+
case 'Home':
|
|
370
|
+
focusManager.focusFirst({ tabbable: true })
|
|
371
|
+
handled = true
|
|
372
|
+
break
|
|
373
|
+
case 'End':
|
|
374
|
+
focusManager.focusLast({ tabbable: true })
|
|
375
|
+
handled = true
|
|
376
|
+
break
|
|
303
377
|
case 'Tab':
|
|
304
378
|
// Store the last focused element for re-entry
|
|
305
379
|
lastFocusedElement = e.target as Element
|