@react-spectrum/list 3.0.0-alpha.8 → 3.0.0-alpha.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-spectrum/list",
3
- "version": "3.0.0-alpha.8",
3
+ "version": "3.0.0-alpha.9",
4
4
  "description": "Spectrum UI components in React",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/main.js",
@@ -32,34 +32,40 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@babel/runtime": "^7.6.2",
35
- "@react-aria/focus": "^3.5.2",
36
- "@react-aria/grid": "^3.2.3",
37
- "@react-aria/i18n": "^3.3.6",
38
- "@react-aria/interactions": "^3.8.1",
39
- "@react-aria/listbox": "^3.4.2",
40
- "@react-aria/separator": "^3.1.5",
41
- "@react-aria/utils": "^3.11.2",
42
- "@react-aria/virtualizer": "^3.3.7",
43
- "@react-spectrum/button": "^3.7.1",
44
- "@react-spectrum/checkbox": "^3.3.1",
45
- "@react-spectrum/layout": "^3.2.3",
46
- "@react-spectrum/listbox": "^3.5.5",
47
- "@react-spectrum/progress": "^3.1.5",
48
- "@react-spectrum/text": "^3.1.5",
49
- "@react-spectrum/textfield": "^3.3.2",
50
- "@react-spectrum/utils": "^3.6.5",
51
- "@react-spectrum/view": "^3.1.5",
52
- "@react-stately/collections": "^3.3.6",
53
- "@react-stately/grid": "^3.1.2",
54
- "@react-stately/layout": "^3.4.4",
55
- "@react-stately/list": "^3.4.3",
56
- "@react-stately/virtualizer": "^3.1.7",
57
- "@react-types/listbox": "^3.2.3",
58
- "@react-types/shared": "^3.11.1",
59
- "@spectrum-icons/ui": "^3.2.3"
35
+ "@react-aria/button": "^3.4.2",
36
+ "@react-aria/focus": "^3.5.3",
37
+ "@react-aria/grid": "^3.2.4",
38
+ "@react-aria/i18n": "^3.3.7",
39
+ "@react-aria/interactions": "^3.8.2",
40
+ "@react-aria/listbox": "^3.4.3",
41
+ "@react-aria/separator": "^3.1.6",
42
+ "@react-aria/utils": "^3.11.3",
43
+ "@react-aria/virtualizer": "^3.3.8",
44
+ "@react-aria/visually-hidden": "^3.2.6",
45
+ "@react-spectrum/button": "^3.7.2",
46
+ "@react-spectrum/checkbox": "^3.3.2",
47
+ "@react-spectrum/dnd": "3.0.0-alpha.0",
48
+ "@react-spectrum/layout": "^3.2.4",
49
+ "@react-spectrum/listbox": "^3.5.6",
50
+ "@react-spectrum/progress": "^3.1.6",
51
+ "@react-spectrum/text": "^3.1.6",
52
+ "@react-spectrum/textfield": "^3.3.3",
53
+ "@react-spectrum/utils": "^3.6.6",
54
+ "@react-spectrum/view": "^3.1.6",
55
+ "@react-stately/collections": "^3.3.7",
56
+ "@react-stately/grid": "^3.1.3",
57
+ "@react-stately/layout": "^3.4.5",
58
+ "@react-stately/list": "^3.4.4",
59
+ "@react-stately/virtualizer": "^3.1.8",
60
+ "@react-types/button": "^3.4.4",
61
+ "@react-types/listbox": "^3.2.4",
62
+ "@react-types/shared": "^3.11.2",
63
+ "@spectrum-icons/ui": "^3.2.4"
60
64
  },
61
65
  "devDependencies": {
62
- "@adobe/spectrum-css-temp": "^3.0.0-alpha.1"
66
+ "@adobe/spectrum-css-temp": "^3.0.0-alpha.1",
67
+ "@react-aria/dnd": "3.0.0-alpha.6",
68
+ "@react-stately/dnd": "3.0.0-alpha.5"
63
69
  },
64
70
  "peerDependencies": {
65
71
  "@react-spectrum/provider": "^3.2.0",
@@ -69,5 +75,5 @@
69
75
  "publishConfig": {
70
76
  "access": "public"
71
77
  },
72
- "gitHead": "b98f0e60590d784afd54140916576021d4c1bfd7"
78
+ "gitHead": "ed8d8d984c2f7f2c31e8b18795b97858a95e4729"
73
79
  }
