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