@react-spectrum/list 3.0.0-alpha.7 → 3.0.0-beta.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/main.css +1 -1
- package/dist/main.js +767 -370
- package/dist/main.js.map +1 -1
- package/dist/module.js +765 -368
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +6 -14
- package/dist/types.d.ts.map +1 -1
- package/package.json +40 -30
- package/src/DragPreview.tsx +60 -0
- package/src/InsertionIndicator.tsx +46 -0
- package/src/ListView.tsx +203 -93
- package/src/ListViewItem.tsx +190 -53
- package/src/RootDropIndicator.tsx +28 -0
- package/src/styles.css +593 -0
- package/src/listview.css +0 -197
package/src/ListViewItem.tsx
CHANGED
|
@@ -12,77 +12,175 @@
|
|
|
12
12
|
import {Checkbox} from '@react-spectrum/checkbox';
|
|
13
13
|
import ChevronLeftMedium from '@spectrum-icons/ui/ChevronLeftMedium';
|
|
14
14
|
import ChevronRightMedium from '@spectrum-icons/ui/ChevronRightMedium';
|
|
15
|
-
import {classNames, ClearSlots, SlotProvider} from '@react-spectrum/utils';
|
|
16
|
-
import {
|
|
15
|
+
import {classNames, ClearSlots, SlotProvider, useHasChild} from '@react-spectrum/utils';
|
|
16
|
+
import {CSSTransition} from 'react-transition-group';
|
|
17
|
+
import type {DraggableItemResult, DropIndicatorAria, DroppableItemResult} from '@react-aria/dnd';
|
|
18
|
+
import {DropTarget, Node} from '@react-types/shared';
|
|
19
|
+
import {FocusRing, useFocusRing} from '@react-aria/focus';
|
|
17
20
|
import {Grid} from '@react-spectrum/layout';
|
|
18
|
-
import
|
|
21
|
+
import {isFocusVisible as isGlobalFocusVisible, useHover} from '@react-aria/interactions';
|
|
22
|
+
import ListGripper from '@spectrum-icons/ui/ListGripper';
|
|
23
|
+
import listStyles from './styles.css';
|
|
19
24
|
import {ListViewContext} from './ListView';
|
|
20
25
|
import {mergeProps} from '@react-aria/utils';
|
|
26
|
+
import {Provider} from '@react-spectrum/provider';
|
|
21
27
|
import React, {useContext, useRef} from 'react';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
28
|
+
import {Text} from '@react-spectrum/text';
|
|
29
|
+
import {useButton} from '@react-aria/button';
|
|
30
|
+
import {useListItem, useListSelectionCheckbox} from '@react-aria/list';
|
|
25
31
|
import {useLocale} from '@react-aria/i18n';
|
|
32
|
+
import {useVisuallyHidden} from '@react-aria/visually-hidden';
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
interface ListViewItemProps<T> {
|
|
35
|
+
item: Node<T>,
|
|
36
|
+
isEmphasized: boolean,
|
|
37
|
+
hasActions: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
28
41
|
let {
|
|
29
42
|
item,
|
|
30
|
-
onAction,
|
|
31
43
|
isEmphasized
|
|
32
44
|
} = props;
|
|
33
|
-
let
|
|
34
|
-
let {state} = useContext(ListViewContext);
|
|
45
|
+
let {state, dragState, dropState, isListDraggable, isListDroppable, layout, dragHooks, dropHooks, loadingState} = useContext(ListViewContext);
|
|
35
46
|
let {direction} = useLocale();
|
|
36
47
|
let rowRef = useRef<HTMLDivElement>();
|
|
37
|
-
let cellRef = useRef<HTMLDivElement>();
|
|
38
48
|
let {
|
|
39
49
|
isFocusVisible: isFocusVisibleWithin,
|
|
40
50
|
focusProps: focusWithinProps
|
|
41
51
|
} = useFocusRing({within: true});
|
|
42
52
|
let {isFocusVisible, focusProps} = useFocusRing();
|
|
43
|
-
let
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
let {
|
|
54
|
+
rowProps,
|
|
55
|
+
gridCellProps,
|
|
56
|
+
isPressed,
|
|
57
|
+
descriptionProps,
|
|
58
|
+
isSelected,
|
|
59
|
+
isDisabled,
|
|
60
|
+
allowsSelection,
|
|
61
|
+
hasAction
|
|
62
|
+
} = useListItem({
|
|
48
63
|
node: item,
|
|
49
64
|
isVirtualized: true,
|
|
50
|
-
|
|
65
|
+
shouldSelectOnPressUp: isListDraggable
|
|
51
66
|
}, state, rowRef);
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}, state
|
|
67
|
+
let isDroppable = isListDroppable && !isDisabled;
|
|
68
|
+
let {hoverProps, isHovered} = useHover({isDisabled: !allowsSelection && !hasAction});
|
|
69
|
+
|
|
70
|
+
let {checkboxProps} = useListSelectionCheckbox({key: item.key}, state);
|
|
71
|
+
let hasDescription = useHasChild(`.${listStyles['react-spectrum-ListViewItem-description']}`, rowRef);
|
|
72
|
+
|
|
73
|
+
let draggableItem: DraggableItemResult;
|
|
74
|
+
if (isListDraggable) {
|
|
75
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
76
|
+
draggableItem = dragHooks.useDraggableItem({key: item.key}, dragState);
|
|
77
|
+
if (isDisabled) {
|
|
78
|
+
draggableItem = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let droppableItem: DroppableItemResult;
|
|
82
|
+
let isDropTarget: boolean;
|
|
83
|
+
let dropIndicator: DropIndicatorAria;
|
|
84
|
+
let dropIndicatorRef = useRef();
|
|
85
|
+
if (isListDroppable) {
|
|
86
|
+
let target = {type: 'item', key: item.key, dropPosition: 'on'} as DropTarget;
|
|
87
|
+
isDropTarget = dropState.isDropTarget(target);
|
|
88
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
89
|
+
droppableItem = dropHooks.useDroppableItem({target}, dropState, rowRef);
|
|
90
|
+
dropIndicator = dropHooks.useDropIndicator({target}, dropState, dropIndicatorRef);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let dragButtonRef = React.useRef();
|
|
94
|
+
let {buttonProps} = useButton({
|
|
95
|
+
...draggableItem?.dragButtonProps,
|
|
96
|
+
elementType: 'div'
|
|
97
|
+
}, dragButtonRef);
|
|
98
|
+
|
|
99
|
+
let chevron = direction === 'ltr'
|
|
100
|
+
? (
|
|
101
|
+
<ChevronRightMedium
|
|
102
|
+
aria-hidden="true"
|
|
103
|
+
UNSAFE_className={
|
|
104
|
+
classNames(
|
|
105
|
+
listStyles,
|
|
106
|
+
'react-spectrum-ListViewItem-parentIndicator',
|
|
107
|
+
{
|
|
108
|
+
'react-spectrum-ListViewItem-parentIndicator--hasChildItems': item.props.hasChildItems,
|
|
109
|
+
'is-disabled': !hasAction
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
} />
|
|
113
|
+
)
|
|
114
|
+
: (
|
|
115
|
+
<ChevronLeftMedium
|
|
116
|
+
aria-hidden="true"
|
|
117
|
+
UNSAFE_className={
|
|
118
|
+
classNames(
|
|
119
|
+
listStyles,
|
|
120
|
+
'react-spectrum-ListViewItem-parentIndicator',
|
|
121
|
+
{
|
|
122
|
+
'react-spectrum-ListViewItem-parentIndicator--hasChildItems': item.props.hasChildItems,
|
|
123
|
+
'is-disabled': !hasAction
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
} />
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
let showCheckbox = state.selectionManager.selectionMode !== 'none' && state.selectionManager.selectionBehavior === 'toggle';
|
|
130
|
+
let {visuallyHiddenProps} = useVisuallyHidden();
|
|
131
|
+
|
|
132
|
+
let dropProps = isDroppable ? droppableItem?.dropProps : {'aria-hidden': droppableItem?.dropProps['aria-hidden']};
|
|
56
133
|
const mergedProps = mergeProps(
|
|
57
|
-
|
|
134
|
+
rowProps,
|
|
135
|
+
draggableItem?.dragProps,
|
|
136
|
+
dropProps,
|
|
58
137
|
hoverProps,
|
|
59
138
|
focusWithinProps,
|
|
60
139
|
focusProps
|
|
61
140
|
);
|
|
62
|
-
let {checkboxProps} = useGridSelectionCheckbox({...props, key: item.key}, state);
|
|
63
141
|
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
142
|
+
let isFirstRow = item.prevKey == null;
|
|
143
|
+
let isLastRow = item.nextKey == null;
|
|
144
|
+
// Figure out if the ListView content is equal or greater in height to the container. If so, we'll need to round the bottom
|
|
145
|
+
// border corners of the last row when selected and we can get rid of the bottom border if it isn't selected to avoid border overlap
|
|
146
|
+
// with bottom border
|
|
147
|
+
let isFlushWithContainerBottom = false;
|
|
148
|
+
if (isLastRow && loadingState !== 'loadingMore') {
|
|
149
|
+
if (layout.getContentSize()?.height >= layout.virtualizer?.getVisibleRect().height) {
|
|
150
|
+
isFlushWithContainerBottom = true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// previous item isn't selected
|
|
154
|
+
// and the previous item isn't focused or, if it is focused, then if focus globally isn't visible or just focus isn't in the listview
|
|
155
|
+
let roundTops = (!state.selectionManager.isSelected(item.prevKey)
|
|
156
|
+
&& (state.selectionManager.focusedKey !== item.prevKey || !(isGlobalFocusVisible() && state.selectionManager.isFocused)));
|
|
157
|
+
let roundBottoms = (!state.selectionManager.isSelected(item.nextKey)
|
|
158
|
+
&& (state.selectionManager.focusedKey !== item.nextKey || !(isGlobalFocusVisible() && state.selectionManager.isFocused)));
|
|
159
|
+
|
|
160
|
+
let content = typeof item.rendered === 'string' ? <Text>{item.rendered}</Text> : item.rendered;
|
|
161
|
+
if (isDisabled) {
|
|
162
|
+
content = <Provider isDisabled>{content}</Provider>;
|
|
77
163
|
}
|
|
78
164
|
|
|
79
|
-
let showCheckbox = state.selectionManager.selectionMode !== 'none' && state.selectionManager.selectionBehavior === 'toggle';
|
|
80
|
-
let isSelected = state.selectionManager.isSelected(item.key);
|
|
81
165
|
return (
|
|
82
166
|
<div
|
|
83
|
-
{...
|
|
167
|
+
{...mergedProps}
|
|
168
|
+
className={
|
|
169
|
+
classNames(
|
|
170
|
+
listStyles,
|
|
171
|
+
'react-spectrum-ListView-row',
|
|
172
|
+
{
|
|
173
|
+
'focus-ring': isFocusVisible,
|
|
174
|
+
'round-tops':
|
|
175
|
+
roundTops || (isHovered && !isSelected && state.selectionManager.focusedKey !== item.key),
|
|
176
|
+
'round-bottoms':
|
|
177
|
+
roundBottoms || (isHovered && !isSelected && state.selectionManager.focusedKey !== item.key)
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
}
|
|
84
181
|
ref={rowRef}>
|
|
85
182
|
<div
|
|
183
|
+
// TODO: refactor the css here now that we are focusing the row?
|
|
86
184
|
className={
|
|
87
185
|
classNames(
|
|
88
186
|
listStyles,
|
|
@@ -93,28 +191,67 @@ export function ListViewItem(props) {
|
|
|
93
191
|
'focus-ring': isFocusVisible,
|
|
94
192
|
'is-hovered': isHovered,
|
|
95
193
|
'is-selected': isSelected,
|
|
96
|
-
'is-
|
|
97
|
-
'
|
|
194
|
+
'is-disabled': isDisabled,
|
|
195
|
+
'is-prev-selected': state.selectionManager.isSelected(item.prevKey),
|
|
196
|
+
'is-next-selected': state.selectionManager.isSelected(item.nextKey),
|
|
197
|
+
'react-spectrum-ListViewItem--highlightSelection': state.selectionManager.selectionBehavior === 'replace' && (isSelected || state.selectionManager.isSelected(item.nextKey)),
|
|
198
|
+
'react-spectrum-ListViewItem--dropTarget': !!isDropTarget,
|
|
199
|
+
'react-spectrum-ListViewItem--firstRow': isFirstRow,
|
|
200
|
+
'react-spectrum-ListViewItem--lastRow': isLastRow,
|
|
201
|
+
'react-spectrum-ListViewItem--isFlushBottom': isFlushWithContainerBottom,
|
|
202
|
+
'react-spectrum-ListViewItem--hasDescription': hasDescription
|
|
98
203
|
}
|
|
99
204
|
)
|
|
100
205
|
}
|
|
101
|
-
|
|
102
|
-
{...mergedProps}>
|
|
206
|
+
{...gridCellProps}>
|
|
103
207
|
<Grid UNSAFE_className={listStyles['react-spectrum-ListViewItem-grid']}>
|
|
104
|
-
{
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
208
|
+
{isListDraggable &&
|
|
209
|
+
<div className={listStyles['react-spectrum-ListViewItem-draghandle-container']}>
|
|
210
|
+
{!isDisabled &&
|
|
211
|
+
<FocusRing focusRingClass={classNames(listStyles, 'focus-ring')}>
|
|
212
|
+
<div
|
|
213
|
+
{...buttonProps as React.HTMLAttributes<HTMLElement>}
|
|
214
|
+
className={
|
|
215
|
+
classNames(
|
|
216
|
+
listStyles,
|
|
217
|
+
'react-spectrum-ListViewItem-draghandle-button'
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
style={!isFocusVisibleWithin ? {...visuallyHiddenProps.style} : {}}
|
|
221
|
+
ref={dragButtonRef}
|
|
222
|
+
draggable="true">
|
|
223
|
+
<ListGripper />
|
|
224
|
+
</div>
|
|
225
|
+
</FocusRing>
|
|
226
|
+
}
|
|
227
|
+
</div>
|
|
228
|
+
}
|
|
229
|
+
{isDropTarget && !dropIndicator?.dropIndicatorProps['aria-hidden'] &&
|
|
230
|
+
<div role="button" {...visuallyHiddenProps} {...dropIndicator?.dropIndicatorProps} ref={dropIndicatorRef} />
|
|
231
|
+
}
|
|
232
|
+
<CSSTransition
|
|
233
|
+
in={showCheckbox}
|
|
234
|
+
unmountOnExit
|
|
235
|
+
classNames={{
|
|
236
|
+
enter: listStyles['react-spectrum-ListViewItem-checkbox--enter'],
|
|
237
|
+
enterActive: listStyles['react-spectrum-ListViewItem-checkbox--enterActive'],
|
|
238
|
+
exit: listStyles['react-spectrum-ListViewItem-checkbox--exit'],
|
|
239
|
+
exitActive: listStyles['react-spectrum-ListViewItem-checkbox--exitActive']
|
|
240
|
+
}}
|
|
241
|
+
timeout={160} >
|
|
242
|
+
<div className={listStyles['react-spectrum-ListViewItem-checkboxWrapper']}>
|
|
243
|
+
<Checkbox
|
|
244
|
+
{...checkboxProps}
|
|
245
|
+
UNSAFE_className={listStyles['react-spectrum-ListViewItem-checkbox']}
|
|
246
|
+
isEmphasized={isEmphasized} />
|
|
247
|
+
</div>
|
|
248
|
+
</CSSTransition>
|
|
110
249
|
<SlotProvider
|
|
111
250
|
slots={{
|
|
112
|
-
content: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
|
|
113
251
|
text: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
|
|
114
|
-
description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description']},
|
|
252
|
+
description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description'], ...descriptionProps},
|
|
115
253
|
icon: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-icon'], size: 'M'},
|
|
116
254
|
image: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-image']},
|
|
117
|
-
link: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content'], isQuiet: true},
|
|
118
255
|
actionButton: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'], isQuiet: true},
|
|
119
256
|
actionGroup: {
|
|
120
257
|
UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'],
|
|
@@ -123,7 +260,7 @@ export function ListViewItem(props) {
|
|
|
123
260
|
},
|
|
124
261
|
actionMenu: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actionmenu'], isQuiet: true}
|
|
125
262
|
}}>
|
|
126
|
-
{
|
|
263
|
+
{content}
|
|
127
264
|
<ClearSlots>
|
|
128
265
|
{chevron}
|
|
129
266
|
</ClearSlots>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {ListViewContext} from './ListView';
|
|
2
|
+
import React, {useContext, useRef} from 'react';
|
|
3
|
+
import {useVisuallyHidden} from '@react-aria/visually-hidden';
|
|
4
|
+
|
|
5
|
+
export default function RootDropIndicator() {
|
|
6
|
+
let {dropState, dropHooks} = useContext(ListViewContext);
|
|
7
|
+
let ref = useRef();
|
|
8
|
+
let {dropIndicatorProps} = dropHooks.useDropIndicator({
|
|
9
|
+
target: {type: 'root'}
|
|
10
|
+
}, dropState, ref);
|
|
11
|
+
let isDropTarget = dropState.isDropTarget({type: 'root'});
|
|
12
|
+
let {visuallyHiddenProps} = useVisuallyHidden();
|
|
13
|
+
|
|
14
|
+
if (!isDropTarget && dropIndicatorProps['aria-hidden']) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div role="row" aria-hidden={dropIndicatorProps['aria-hidden']}>
|
|
20
|
+
<div
|
|
21
|
+
role="gridcell"
|
|
22
|
+
aria-selected="false"
|
|
23
|
+
{...visuallyHiddenProps}>
|
|
24
|
+
<div role="button" {...dropIndicatorProps} ref={ref} />
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|