package/src/ListView.tsx CHANGED
@@ -20,23 +20,35 @@ import {
20
20
  SpectrumSelectionProps,
21
21
  StyleProps
22
22
  } from '@react-types/shared';
23
- import {classNames, useDOMRef, useStyleProps} from '@react-spectrum/utils';
24
- import {GridCollection, useGridState} from '@react-stately/grid';
25
- import {GridKeyboardDelegate, useGrid} from '@react-aria/grid';
23
+ import {Checkbox} from '@react-spectrum/checkbox';
24
+ import {classNames, SlotProvider, useDOMRef, useStyleProps} from '@react-spectrum/utils';
25
+ import {Content} from '@react-spectrum/view';
26
+ import type {DraggableCollectionState} from '@react-stately/dnd';
27
+ import {DragHooks} from '@react-spectrum/dnd';
28
+ import {GridCollection, GridState, useGridState} from '@react-stately/grid';
29
+ import {GridKeyboardDelegate, useGrid, useGridSelectionCheckbox} from '@react-aria/grid';
26
30
  // @ts-ignore
27
31
  import intlMessages from '../intl/*.json';
32
+ import ListGripper from '@spectrum-icons/ui/ListGripper';
28
33
  import {ListLayout} from '@react-stately/layout';
29
34
  import {ListState, useListState} from '@react-stately/list';
30
35
  import listStyles from './listview.css';
31
36
  import {ListViewItem} from './ListViewItem';
32
37
  import {ProgressCircle} from '@react-spectrum/progress';
33
- import React, {ReactElement, useContext, useMemo} from 'react';
38
+ import {Provider, useProvider} from '@react-spectrum/provider';
39
+ import React, {ReactElement, useContext, useMemo, useRef} from 'react';
34
40
  import {useCollator, useLocale, useMessageFormatter} from '@react-aria/i18n';
35
- import {useProvider} from '@react-spectrum/provider';
36
41
  import {Virtualizer} from '@react-aria/virtualizer';
37
42
 
43
+ interface ListViewContextValue {
44
+ state: GridState<object, GridCollection<any>>,
45
+ keyboardDelegate: GridKeyboardDelegate<unknown, GridCollection<any>>,
46
+ dragState: DraggableCollectionState,
47
+ onAction:(key: string) => void,
48
+ isListDraggable: boolean
49
+ }
38
50
 
39
- export const ListViewContext = React.createContext(null);
51
+ export const ListViewContext = React.createContext<ListViewContextValue>(null);
40
52
 
41
53
  const ROW_HEIGHTS = {
42
54
  compact: {
@@ -58,12 +70,12 @@ export function useListLayout<T>(state: ListState<T>, density: ListViewProps<T>[
58
70
  let collator = useCollator({usage: 'search', sensitivity: 'base'});
59
71
  let isEmpty = state.collection.size === 0;
60
72
  let layout = useMemo(() =>
61
- new ListLayout<T>({
62
- estimatedRowHeight: ROW_HEIGHTS[density][scale],
63
- padding: 0,
64
- collator,
65
- loaderHeight: isEmpty ? null : ROW_HEIGHTS[density][scale]
66
- })
73
+ new ListLayout<T>({
74
+ estimatedRowHeight: ROW_HEIGHTS[density][scale],
75
+ padding: 0,
76
+ collator,
77
+ loaderHeight: isEmpty ? null : ROW_HEIGHTS[density][scale]
78
+ })
67
79
  , [collator, scale, density, isEmpty]);
68
80
 
69
81
  layout.collection = state.collection;
@@ -81,7 +93,8 @@ interface ListViewProps<T> extends CollectionBase<T>, DOMProps, AriaLabelingProp
81
93
  loadingState?: LoadingState,
82
94
  renderEmptyState?: () => JSX.Element,
83
95
  transitionDuration?: number,
84
- onAction?: (key: string) => void
96
+ onAction?: (key: string) => void,
97
+ dragHooks?: DragHooks
85
98
  }
86
99
 
87
100
  function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDivElement>) {
@@ -91,8 +104,14 @@ function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDiv
91
104
  loadingState,
92
105
  isQuiet,
93
106
  transitionDuration = 0,
94
- onAction
107
+ onAction,
108
+ dragHooks
95
109
  } = props;
110
+ let isListDraggable = !!dragHooks;
111
+ let dragHooksProvided = useRef(isListDraggable);
112
+ if (dragHooksProvided.current !== isListDraggable) {
113
+ console.warn('Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
114
+ }
96
115
  let domRef = useDOMRef(ref);
97
116
  let {collection} = useListState(props);
98
117
  let formatMessage = useMessageFormatter(intlMessages);
@@ -136,6 +155,62 @@ function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDiv
136
155
  // focusable children in the cell.
137
156
  focusMode: 'cell'
138
157
  }), [state, domRef, direction, collator]);
