@react-aria/tag 3.0.0-rc.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.d.ts CHANGED
@@ -1,38 +1,8 @@
1
- import { Collection, Direction, KeyboardDelegate, AriaLabelingProps, DOMAttributes, DOMProps, FocusableElement, Validation } from "@react-types/shared";
1
+ import { AriaLabelingProps, CollectionBase, DOMAttributes, DOMProps, HelpTextProps, KeyboardDelegate, LabelableProps, MultipleSelection, SelectionBehavior, FocusableElement, Node } from "@react-types/shared";
2
2
  import { Key, RefObject } from "react";
3
+ import { ListState } from "@react-stately/list";
3
4
  import { AriaButtonProps } from "@react-types/button";
4
- import { TagGroupState } from "@react-stately/tag";
5
- import { TagProps, TagGroupProps } from "@react-types/tag";
6
- export class TagKeyboardDelegate<T> implements KeyboardDelegate {
7
- constructor(collection: Collection<T>, direction: Direction);
8
- getFirstKey(): Key;
9
- getLastKey(): Key;
10
- getKeyRightOf(key: Key): any;
11
- getKeyLeftOf(key: Key): any;
12
- getKeyBelow(key: any): any;
13
- getKeyAbove(key: any): any;
14
- getKeyPageAbove(key: any): any;
15
- getKeyPageBelow(key: any): any;
16
- }
17
- export interface TagAria {
18
- /** Props for the tag visible label (if any). */
19
- labelProps: DOMAttributes;
20
- /** Props for the tag cell element. */
21
- gridCellProps: DOMAttributes;
22
- /** Props for the tag row element. */
23
- rowProps: DOMAttributes;
24
- /** Props for the tag remove button. */
25
- removeButtonProps: AriaButtonProps;
26
- }
27
- export interface AriaTagProps<T> extends TagProps<T>, DOMProps, AriaLabelingProps {
28
- }
29
- /**
30
- * Provides the behavior and accessibility implementation for a tag component.
31
- * @param props - Props to be applied to the tag.
32
- * @param state - State for the tag group, as returned by `useTagGroupState`.
33
- * @param ref - A ref to a DOM element for the tag.
34
- */
35
- export function useTag<T>(props: AriaTagProps<T>, state: TagGroupState<T>, ref: RefObject<FocusableElement>): TagAria;
5
+ import { SelectableItemStates } from "@react-aria/selection";
36
6
  export interface TagGroupAria {
37
7
  /** Props for the tag grouping element. */
38
8
  gridProps: DOMAttributes;
@@ -43,21 +13,47 @@ export interface TagGroupAria {
43
13
  /** Props for the tag group error message element, if any. */
44
14
  errorMessageProps: DOMAttributes;
45
15
  }
46
- export interface AriaTagGroupProps<T> extends TagGroupProps<T>, DOMProps, AriaLabelingProps, Validation {
16
+ export interface AriaTagGroupProps<T> extends CollectionBase<T>, MultipleSelection, DOMProps, LabelableProps, AriaLabelingProps, HelpTextProps {
17
+ /** How multiple selection should behave in the collection. */
18
+ selectionBehavior?: SelectionBehavior;
19
+ /** Handler that is called when a user deletes a tag. */
20
+ onRemove?: (keys: Set<Key>) => void;
21
+ }
22
+ interface AriaTagGroupOptions<T> extends Omit<AriaTagGroupProps<T>, 'children'> {
47
23
  /**
48
24
  * An optional keyboard delegate to handle arrow key navigation,
49
25
  * to override the default.
50
26
  */
51
- keyboardDelegate?: TagKeyboardDelegate<T>;
27
+ keyboardDelegate?: KeyboardDelegate;
52
28
  }
53
29
  /**
54
30
  * Provides the behavior and accessibility implementation for a tag group component.
55
- * A tag group is a focusable list of labels, categories, keywords, or other items, with support for keyboard navigation and removal.
31
+ * A tag group is a focusable list of labels, categories, keywords, filters, or other items, with support for keyboard navigation, selection, and removal.
56
32
  * @param props - Props to be applied to the tag group.
57
- * @param state - State for the tag group, as returned by `useTagGroupState`.
33
+ * @param state - State for the tag group, as returned by `useListState`.
58
34
  * @param ref - A ref to a DOM element for the tag group.
59
35
  */
60
- export function useTagGroup<T>(props: AriaTagGroupProps<T>, state: TagGroupState<T>, ref: RefObject<HTMLElement>): TagGroupAria;
61
- export type { TagProps } from '@react-types/tag';
36
+ export function useTagGroup<T>(props: AriaTagGroupOptions<T>, state: ListState<T>, ref: RefObject<HTMLElement>): TagGroupAria;
37
+ export interface TagAria extends Omit<SelectableItemStates, 'hasAction'> {
38
+ /** Props for the tag row element. */
39
+ rowProps: DOMAttributes;
40
+ /** Props for the tag cell element. */
41
+ gridCellProps: DOMAttributes;
42
+ /** Props for the tag remove button. */
43
+ removeButtonProps: AriaButtonProps;
44
+ /** Whether the tag can be removed. */
45
+ allowsRemoving: boolean;
46
+ }
47
+ export interface AriaTagProps<T> {
48
+ /** An object representing the tag. Contains all the relevant information that makes up the tag. */
49
+ item: Node<T>;
50
+ }
51
+ /**
52
+ * Provides the behavior and accessibility implementation for a tag component.
53
+ * @param props - Props to be applied to the tag.
54
+ * @param state - State for the tag group, as returned by `useListState`.
55
+ * @param ref - A ref to a DOM element for the tag.
56
+ */
57
+ export function useTag<T>(props: AriaTagProps<T>, state: ListState<T>, ref: RefObject<FocusableElement>): TagAria;
62
58
 
63
59
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"mappings":";;;;;AAeA,iCAAiC,CAAC,CAAE,YAAW,gBAAgB;gBAIjD,UAAU,EAAE,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS;IAK3D,WAAW;IAIX,UAAU;IAIV,aAAa,CAAC,GAAG,EAAE,GAAG;IAItB,YAAY,CAAC,GAAG,EAAE,GAAG;IAIrB,WAAW,CAAC,GAAG,KAAA;IAgBf,WAAW,CAAC,GAAG,KAAA;IAef,eAAe,CAAC,GAAG,KAAA;IAInB,eAAe,CAAC,GAAG,KAAA;CAGpB;ACrDD;IACE,gDAAgD;IAChD,UAAU,EAAE,aAAa,CAAC;IAC1B,sCAAsC;IACtC,aAAa,EAAE,aAAa,CAAC;IAC7B,qCAAqC;IACrC,QAAQ,EAAE,aAAa,CAAC;IACxB,uCAAuC;IACvC,iBAAiB,EAAE,eAAe,CAAA;CACnC;AAED,8BAA8B,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,EAAE,QAAQ,EAAE,iBAAiB;CAAG;AAEpF;;;;;GAKG;AACH,uBAAuB,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,gBAAgB,CAAC,GAAG,OAAO,CAwDpH;AC7ED;IACE,0CAA0C;IAC1C,SAAS,EAAE,aAAa,CAAC;IACzB,wDAAwD;IACxD,UAAU,EAAE,aAAa,CAAC;IAC1B,2DAA2D;IAC3D,gBAAgB,EAAE,aAAa,CAAC;IAChC,6DAA6D;IAC7D,iBAAiB,EAAE,aAAa,CAAA;CACjC;AAED,mCAAmC,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,iBAAiB,EAAE,UAAU;IACrG;;;OAGG;IACH,gBAAgB,CAAC,EAAE,oBAAoB,CAAC,CAAC,CAAA;CAC1C;AAED;;;;;;GAMG;AACH,4BAA4B,CAAC,EAAE,KAAK,EAAE,kBAAkB,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,WAAW,CAAC,GAAG,YAAY,CA2B9H;AC5DD,YAAY,EAAC,QAAQ,EAAC,MAAM,kBAAkB,CAAC","sources":["packages/@react-aria/tag/src/packages/@react-aria/tag/src/TagKeyboardDelegate.ts","packages/@react-aria/tag/src/packages/@react-aria/tag/src/useTag.ts","packages/@react-aria/tag/src/packages/@react-aria/tag/src/useTagGroup.ts","packages/@react-aria/tag/src/packages/@react-aria/tag/src/index.ts","packages/@react-aria/tag/src/index.ts"],"sourcesContent":[null,null,null,null,"/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nexport {TagKeyboardDelegate} from './TagKeyboardDelegate';\nexport {useTag} from './useTag';\nexport {useTagGroup} from './useTagGroup';\n\nexport type {TagProps} from '@react-types/tag';\nexport type {TagGroupAria, AriaTagGroupProps} from './useTagGroup';\nexport type {AriaTagProps, TagAria} from './useTag';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
1
+ {"mappings":";;;;;AAsBA;IACE,0CAA0C;IAC1C,SAAS,EAAE,aAAa,CAAC;IACzB,wDAAwD;IACxD,UAAU,EAAE,aAAa,CAAC;IAC1B,2DAA2D;IAC3D,gBAAgB,EAAE,aAAa,CAAC;IAChC,6DAA6D;IAC7D,iBAAiB,EAAE,aAAa,CAAA;CACjC;AAED,mCAAmC,CAAC,CAAE,SAAQ,eAAe,CAAC,CAAC,EAAE,iBAAiB,EAAE,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,aAAa;IAC5I,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,yDAAyD;IACzD,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,CAAA;CACpC;AAED,8BAAqC,CAAC,CAAE,SAAQ,IAAI,CAAC,kBAAkB,CAAC,CAAC,EAAE,UAAU,CAAC;IACpF;;;OAGG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;CACpC;AAQD;;;;;;GAMG;AACH,4BAA4B,CAAC,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,WAAW,CAAC,GAAG,YAAY,CAkD5H;ACrFD,wBAAyB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,WAAW,CAAC;IACtE,qCAAqC;IACrC,QAAQ,EAAE,aAAa,CAAC;IACxB,sCAAsC;IACtC,aAAa,EAAE,aAAa,CAAC;IAC7B,uCAAuC;IACvC,iBAAiB,EAAE,eAAe,CAAC;IACnC,sCAAsC;IACtC,cAAc,EAAE,OAAO,CAAA;CACxB;AAED,8BAA8B,CAAC;IAC7B,mGAAmG;IACnG,IAAI,EAAE,KAAK,CAAC,CAAC,CAAA;CACd;AAED;;;;;GAKG;AACH,uBAAuB,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,gBAAgB,CAAC,GAAG,OAAO,CAuDhH","sources":["packages/@react-aria/tag/src/packages/@react-aria/tag/src/useTagGroup.ts","packages/@react-aria/tag/src/packages/@react-aria/tag/src/useTag.ts","packages/@react-aria/tag/src/packages/@react-aria/tag/src/index.ts","packages/@react-aria/tag/src/index.ts"],"sourcesContent":[null,null,null,"/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nexport {useTag} from './useTag';\nexport {useTagGroup} from './useTagGroup';\n\nexport type {TagGroupAria, AriaTagGroupProps} from './useTagGroup';\nexport type {AriaTagProps, TagAria} from './useTag';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-aria/tag",
3
- "version": "3.0.0-rc.0",
3
+ "version": "3.1.0",
4
4
  "description": "Spectrum UI components in React",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/main.js",
@@ -22,17 +22,16 @@
22
22
  "url": "https://github.com/adobe/react-spectrum"
23
23
  },
24
24
  "dependencies": {
25
- "@react-aria/gridlist": "^3.3.0",
26
- "@react-aria/i18n": "^3.7.1",
27
- "@react-aria/interactions": "^3.15.0",
28
- "@react-aria/label": "^3.5.1",
29
- "@react-aria/utils": "^3.16.0",
30
- "@react-stately/list": "^3.8.0",
31
- "@react-stately/tag": "3.0.0-rc.0",
32
- "@react-types/button": "^3.7.2",
33
- "@react-types/shared": "^3.18.0",
34
- "@react-types/tag": "3.0.0-rc.0",
35
- "@swc/helpers": "^0.4.14"
25
+ "@react-aria/gridlist": "^3.5.0",
26
+ "@react-aria/i18n": "^3.8.0",
27
+ "@react-aria/interactions": "^3.16.0",
28
+ "@react-aria/label": "^3.6.0",
29
+ "@react-aria/selection": "^3.16.0",
30
+ "@react-aria/utils": "^3.18.0",
31
+ "@react-stately/list": "^3.9.0",
32
+ "@react-types/button": "^3.7.3",
33
+ "@react-types/shared": "^3.18.1",
34
+ "@swc/helpers": "^0.5.0"
36
35
  },
37
36
  "peerDependencies": {
38
37
  "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
@@ -40,5 +39,5 @@
40
39
  "publishConfig": {
41
40
  "access": "public"
42
41
  },
43
- "gitHead": "9d1ba9bd8ebcd63bf3495ade16d349bcb71795ce"
42
+ "gitHead": "504e40e0a50c1b20ed0fb3ba9561a263b6d5565e"
44
43
  }
package/src/index.ts CHANGED
@@ -10,10 +10,8 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- export {TagKeyboardDelegate} from './TagKeyboardDelegate';
14
13
  export {useTag} from './useTag';
15
14
  export {useTagGroup} from './useTagGroup';
16
15
 
17
- export type {TagProps} from '@react-types/tag';
18
16
  export type {TagGroupAria, AriaTagGroupProps} from './useTagGroup';
19
17
  export type {AriaTagProps, TagAria} from './useTag';
package/src/useTag.ts CHANGED
@@ -11,59 +11,63 @@
11
11
  */
12
12
 
13
13
  import {AriaButtonProps} from '@react-types/button';
14
- import {AriaLabelingProps, DOMAttributes, DOMProps, FocusableElement} from '@react-types/shared';
15
- import {chain, filterDOMProps, mergeProps, useDescription, useId} from '@react-aria/utils';
14
+ import {DOMAttributes, FocusableElement, Node} from '@react-types/shared';
15
+ import {filterDOMProps, mergeProps, useDescription, useId} from '@react-aria/utils';
16
+ import {hookData} from './useTagGroup';
16
17
  // @ts-ignore
17
18
  import intlMessages from '../intl/*.json';
18
19
  import {KeyboardEvent, RefObject} from 'react';
19
- import type {TagGroupState} from '@react-stately/tag';
20
- import {TagProps} from '@react-types/tag';
20
+ import type {ListState} from '@react-stately/list';
21
+ import {SelectableItemStates} from '@react-aria/selection';
21
22
  import {useGridListItem} from '@react-aria/gridlist';
22
23
  import {useInteractionModality} from '@react-aria/interactions';
23
24
  import {useLocalizedStringFormatter} from '@react-aria/i18n';
24
25
 
25
26
 
26
- export interface TagAria {
27
- /** Props for the tag visible label (if any). */
28
- labelProps: DOMAttributes,
29
- /** Props for the tag cell element. */
30
- gridCellProps: DOMAttributes,
27
+ export interface TagAria extends Omit<SelectableItemStates, 'hasAction'> {
31
28
  /** Props for the tag row element. */
32
29
  rowProps: DOMAttributes,
30
+ /** Props for the tag cell element. */
31
+ gridCellProps: DOMAttributes,
33
32
  /** Props for the tag remove button. */
34
- removeButtonProps: AriaButtonProps
33
+ removeButtonProps: AriaButtonProps,
34
+ /** Whether the tag can be removed. */
35
+ allowsRemoving: boolean
35
36
  }
36
37
 
37
- export interface AriaTagProps<T> extends TagProps<T>, DOMProps, AriaLabelingProps {}
38
+ export interface AriaTagProps<T> {
39
+ /** An object representing the tag. Contains all the relevant information that makes up the tag. */
40
+ item: Node<T>
41
+ }
38
42
 
39
43
  /**
40
44
  * Provides the behavior and accessibility implementation for a tag component.
41
45
  * @param props - Props to be applied to the tag.
42
- * @param state - State for the tag group, as returned by `useTagGroupState`.
46
+ * @param state - State for the tag group, as returned by `useListState`.
43
47
  * @param ref - A ref to a DOM element for the tag.
44
48
  */
45
- export function useTag<T>(props: AriaTagProps<T>, state: TagGroupState<T>, ref: RefObject<FocusableElement>): TagAria {
46
- let {
47
- allowsRemoving,
48
- item
49
- } = props;
49
+ export function useTag<T>(props: AriaTagProps<T>, state: ListState<T>, ref: RefObject<FocusableElement>): TagAria {
50
+ let {item} = props;
50
51
  let stringFormatter = useLocalizedStringFormatter(intlMessages);
51
- let labelId = useId();
52
52
  let buttonId = useId();
53
53
 
54
- let {rowProps, gridCellProps} = useGridListItem({
54
+ let {onRemove} = hookData.get(state) || {};
55
+ let {rowProps, gridCellProps, ...states} = useGridListItem({
55
56
  node: item
56
57
  }, state, ref);
57
58
 
58
59
  // We want the group to handle keyboard navigation between tags.
59
60
  delete rowProps.onKeyDownCapture;
60
-
61
- let onRemove = chain(props.onRemove, state.onRemove);
61
+ delete states.descriptionProps;
62
62
 
63
63
  let onKeyDown = (e: KeyboardEvent) => {
64
64
  if (e.key === 'Delete' || e.key === 'Backspace') {
65
- onRemove(item.key);
66
65
  e.preventDefault();
66
+ if (state.selectionManager.isSelected(item.key)) {
67
+ onRemove(new Set(state.selectionManager.selectedKeys));
68
+ } else {
69
+ onRemove(new Set([item.key]));
70
+ }
67
71
  }
68
72
  };
69
73
 
@@ -71,31 +75,30 @@ export function useTag<T>(props: AriaTagProps<T>, state: TagGroupState<T>, ref:
71
75
  if (modality === 'virtual' && (typeof window !== 'undefined' && 'ontouchstart' in window)) {
72
76
  modality = 'touch';
73
77
  }
74
- let description = allowsRemoving && (modality === 'keyboard' || modality === 'virtual') ? stringFormatter.format('removeDescription') : '';
78
+ let description = onRemove && (modality === 'keyboard' || modality === 'virtual') ? stringFormatter.format('removeDescription') : '';
75
79
  let descProps = useDescription(description);
76
80
 
77
- let domProps = filterDOMProps(props);
78
81
  let isFocused = item.key === state.selectionManager.focusedKey;
82
+ // @ts-ignore - data attributes are ok but TS doesn't know about them.
83
+ let domProps = filterDOMProps(props);
79
84
  return {
80
85
  removeButtonProps: {
81
- 'aria-label': stringFormatter.format('removeButtonLabel', {label: item.textValue}),
82
- 'aria-labelledby': `${buttonId} ${labelId}`,
86
+ 'aria-label': stringFormatter.format('removeButtonLabel'),
87
+ 'aria-labelledby': `${buttonId} ${rowProps.id}`,
83
88
  id: buttonId,
84
- onPress: () => allowsRemoving && onRemove ? onRemove(item.key) : null,
89
+ onPress: () => onRemove ? onRemove(new Set([item.key])) : null,
85
90
  excludeFromTabOrder: true
86
91
  },
87
- labelProps: {
88
- id: labelId
89
- },
90
- rowProps: {
91
- ...rowProps,
92
+ rowProps: mergeProps(rowProps, domProps, {
92
93
  tabIndex: (isFocused || state.selectionManager.focusedKey == null) ? 0 : -1,
93
- onKeyDown: allowsRemoving ? onKeyDown : null,
94
+ onKeyDown: onRemove ? onKeyDown : undefined,
94
95
  'aria-describedby': descProps['aria-describedby']
95
- },
96
- gridCellProps: mergeProps(domProps, gridCellProps, {
96
+ }),
97
+ gridCellProps: mergeProps(gridCellProps, {
97
98
  'aria-errormessage': props['aria-errormessage'],
98
99
  'aria-label': props['aria-label']
99
- })
100
+ }),
101
+ ...states,
102
+ allowsRemoving: !!onRemove
100
103
  };
101
104
  }
@@ -10,12 +10,11 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {AriaLabelingProps, DOMAttributes, DOMProps, Validation} from '@react-types/shared';
13
+ import {AriaLabelingProps, CollectionBase, DOMAttributes, DOMProps, HelpTextProps, KeyboardDelegate, LabelableProps, MultipleSelection, SelectionBehavior} from '@react-types/shared';
14
14
  import {filterDOMProps, mergeProps} from '@react-aria/utils';
15
- import {RefObject, useState} from 'react';
16
- import {TagGroupProps} from '@react-types/tag';
17
- import type {TagGroupState} from '@react-stately/tag';
18
- import {TagKeyboardDelegate} from './TagKeyboardDelegate';
15
+ import {Key, RefObject, useEffect, useRef, useState} from 'react';
16
+ import {ListKeyboardDelegate} from '@react-aria/selection';
17
+ import type {ListState} from '@react-stately/list';
19
18
  import {useField} from '@react-aria/label';
20
19
  import {useFocusWithin} from '@react-aria/interactions';
21
20
  import {useGridList} from '@react-aria/gridlist';
@@ -32,35 +31,71 @@ export interface TagGroupAria {
32
31
  errorMessageProps: DOMAttributes
33
32
  }
34
33
 
35
- export interface AriaTagGroupProps<T> extends TagGroupProps<T>, DOMProps, AriaLabelingProps, Validation {
34
+ export interface AriaTagGroupProps<T> extends CollectionBase<T>, MultipleSelection, DOMProps, LabelableProps, AriaLabelingProps, HelpTextProps {
35
+ /** How multiple selection should behave in the collection. */
36
+ selectionBehavior?: SelectionBehavior,
37
+ /** Handler that is called when a user deletes a tag. */
38
+ onRemove?: (keys: Set<Key>) => void
39
+ }
40
+
41
+ export interface AriaTagGroupOptions<T> extends Omit<AriaTagGroupProps<T>, 'children'> {
36
42
  /**
37
43
  * An optional keyboard delegate to handle arrow key navigation,
38
44
  * to override the default.
39
45
  */
40
- keyboardDelegate?: TagKeyboardDelegate<T>
46
+ keyboardDelegate?: KeyboardDelegate
47
+ }
48
+
49
+ interface HookData {
50
+ onRemove?: (keys: Set<Key>) => void
41
51
  }
42
52
 
53
+ export const hookData = new WeakMap<ListState<any>, HookData>();
54
+
43
55
  /**
44
56
  * Provides the behavior and accessibility implementation for a tag group component.
45
- * A tag group is a focusable list of labels, categories, keywords, or other items, with support for keyboard navigation and removal.
57
+ * A tag group is a focusable list of labels, categories, keywords, filters, or other items, with support for keyboard navigation, selection, and removal.
46
58
  * @param props - Props to be applied to the tag group.
47
- * @param state - State for the tag group, as returned by `useTagGroupState`.
59
+ * @param state - State for the tag group, as returned by `useListState`.
48
60
  * @param ref - A ref to a DOM element for the tag group.
49
61
  */
50
- export function useTagGroup<T>(props: AriaTagGroupProps<T>, state: TagGroupState<T>, ref: RefObject<HTMLElement>): TagGroupAria {
62
+ export function useTagGroup<T>(props: AriaTagGroupOptions<T>, state: ListState<T>, ref: RefObject<HTMLElement>): TagGroupAria {
51
63
  let {direction} = useLocale();
52
- let keyboardDelegate = props.keyboardDelegate || new TagKeyboardDelegate(state.collection, direction);
53
- let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useField(props);
54
- let {gridProps} = useGridList({...props, ...fieldProps, keyboardDelegate}, state, ref);
55
-
56
- // Don't want the grid to be focusable or accessible via keyboard
57
- delete gridProps.tabIndex;
64
+ let keyboardDelegate = props.keyboardDelegate || new ListKeyboardDelegate({
65
+ collection: state.collection,
66
+ ref,
67
+ orientation: 'horizontal',
68
+ direction,
69
+ disabledKeys: state.disabledKeys
70
+ });
71
+ let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useField({
72
+ ...props,
73
+ labelElementType: 'span'
74
+ });
75
+ let {gridProps} = useGridList({
76
+ ...props,
77
+ ...fieldProps,
78
+ keyboardDelegate,
79
+ shouldFocusWrap: true
80
+ }, state, ref);
58
81
 
59
82
  let [isFocusWithin, setFocusWithin] = useState(false);
60
83
  let {focusWithinProps} = useFocusWithin({
61
84
  onFocusWithinChange: setFocusWithin
62
85
  });
63
86
  let domProps = filterDOMProps(props);
87
+
88
+ // If the last tag is removed, focus the container.
89
+ let prevCount = useRef(state.collection.size);
90
+ useEffect(() => {
91
+ if (prevCount.current > 0 && state.collection.size === 0 && isFocusWithin) {
92
+ ref.current.focus();
93
+ }
94
+ prevCount.current = state.collection.size;
95
+ }, [state.collection.size, isFocusWithin, ref]);
96
+
97
+ hookData.set(state, {onRemove: props.onRemove});
98
+
64
99
  return {
65
100
  gridProps: mergeProps(gridProps, domProps, {
66
101
  role: state.collection.size ? 'grid' : null,
@@ -1,79 +0,0 @@
1
- /*
2
- * Copyright 2020 Adobe. All rights reserved.
3
- * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License. You may obtain a copy
5
- * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
- *
7
- * Unless required by applicable law or agreed to in writing, software distributed under
8
- * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
- * OF ANY KIND, either express or implied. See the License for the specific language
10
- * governing permissions and limitations under the License.
11
- */
12
-
13
- import {Collection, Direction, KeyboardDelegate} from '@react-types/shared';
14
- import {Key} from 'react';
15
-
16
- export class TagKeyboardDelegate<T> implements KeyboardDelegate {
17
- private collection: Collection<T>;
18
- private direction: Direction;
19
-
20
- constructor(collection: Collection<T>, direction: Direction) {
21
- this.collection = collection;
22
- this.direction = direction;
23
- }
24
-
25
- getFirstKey() {
26
- return this.collection.getFirstKey();
27
- }
28
-
29
- getLastKey() {
30
- return this.collection.getLastKey();
31
- }
32
-
33
- getKeyRightOf(key: Key) {
34
- return this.direction === 'rtl' ? this.getKeyAbove(key) : this.getKeyBelow(key);
35
- }
36
-
37
- getKeyLeftOf(key: Key) {
38
- return this.direction === 'rtl' ? this.getKeyBelow(key) : this.getKeyAbove(key);
39
- }
40
-
41
- getKeyBelow(key) {
42
- let startItem = this.collection.getItem(key);
43
- if (!startItem) {
44
- return;
45
- }
46
-
47
- // Find the next item
48
- key = this.collection.getKeyAfter(key);
49
-
50
- if (key != null) {
51
- return key;
52
- } else {
53
- return this.collection.getFirstKey();
54
- }
55
- }
56
-
57
- getKeyAbove(key) {
58
- let startItem = this.collection.getItem(key);
59
- if (!startItem) {
60
- return;
61
- }
62
-
63
- // Find the previous item
64
- key = this.collection.getKeyBefore(key);
65
- if (key != null) {
66
- return key;
67
- } else {
68
- return this.collection.getLastKey();
69
- }
70
- }
71
-
72
- getKeyPageAbove(key) {
73
- return this.getKeyAbove(key);
74
- }
75
-
76
- getKeyPageBelow(key) {
77
- return this.getKeyBelow(key);
78
- }
79
- }