@react-spectrum/list 3.0.0-alpha.9 → 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 +572 -304
- package/dist/main.js.map +1 -1
- package/dist/module.js +574 -306
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +6 -16
- package/dist/types.d.ts.map +1 -1
- package/package.json +39 -35
- package/src/DragPreview.tsx +60 -0
- package/src/InsertionIndicator.tsx +46 -0
- package/src/ListView.tsx +184 -150
- package/src/ListViewItem.tsx +158 -62
- package/src/RootDropIndicator.tsx +28 -0
- package/src/styles.css +593 -0
- package/src/listview.css +0 -236
package/src/ListViewItem.tsx
CHANGED
|
@@ -12,65 +12,83 @@
|
|
|
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 {
|
|
17
|
-
import type {DraggableItemResult} from '@react-aria/dnd';
|
|
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';
|
|
18
19
|
import {FocusRing, useFocusRing} from '@react-aria/focus';
|
|
19
20
|
import {Grid} from '@react-spectrum/layout';
|
|
21
|
+
import {isFocusVisible as isGlobalFocusVisible, useHover} from '@react-aria/interactions';
|
|
20
22
|
import ListGripper from '@spectrum-icons/ui/ListGripper';
|
|
21
|
-
import listStyles from './
|
|
23
|
+
import listStyles from './styles.css';
|
|
22
24
|
import {ListViewContext} from './ListView';
|
|
23
25
|
import {mergeProps} from '@react-aria/utils';
|
|
26
|
+
import {Provider} from '@react-spectrum/provider';
|
|
24
27
|
import React, {useContext, useRef} from 'react';
|
|
28
|
+
import {Text} from '@react-spectrum/text';
|
|
25
29
|
import {useButton} from '@react-aria/button';
|
|
26
|
-
import {
|
|
27
|
-
import {useHover, usePress} from '@react-aria/interactions';
|
|
30
|
+
import {useListItem, useListSelectionCheckbox} from '@react-aria/list';
|
|
28
31
|
import {useLocale} from '@react-aria/i18n';
|
|
29
32
|
import {useVisuallyHidden} from '@react-aria/visually-hidden';
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
interface ListViewItemProps<T> {
|
|
35
|
+
item: Node<T>,
|
|
36
|
+
isEmphasized: boolean,
|
|
37
|
+
hasActions: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
32
41
|
let {
|
|
33
42
|
item,
|
|
34
|
-
isEmphasized
|
|
35
|
-
dragHooks
|
|
43
|
+
isEmphasized
|
|
36
44
|
} = props;
|
|
37
|
-
let
|
|
38
|
-
let {state, dragState, onAction, isListDraggable} = useContext(ListViewContext);
|
|
45
|
+
let {state, dragState, dropState, isListDraggable, isListDroppable, layout, dragHooks, dropHooks, loadingState} = useContext(ListViewContext);
|
|
39
46
|
let {direction} = useLocale();
|
|
40
47
|
let rowRef = useRef<HTMLDivElement>();
|
|
41
|
-
let cellRef = useRef<HTMLDivElement>();
|
|
42
48
|
let {
|
|
43
49
|
isFocusVisible: isFocusVisibleWithin,
|
|
44
50
|
focusProps: focusWithinProps
|
|
45
51
|
} = useFocusRing({within: true});
|
|
46
52
|
let {isFocusVisible, focusProps} = useFocusRing();
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
let {
|
|
54
|
+
rowProps,
|
|
55
|
+
gridCellProps,
|
|
56
|
+
isPressed,
|
|
57
|
+
descriptionProps,
|
|
58
|
+
isSelected,
|
|
59
|
+
isDisabled,
|
|
60
|
+
allowsSelection,
|
|
61
|
+
hasAction
|
|
62
|
+
} = useListItem({
|
|
53
63
|
node: item,
|
|
54
64
|
isVirtualized: true,
|
|
55
|
-
onAction: onAction ? () => onAction(item.key) : undefined,
|
|
56
65
|
shouldSelectOnPressUp: isListDraggable
|
|
57
66
|
}, state, rowRef);
|
|
58
|
-
let
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}, 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
|
+
|
|
62
73
|
let draggableItem: DraggableItemResult;
|
|
63
74
|
if (isListDraggable) {
|
|
64
75
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
65
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);
|
|
66
91
|
}
|
|
67
|
-
const mergedProps = mergeProps(
|
|
68
|
-
gridCellProps,
|
|
69
|
-
hoverProps,
|
|
70
|
-
focusWithinProps,
|
|
71
|
-
focusProps
|
|
72
|
-
);
|
|
73
|
-
let {checkboxProps} = useGridSelectionCheckbox({...props, key: item.key}, state);
|
|
74
92
|
|
|
75
93
|
let dragButtonRef = React.useRef();
|
|
76
94
|
let {buttonProps} = useButton({
|
|
@@ -78,30 +96,91 @@ export function ListViewItem(props) {
|
|
|
78
96
|
elementType: 'div'
|
|
79
97
|
}, dragButtonRef);
|
|
80
98
|
|
|
81
|
-
let chevron =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
);
|
|
95
128
|
|
|
96
129
|
let showCheckbox = state.selectionManager.selectionMode !== 'none' && state.selectionManager.selectionBehavior === 'toggle';
|
|
97
|
-
let isSelected = state.selectionManager.isSelected(item.key);
|
|
98
|
-
let showDragHandle = isDraggable && (isFocusVisibleWithin || isHovered || isPressed);
|
|
99
130
|
let {visuallyHiddenProps} = useVisuallyHidden();
|
|
131
|
+
|
|
132
|
+
let dropProps = isDroppable ? droppableItem?.dropProps : {'aria-hidden': droppableItem?.dropProps['aria-hidden']};
|
|
133
|
+
const mergedProps = mergeProps(
|
|
134
|
+
rowProps,
|
|
135
|
+
draggableItem?.dragProps,
|
|
136
|
+
dropProps,
|
|
137
|
+
hoverProps,
|
|
138
|
+
focusWithinProps,
|
|
139
|
+
focusProps
|
|
140
|
+
);
|
|
141
|
+
|
|
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>;
|
|
163
|
+
}
|
|
164
|
+
|
|
100
165
|
return (
|
|
101
166
|
<div
|
|
102
|
-
{...
|
|
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
|
+
}
|
|
103
181
|
ref={rowRef}>
|
|
104
182
|
<div
|
|
183
|
+
// TODO: refactor the css here now that we are focusing the row?
|
|
105
184
|
className={
|
|
106
185
|
classNames(
|
|
107
186
|
listStyles,
|
|
@@ -112,18 +191,23 @@ export function ListViewItem(props) {
|
|
|
112
191
|
'focus-ring': isFocusVisible,
|
|
113
192
|
'is-hovered': isHovered,
|
|
114
193
|
'is-selected': isSelected,
|
|
115
|
-
'is-
|
|
194
|
+
'is-disabled': isDisabled,
|
|
195
|
+
'is-prev-selected': state.selectionManager.isSelected(item.prevKey),
|
|
196
|
+
'is-next-selected': state.selectionManager.isSelected(item.nextKey),
|
|
116
197
|
'react-spectrum-ListViewItem--highlightSelection': state.selectionManager.selectionBehavior === 'replace' && (isSelected || state.selectionManager.isSelected(item.nextKey)),
|
|
117
|
-
'react-spectrum-ListViewItem--
|
|
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
|
|
118
203
|
}
|
|
119
204
|
)
|
|
120
205
|
}
|
|
121
|
-
|
|
122
|
-
{...mergedProps}>
|
|
206
|
+
{...gridCellProps}>
|
|
123
207
|
<Grid UNSAFE_className={listStyles['react-spectrum-ListViewItem-grid']}>
|
|
124
208
|
{isListDraggable &&
|
|
125
209
|
<div className={listStyles['react-spectrum-ListViewItem-draghandle-container']}>
|
|
126
|
-
{
|
|
210
|
+
{!isDisabled &&
|
|
127
211
|
<FocusRing focusRingClass={classNames(listStyles, 'focus-ring')}>
|
|
128
212
|
<div
|
|
129
213
|
{...buttonProps as React.HTMLAttributes<HTMLElement>}
|
|
@@ -133,7 +217,7 @@ export function ListViewItem(props) {
|
|
|
133
217
|
'react-spectrum-ListViewItem-draghandle-button'
|
|
134
218
|
)
|
|
135
219
|
}
|
|
136
|
-
style={!
|
|
220
|
+
style={!isFocusVisibleWithin ? {...visuallyHiddenProps.style} : {}}
|
|
137
221
|
ref={dragButtonRef}
|
|
138
222
|
draggable="true">
|
|
139
223
|
<ListGripper />
|
|
@@ -142,20 +226,32 @@ export function ListViewItem(props) {
|
|
|
142
226
|
}
|
|
143
227
|
</div>
|
|
144
228
|
}
|
|
145
|
-
{
|
|
146
|
-
<
|
|
147
|
-
UNSAFE_className={listStyles['react-spectrum-ListViewItem-checkbox']}
|
|
148
|
-
{...checkboxProps}
|
|
149
|
-
isEmphasized={isEmphasized} />
|
|
229
|
+
{isDropTarget && !dropIndicator?.dropIndicatorProps['aria-hidden'] &&
|
|
230
|
+
<div role="button" {...visuallyHiddenProps} {...dropIndicator?.dropIndicatorProps} ref={dropIndicatorRef} />
|
|
150
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>
|
|
151
249
|
<SlotProvider
|
|
152
250
|
slots={{
|
|
153
|
-
content: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
|
|
154
251
|
text: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
|
|
155
|
-
description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description']},
|
|
252
|
+
description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description'], ...descriptionProps},
|
|
156
253
|
icon: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-icon'], size: 'M'},
|
|
157
254
|
image: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-image']},
|
|
158
|
-
link: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content'], isQuiet: true},
|
|
159
255
|
actionButton: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'], isQuiet: true},
|
|
160
256
|
actionGroup: {
|
|
161
257
|
UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'],
|
|
@@ -164,7 +260,7 @@ export function ListViewItem(props) {
|
|
|
164
260
|
},
|
|
165
261
|
actionMenu: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actionmenu'], isQuiet: true}
|
|
166
262
|
}}>
|
|
167
|
-
{
|
|
263
|
+
{content}
|
|
168
264
|
<ClearSlots>
|
|
169
265
|
{chevron}
|
|
170
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
|
+
}
|