158
+
159
+ let provider = useProvider();
160
+ let {checkboxProps} = useGridSelectionCheckbox({key: null}, state);
161
+ let dragState: DraggableCollectionState;
162
+ if (isListDraggable) {
163
+ dragState = dragHooks.useDraggableCollectionState({
164
+ collection: state.collection,
165
+ selectionManager: state.selectionManager,
166
+ renderPreview(selectedKeys, draggedKey) {
167
+ let item = state.collection.getItem(draggedKey);
168
+ let itemWidth = domRef.current.offsetWidth;
169
+ let showCheckbox = state.selectionManager.selectionMode !== 'none' && state.selectionManager.selectionBehavior === 'toggle';
170
+ let isSelected = state.selectionManager.isSelected(item.key);
171
+ return (
172
+ <Provider
173
+ {...provider}
174
+ UNSAFE_className={classNames(listStyles, 'react-spectrum-ListViewItem', 'is-dragging')}
175
+ UNSAFE_style={{width: itemWidth, paddingInlineStart: 0}}>
176
+ <div className={listStyles['react-spectrum-ListViewItem-grid']}>
177
+ <div className={listStyles['react-spectrum-ListViewItem-draghandle-container']}>
178
+ <div className={listStyles['react-spectrum-ListViewItem-draghandle-button']}>
179
+ <ListGripper />
180
+ </div>
181
+ </div>
182
+ {showCheckbox &&
183
+ <Checkbox
184
+ isSelected={isSelected}
185
+ UNSAFE_className={listStyles['react-spectrum-ListViewItem-checkbox']}
186
+ isEmphasized
187
+ aria-label={checkboxProps['aria-label']} />
188
+ }
189
+ <SlotProvider
190
+ slots={{
191
+ content: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
192
+ text: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
193
+ description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description']},
194
+ icon: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-icon'], size: 'M'},
195
+ image: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-image']},
196
+ link: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content'], isQuiet: true},
197
+ actionButton: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'], isQuiet: true},
198
+ actionGroup: {
199
+ UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'],
200
+ isQuiet: true,
201
+ density: 'compact'
202
+ },
203
+ actionMenu: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actionmenu'], isQuiet: true}
204
+ }}>
205
+ {typeof item.rendered === 'string' ? <Content>{item.rendered}</Content> : item.rendered}
206
+ </SlotProvider>
207
+ </div>
208
+ </Provider>
209
+ );
210
+ }
211
+ });
212
+ }
213
+
139
214
  let {gridProps} = useGrid({
140
215
  ...props,
141
216
  isVirtualized: true,
@@ -152,7 +227,7 @@ function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDiv
152
227
  }
153
228
 
154
229
  return (
155
- <ListViewContext.Provider value={{state, keyboardDelegate}}>
230
+ <ListViewContext.Provider value={{state, keyboardDelegate, dragState, onAction, isListDraggable}}>
156
231
  <Virtualizer
157
232
  {...gridProps}
158
233
  {...styleProps}
@@ -168,7 +243,8 @@ function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDiv
168
243
  `react-spectrum-ListView--${density}`,
169
244
  'react-spectrum-ListView--emphasized',
170
245
  {
171
- 'react-spectrum-ListView--quiet': isQuiet
246
+ 'react-spectrum-ListView--quiet': isQuiet,
247
+ 'react-spectrum-ListView--draggable': isListDraggable
172
248
  },
173
249
  styleProps.className
174
250
  )
@@ -179,7 +255,7 @@ function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDiv
179
255
  {(type, item) => {
180
256
  if (type === 'item') {
181
257
  return (
182
- <ListViewItem item={item} onAction={onAction} isEmphasized />
258
+ <ListViewItem item={item} isEmphasized dragHooks={dragHooks} />
183
259
  );
184
260
  } else if (type === 'loader') {
185
261
  return (
@@ -14,24 +14,28 @@ import ChevronLeftMedium from '@spectrum-icons/ui/ChevronLeftMedium';
14
14
  import ChevronRightMedium from '@spectrum-icons/ui/ChevronRightMedium';
15
15
  import {classNames, ClearSlots, SlotProvider} from '@react-spectrum/utils';
16
16
  import {Content} from '@react-spectrum/view';
17
+ import type {DraggableItemResult} from '@react-aria/dnd';
18
+ import {FocusRing, useFocusRing} from '@react-aria/focus';
17
19
  import {Grid} from '@react-spectrum/layout';
20
+ import ListGripper from '@spectrum-icons/ui/ListGripper';
18
21
  import listStyles from './listview.css';
19
22
  import {ListViewContext} from './ListView';
20
23
  import {mergeProps} from '@react-aria/utils';
21
24
  import React, {useContext, useRef} from 'react';
22
- import {useFocusRing} from '@react-aria/focus';
25
+ import {useButton} from '@react-aria/button';
23
26
  import {useGridCell, useGridRow, useGridSelectionCheckbox} from '@react-aria/grid';
24
27
  import {useHover, usePress} from '@react-aria/interactions';
25
28
  import {useLocale} from '@react-aria/i18n';
29
+ import {useVisuallyHidden} from '@react-aria/visually-hidden';
26
30
 
27
31
  export function ListViewItem(props) {
28
32
  let {
29
33
  item,
30
- onAction,
31
- isEmphasized
34
+ isEmphasized,
35
+ dragHooks
32
36
  } = props;
33
37
  let cellNode = [...item.childNodes][0];
34
- let {state} = useContext(ListViewContext);
38
+ let {state, dragState, onAction, isListDraggable} = useContext(ListViewContext);
35
39
  let {direction} = useLocale();
36
40
  let rowRef = useRef<HTMLDivElement>();
37
41
  let cellRef = useRef<HTMLDivElement>();
@@ -42,17 +46,24 @@ export function ListViewItem(props) {
42
46
  let {isFocusVisible, focusProps} = useFocusRing();
43
47
  let allowsInteraction = state.selectionManager.selectionMode !== 'none' || onAction;
44
48
  let isDisabled = !allowsInteraction || state.disabledKeys.has(item.key);
49
+ let isDraggable = dragState?.isDraggable(item.key) && !isDisabled;
45
50
  let {hoverProps, isHovered} = useHover({isDisabled});
46
51
  let {pressProps, isPressed} = usePress({isDisabled});
47
52
  let {rowProps} = useGridRow({
48
53
  node: item,
49
54
  isVirtualized: true,
50
- onAction: onAction ? () => onAction(item.key) : undefined
55
+ onAction: onAction ? () => onAction(item.key) : undefined,
56
+ shouldSelectOnPressUp: isListDraggable
51
57
  }, state, rowRef);
52
58
  let {gridCellProps} = useGridCell({
53
59
  node: cellNode,
54
60
  focusMode: 'cell'
55
61
  }, state, cellRef);
62
+ let draggableItem: DraggableItemResult;
63
+ if (isListDraggable) {
64
+ // eslint-disable-next-line react-hooks/rules-of-hooks
65
+ draggableItem = dragHooks.useDraggableItem({key: item.key}, dragState);
66
+ }
56
67
  const mergedProps = mergeProps(
57
68
  gridCellProps,
58
69
  hoverProps,
@@ -61,6 +72,12 @@ export function ListViewItem(props) {
61
72
  );
62
73
  let {checkboxProps} = useGridSelectionCheckbox({...props, key: item.key}, state);
63
74
 
75
+ let dragButtonRef = React.useRef();
76
+ let {buttonProps} = useButton({
77
+ ...draggableItem?.dragButtonProps,
78
+ elementType: 'div'
79
+ }, dragButtonRef);
80
+
64
81
  let chevron = null;
65
82
  if (item.props.hasChildItems) {
66
83
  chevron = direction === 'ltr'
@@ -78,9 +95,11 @@ export function ListViewItem(props) {
78
95
 
79
96
  let showCheckbox = state.selectionManager.selectionMode !== 'none' && state.selectionManager.selectionBehavior === 'toggle';
80
97
  let isSelected = state.selectionManager.isSelected(item.key);
98
+ let showDragHandle = isDraggable && (isFocusVisibleWithin || isHovered || isPressed);
99
+ let {visuallyHiddenProps} = useVisuallyHidden();
81
100
  return (
82
101
  <div
83
- {...mergeProps(rowProps, pressProps)}
102
+ {...mergeProps(rowProps, pressProps, isDraggable && draggableItem?.dragProps)}
84
103
  ref={rowRef}>
85
104
  <div
86
105
  className={
@@ -94,19 +113,41 @@ export function ListViewItem(props) {
94
113
  'is-hovered': isHovered,
95
114
  'is-selected': isSelected,
96
115
  'is-previous-selected': state.selectionManager.isSelected(item.prevKey),
97
- 'react-spectrum-ListViewItem--highlightSelection': state.selectionManager.selectionBehavior === 'replace' && (isSelected || state.selectionManager.isSelected(item.nextKey))
116
+ 'react-spectrum-ListViewItem--highlightSelection': state.selectionManager.selectionBehavior === 'replace' && (isSelected || state.selectionManager.isSelected(item.nextKey)),
117
+ 'react-spectrum-ListViewItem--draggable': isDraggable
98
118
  }
99
119
  )
100
120
  }
101
121
  ref={cellRef}
102
122
  {...mergedProps}>
103
123
  <Grid UNSAFE_className={listStyles['react-spectrum-ListViewItem-grid']}>
104
- {showCheckbox && (
124
+ {isListDraggable &&
125
+ <div className={listStyles['react-spectrum-ListViewItem-draghandle-container']}>
126
+ {isDraggable &&
127
+ <FocusRing focusRingClass={classNames(listStyles, 'focus-ring')}>
128
+ <div
129
+ {...buttonProps as React.HTMLAttributes<HTMLElement>}
130
+ className={
131
+ classNames(
132
+ listStyles,
133
+ 'react-spectrum-ListViewItem-draghandle-button'
134
+ )
135
+ }
136
+ style={!showDragHandle ? {...visuallyHiddenProps.style} : {}}
137
+ ref={dragButtonRef}
138
+ draggable="true">
139
+ <ListGripper />
140
+ </div>
141
+ </FocusRing>
142
+ }
143
+ </div>
144
+ }
145
+ {showCheckbox &&
105
146
  <Checkbox
106
147
  UNSAFE_className={listStyles['react-spectrum-ListViewItem-checkbox']}
107
148
  {...checkboxProps}
108
149
  isEmphasized={isEmphasized} />
109
- )}
150
+ }
110
151
  <SlotProvider
111
152
  slots={{
112
153
  content: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
package/src/listview.css CHANGED
@@ -98,17 +98,46 @@
98
98
  padding-bottom: 0px;
99
99
  }
100
100
 
101
+ &.is-dragging {
102
+ border: 1px solid var(--spectrum-global-color-blue-500);
103
+ border-radius: var(--spectrum-global-dimension-size-50);
104
+ }
105
+
101
106
  .react-spectrum-ListViewItem-grid {
102
107
  display: grid;
103
- grid-template-columns: auto auto auto 1fr auto auto;
108
+ grid-template-columns: auto auto auto auto 1fr auto auto;
104
109
  grid-template-rows: 1fr auto;
105
110
  grid-template-areas:
106
- "checkbox icon image content actions actionmenu chevron"
107
- "checkbox icon image description actions actionmenu chevron"
111
+ "draghandle checkbox icon image content actions actionmenu chevron"
112
+ "draghandle checkbox icon image description actions actionmenu chevron"
108
113
  ;
109
114
  align-items: center;
110
115
  }
111
116
 
117
+ .react-spectrum-ListViewItem-draghandle-container {
118
+ grid-area: draghandle;
119
+ width: var(--spectrum-global-dimension-size-250);
120
+ display: flex;
121
+ align-self: stretch;
122
+ justify-self: stretch;
123
+ justify-content: center;
124
+ padding: var(--spectrum-global-dimension-size-25);
125
+
126
+
127
+ .react-spectrum-ListViewItem-draghandle-button {
128
+ width: var(--spectrum-global-dimension-size-200);
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ border-radius: var(--spectrum-alias-border-radius-regular);
133
+
134
+ &:focus-ring {
135
+ box-shadow: inset 0 0 0 2px var(--spectrum-table-cell-border-color-key-focus);
136
+ outline: none;
137
+ }
138
+ }
139
+ }
140
+
112
141
  .react-spectrum-ListViewItem-checkbox {
113
142
  grid-area: checkbox;
114
143
  align-items: center;
@@ -185,6 +214,16 @@
185
214
  min-height: var(--spectrum-global-dimension-size-600);
186
215
  }
187
216
 
217
+ .react-spectrum-ListView--draggable .react-spectrum-ListViewItem {
218
+ padding-inline-start: 0;
219
+ }
220
+
221
+ .react-spectrum-ListViewItem--draggable {
222
+ .react-spectrum-ListViewItem-checkbox input {
223
+ inset-inline-start: 0;
224
+ }
225
+ }
226
+
188
227
  .react-spectrum-ListView-centeredWrapper {
189
228
  display: flex;
190
229
  align-items: center;