@react-spectrum/list 3.0.0-alpha.0 → 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 -2
- package/dist/main.js +915 -373
- package/dist/main.js.map +1 -1
- package/dist/module.js +929 -328
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +9 -10
- package/dist/types.d.ts.map +1 -1
- package/package.json +41 -30
- package/src/DragPreview.tsx +60 -0
- package/src/InsertionIndicator.tsx +42 -0
- package/src/ListView.tsx +235 -63
- package/src/ListViewItem.tsx +218 -24
- package/src/RootDropIndicator.tsx +25 -0
- package/src/styles.css +592 -0
- package/dist/main.css.map +0 -1
- package/src/listview.css +0 -49
package/src/ListView.tsx
CHANGED
|
@@ -9,122 +9,275 @@
|
|
|
9
9
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
import {AriaLabelingProps, CollectionBase, DOMProps, DOMRef, StyleProps} from '@react-types/shared';
|
|
13
12
|
import {classNames, useDOMRef, useStyleProps} from '@react-spectrum/utils';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
13
|
+
import {DOMRef, LoadingState} from '@react-types/shared';
|
|
14
|
+
import type {DraggableCollectionState, DroppableCollectionState} from '@react-stately/dnd';
|
|
15
|
+
import {DragHooks, DropHooks} from '@react-spectrum/dnd';
|
|
16
|
+
import type {DroppableCollectionResult} from '@react-aria/dnd';
|
|
17
|
+
import {filterDOMProps, useLayoutEffect} from '@react-aria/utils';
|
|
18
|
+
import InsertionIndicator from './InsertionIndicator';
|
|
16
19
|
// @ts-ignore
|
|
17
20
|
import intlMessages from '../intl/*.json';
|
|
18
21
|
import {ListLayout} from '@react-stately/layout';
|
|
19
22
|
import {ListState, useListState} from '@react-stately/list';
|
|
20
|
-
import listStyles from './
|
|
23
|
+
import listStyles from './styles.css';
|
|
21
24
|
import {ListViewItem} from './ListViewItem';
|
|
25
|
+
import {mergeProps} from '@react-aria/utils';
|
|
22
26
|
import {ProgressCircle} from '@react-spectrum/progress';
|
|
23
|
-
import React, {ReactElement, useContext, useMemo} from 'react';
|
|
24
|
-
import {
|
|
27
|
+
import React, {Key, ReactElement, useContext, useMemo, useRef, useState} from 'react';
|
|
28
|
+
import {Rect} from '@react-stately/virtualizer';
|
|
29
|
+
import RootDropIndicator from './RootDropIndicator';
|
|
30
|
+
import {DragPreview as SpectrumDragPreview} from './DragPreview';
|
|
31
|
+
import {SpectrumListProps} from '@react-types/list';
|
|
32
|
+
import {useCollator, useMessageFormatter} from '@react-aria/i18n';
|
|
33
|
+
import {useList} from '@react-aria/list';
|
|
25
34
|
import {useProvider} from '@react-spectrum/provider';
|
|
26
35
|
import {Virtualizer} from '@react-aria/virtualizer';
|
|
27
36
|
|
|
37
|
+
interface ListViewContextValue<T> {
|
|
38
|
+
state: ListState<T>,
|
|
39
|
+
dragState: DraggableCollectionState,
|
|
40
|
+
dropState: DroppableCollectionState,
|
|
41
|
+
dragHooks: DragHooks,
|
|
42
|
+
dropHooks: DropHooks,
|
|
43
|
+
onAction:(key: Key) => void,
|
|
44
|
+
isListDraggable: boolean,
|
|
45
|
+
isListDroppable: boolean,
|
|
46
|
+
layout: ListLayout<T>,
|
|
47
|
+
loadingState: LoadingState
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const ListViewContext = React.createContext<ListViewContextValue<unknown>>(null);
|
|
28
51
|
|
|
29
|
-
|
|
52
|
+
const ROW_HEIGHTS = {
|
|
53
|
+
compact: {
|
|
54
|
+
medium: 32,
|
|
55
|
+
large: 40
|
|
56
|
+
},
|
|
57
|
+
regular: {
|
|
58
|
+
medium: 40,
|
|
59
|
+
large: 50
|
|
60
|
+
},
|
|
61
|
+
spacious: {
|
|
62
|
+
medium: 48,
|
|
63
|
+
large: 60
|
|
64
|
+
}
|
|
65
|
+
};
|
|
30
66
|
|
|
31
|
-
|
|
67
|
+
function useListLayout<T>(state: ListState<T>, density: SpectrumListProps<T>['density'], allowDisabledKeyFocus: boolean) {
|
|
32
68
|
let {scale} = useProvider();
|
|
33
69
|
let collator = useCollator({usage: 'search', sensitivity: 'base'});
|
|
70
|
+
let isEmpty = state.collection.size === 0;
|
|
34
71
|
let layout = useMemo(() =>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
new ListLayout<T>({
|
|
73
|
+
estimatedRowHeight: ROW_HEIGHTS[density][scale],
|
|
74
|
+
padding: 0,
|
|
75
|
+
collator,
|
|
76
|
+
loaderHeight: isEmpty ? null : ROW_HEIGHTS[density][scale],
|
|
77
|
+
allowDisabledKeyFocus
|
|
78
|
+
})
|
|
79
|
+
, [collator, scale, density, isEmpty, allowDisabledKeyFocus]);
|
|
41
80
|
|
|
42
81
|
layout.collection = state.collection;
|
|
43
82
|
layout.disabledKeys = state.disabledKeys;
|
|
44
83
|
return layout;
|
|
45
84
|
}
|
|
46
85
|
|
|
47
|
-
|
|
48
|
-
isLoading?: boolean,
|
|
49
|
-
renderEmptyState?: () => JSX.Element,
|
|
50
|
-
transitionDuration?: number
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDivElement>) {
|
|
86
|
+
function ListView<T extends object>(props: SpectrumListProps<T>, ref: DOMRef<HTMLDivElement>) {
|
|
54
87
|
let {
|
|
55
|
-
|
|
88
|
+
density = 'regular',
|
|
89
|
+
onLoadMore,
|
|
90
|
+
loadingState,
|
|
91
|
+
isQuiet,
|
|
92
|
+
overflowMode = 'truncate',
|
|
93
|
+
onAction,
|
|
94
|
+
dragHooks,
|
|
95
|
+
dropHooks,
|
|
96
|
+
...otherProps
|
|
56
97
|
} = props;
|
|
98
|
+
let isListDraggable = !!dragHooks;
|
|
99
|
+
let isListDroppable = !!dropHooks;
|
|
100
|
+
let dragHooksProvided = useRef(isListDraggable);
|
|
101
|
+
let dropHooksProvided = useRef(isListDroppable);
|
|
102
|
+
if (dragHooksProvided.current !== isListDraggable) {
|
|
103
|
+
console.warn('Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
|
|
104
|
+
}
|
|
105
|
+
if (dropHooksProvided.current !== isListDroppable) {
|
|
106
|
+
console.warn('Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
|
|
107
|
+
}
|
|
57
108
|
let domRef = useDOMRef(ref);
|
|
58
|
-
let
|
|
109
|
+
let state = useListState({
|
|
110
|
+
...props,
|
|
111
|
+
selectionBehavior: props.selectionStyle === 'highlight' ? 'replace' : 'toggle'
|
|
112
|
+
});
|
|
113
|
+
let {collection, selectionManager} = state;
|
|
59
114
|
let formatMessage = useMessageFormatter(intlMessages);
|
|
115
|
+
let isLoading = loadingState === 'loading' || loadingState === 'loadingMore';
|
|
60
116
|
|
|
61
117
|
let {styleProps} = useStyleProps(props);
|
|
62
|
-
let
|
|
63
|
-
let
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
let
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
118
|
+
let layout = useListLayout(state, props.density || 'regular', state.selectionManager.disabledBehavior === 'selection');
|
|
119
|
+
let dragState: DraggableCollectionState;
|
|
120
|
+
let preview = useRef(null);
|
|
121
|
+
if (isListDraggable) {
|
|
122
|
+
dragState = dragHooks.useDraggableCollectionState({
|
|
123
|
+
collection,
|
|
124
|
+
selectionManager,
|
|
125
|
+
preview
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let DragPreview = dragHooks?.DragPreview;
|
|
130
|
+
let dropState: DroppableCollectionState;
|
|
131
|
+
let droppableCollection: DroppableCollectionResult;
|
|
132
|
+
let isRootDropTarget: boolean;
|
|
133
|
+
if (isListDroppable) {
|
|
134
|
+
dropState = dropHooks.useDroppableCollectionState({
|
|
135
|
+
collection,
|
|
136
|
+
selectionManager
|
|
137
|
+
});
|
|
138
|
+
droppableCollection = dropHooks.useDroppableCollection({
|
|
139
|
+
keyboardDelegate: layout,
|
|
140
|
+
getDropTargetFromPoint(x, y) {
|
|
141
|
+
let closest = null;
|
|
142
|
+
let closestDistance = Infinity;
|
|
143
|
+
let closestDir = null;
|
|
144
|
+
|
|
145
|
+
x += domRef.current.scrollLeft;
|
|
146
|
+
y += domRef.current.scrollTop;
|
|
147
|
+
|
|
148
|
+
let visible = layout.getVisibleLayoutInfos(new Rect(x - 50, y - 50, x + 50, y + 50));
|
|
149
|
+
|
|
150
|
+
for (let layoutInfo of visible) {
|
|
151
|
+
let r = layoutInfo.rect;
|
|
152
|
+
let points: [number, number, string][] = [
|
|
153
|
+
[r.x, r.y + 4, 'before'],
|
|
154
|
+
[r.maxX, r.y + 4, 'before'],
|
|
155
|
+
[r.x, r.maxY - 8, 'after'],
|
|
156
|
+
[r.maxX, r.maxY - 8, 'after']
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
for (let [px, py, dir] of points) {
|
|
160
|
+
let dx = px - x;
|
|
161
|
+
let dy = py - y;
|
|
162
|
+
let d = dx * dx + dy * dy;
|
|
163
|
+
if (d < closestDistance) {
|
|
164
|
+
closestDistance = d;
|
|
165
|
+
closest = layoutInfo;
|
|
166
|
+
closestDir = dir;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// TODO: Best way to implement only for when closest can be dropped on
|
|
171
|
+
// TODO: Figure out the typescript for this
|
|
172
|
+
// @ts-ignore
|
|
173
|
+
if (y >= r.y + 10 && y <= r.maxY - 10 && collection.getItem(closest.key).value.type === 'folder') {
|
|
174
|
+
closestDir = 'on';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let key = closest?.key;
|
|
179
|
+
if (key) {
|
|
180
|
+
return {
|
|
181
|
+
type: 'item',
|
|
182
|
+
key,
|
|
183
|
+
dropPosition: closestDir
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}, dropState, domRef);
|
|
188
|
+
|
|
189
|
+
isRootDropTarget = dropState.isDropTarget({type: 'root'});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let {gridProps} = useList({
|
|
89
193
|
...props,
|
|
90
194
|
isVirtualized: true,
|
|
91
|
-
keyboardDelegate,
|
|
92
|
-
|
|
93
|
-
}, state);
|
|
195
|
+
keyboardDelegate: layout,
|
|
196
|
+
onAction
|
|
197
|
+
}, state, domRef);
|
|
94
198
|
|
|
95
199
|
// Sync loading state into the layout.
|
|
96
|
-
layout.isLoading =
|
|
200
|
+
layout.isLoading = isLoading;
|
|
201
|
+
|
|
202
|
+
let focusedKey = selectionManager.focusedKey;
|
|
203
|
+
if (dropState?.target?.type === 'item') {
|
|
204
|
+
focusedKey = dropState.target.key;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// wait for layout to get accurate measurements
|
|
208
|
+
let [isVerticalScrollbarVisible, setVerticalScollbarVisible] = useState(false);
|
|
209
|
+
let [isHorizontalScrollbarVisible, setHorizontalScollbarVisible] = useState(false);
|
|
210
|
+
useLayoutEffect(() => {
|
|
211
|
+
if (domRef.current) {
|
|
212
|
+
// 2 is the width of the border which is not part of the box size
|
|
213
|
+
setVerticalScollbarVisible(domRef.current.clientWidth + 2 < domRef.current.offsetWidth);
|
|
214
|
+
setHorizontalScollbarVisible(domRef.current.clientHeight + 2 < domRef.current.offsetHeight);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
let hasAnyChildren = useMemo(() => [...collection].some(item => item.hasChildNodes), [collection]);
|
|
97
219
|
|
|
98
220
|
return (
|
|
99
|
-
<ListViewContext.Provider value={{state,
|
|
221
|
+
<ListViewContext.Provider value={{state, dragState, dropState, dragHooks, dropHooks, onAction, isListDraggable, isListDroppable, layout, loadingState}}>
|
|
100
222
|
<Virtualizer
|
|
223
|
+
{...mergeProps(isListDroppable && droppableCollection?.collectionProps, gridProps)}
|
|
224
|
+
{...filterDOMProps(otherProps)}
|
|
101
225
|
{...gridProps}
|
|
102
226
|
{...styleProps}
|
|
227
|
+
isLoading={isLoading}
|
|
228
|
+
onLoadMore={onLoadMore}
|
|
103
229
|
ref={domRef}
|
|
104
|
-
focusedKey={
|
|
105
|
-
sizeToFit="height"
|
|
230
|
+
focusedKey={focusedKey}
|
|
106
231
|
scrollDirection="vertical"
|
|
107
232
|
className={
|
|
108
233
|
classNames(
|
|
109
234
|
listStyles,
|
|
110
235
|
'react-spectrum-ListView',
|
|
236
|
+
`react-spectrum-ListView--${density}`,
|
|
237
|
+
'react-spectrum-ListView--emphasized',
|
|
238
|
+
{
|
|
239
|
+
'react-spectrum-ListView--quiet': isQuiet,
|
|
240
|
+
'react-spectrum-ListView--loadingMore': loadingState === 'loadingMore',
|
|
241
|
+
'react-spectrum-ListView--draggable': !!isListDraggable,
|
|
242
|
+
'react-spectrum-ListView--dropTarget': !!isRootDropTarget,
|
|
243
|
+
'react-spectrum-ListView--isVerticalScrollbarVisible': isVerticalScrollbarVisible,
|
|
244
|
+
'react-spectrum-ListView--isHorizontalScrollbarVisible': isHorizontalScrollbarVisible,
|
|
245
|
+
'react-spectrum-ListView--hasAnyChildren': hasAnyChildren,
|
|
246
|
+
'react-spectrum-ListView--wrap': overflowMode === 'wrap'
|
|
247
|
+
},
|
|
111
248
|
styleProps.className
|
|
112
249
|
)
|
|
113
250
|
}
|
|
114
251
|
layout={layout}
|
|
115
252
|
collection={collection}
|
|
116
|
-
transitionDuration={
|
|
253
|
+
transitionDuration={isLoading ? 160 : 220}>
|
|
117
254
|
{(type, item) => {
|
|
118
255
|
if (type === 'item') {
|
|
119
256
|
return (
|
|
120
|
-
|
|
257
|
+
<>
|
|
258
|
+
{isListDroppable && collection.getKeyBefore(item.key) == null &&
|
|
259
|
+
<RootDropIndicator key="root" />
|
|
260
|
+
}
|
|
261
|
+
{isListDroppable &&
|
|
262
|
+
<InsertionIndicator
|
|
263
|
+
key={`${item.key}-before`}
|
|
264
|
+
target={{key: item.key, type: 'item', dropPosition: 'before'}} />
|
|
265
|
+
}
|
|
266
|
+
<ListViewItem item={item} isEmphasized hasActions={!!onAction} />
|
|
267
|
+
{isListDroppable &&
|
|
268
|
+
<InsertionIndicator
|
|
269
|
+
key={`${item.key}-after`}
|
|
270
|
+
target={{key: item.key, type: 'item', dropPosition: 'after'}}
|
|
271
|
+
isPresentationOnly={collection.getKeyAfter(item.key) !== null} />
|
|
272
|
+
}
|
|
273
|
+
</>
|
|
121
274
|
);
|
|
122
275
|
} else if (type === 'loader') {
|
|
123
276
|
return (
|
|
124
277
|
<CenteredWrapper>
|
|
125
278
|
<ProgressCircle
|
|
126
279
|
isIndeterminate
|
|
127
|
-
aria-label={
|
|
280
|
+
aria-label={collection.size > 0 ? formatMessage('loadingMore') : formatMessage('loading')} />
|
|
128
281
|
</CenteredWrapper>
|
|
129
282
|
);
|
|
130
283
|
} else if (type === 'placeholder') {
|
|
@@ -142,18 +295,34 @@ function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDiv
|
|
|
142
295
|
|
|
143
296
|
}}
|
|
144
297
|
</Virtualizer>
|
|
298
|
+
{DragPreview && isListDraggable &&
|
|
299
|
+
<DragPreview ref={preview}>
|
|
300
|
+
{() => {
|
|
301
|
+
let item = state.collection.getItem(dragState.draggedKey);
|
|
302
|
+
let itemCount = dragState.draggingKeys.size;
|
|
303
|
+
let itemHeight = layout.getLayoutInfo(dragState.draggedKey).rect.height;
|
|
304
|
+
return <SpectrumDragPreview item={item} itemCount={itemCount} itemHeight={itemHeight} />;
|
|
305
|
+
}}
|
|
306
|
+
</DragPreview>
|
|
307
|
+
}
|
|
145
308
|
</ListViewContext.Provider>
|
|
146
309
|
);
|
|
147
310
|
}
|
|
148
311
|
|
|
149
|
-
|
|
150
312
|
function CenteredWrapper({children}) {
|
|
151
313
|
let {state} = useContext(ListViewContext);
|
|
152
314
|
return (
|
|
153
315
|
<div
|
|
154
316
|
role="row"
|
|
155
317
|
aria-rowindex={state.collection.size + 1}
|
|
156
|
-
className={
|
|
318
|
+
className={
|
|
319
|
+
classNames(
|
|
320
|
+
listStyles,
|
|
321
|
+
'react-spectrum-ListView-centeredWrapper',
|
|
322
|
+
{
|
|
323
|
+
'react-spectrum-ListView-centeredWrapper--loadingMore': state.collection.size > 0
|
|
324
|
+
}
|
|
325
|
+
)}>
|
|
157
326
|
<div role="gridcell">
|
|
158
327
|
{children}
|
|
159
328
|
</div>
|
|
@@ -161,5 +330,8 @@ function CenteredWrapper({children}) {
|
|
|
161
330
|
);
|
|
162
331
|
}
|
|
163
332
|
|
|
164
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Lists display a linear collection of data. They allow users to quickly scan, sort, compare, and take action on large amounts of data.
|
|
335
|
+
*/
|
|
336
|
+
const _ListView = React.forwardRef(ListView) as <T>(props: SpectrumListProps<T> & {ref?: DOMRef<HTMLDivElement>}) => ReactElement;
|
|
165
337
|
export {_ListView as ListView};
|
package/src/ListViewItem.tsx
CHANGED
|
@@ -9,62 +9,256 @@
|
|
|
9
9
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
12
|
+
import {Checkbox} from '@react-spectrum/checkbox';
|
|
13
|
+
import ChevronLeftMedium from '@spectrum-icons/ui/ChevronLeftMedium';
|
|
14
|
+
import ChevronRightMedium from '@spectrum-icons/ui/ChevronRightMedium';
|
|
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';
|
|
19
|
+
import {FocusRing, useFocusRing} from '@react-aria/focus';
|
|
20
|
+
import {Grid} from '@react-spectrum/layout';
|
|
21
|
+
import {isFocusVisible as isGlobalFocusVisible, useHover} from '@react-aria/interactions';
|
|
22
|
+
import ListGripper from '@spectrum-icons/ui/ListGripper';
|
|
23
|
+
import listStyles from './styles.css';
|
|
14
24
|
import {ListViewContext} from './ListView';
|
|
15
25
|
import {mergeProps} from '@react-aria/utils';
|
|
26
|
+
import {Provider} from '@react-spectrum/provider';
|
|
16
27
|
import React, {useContext, useRef} from 'react';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
28
|
+
import {Text} from '@react-spectrum/text';
|
|
29
|
+
import {useButton} from '@react-aria/button';
|
|
30
|
+
import {useListItem, useListSelectionCheckbox} from '@react-aria/list';
|
|
31
|
+
import {useLocale} from '@react-aria/i18n';
|
|
32
|
+
import {useVisuallyHidden} from '@react-aria/visually-hidden';
|
|
20
33
|
|
|
34
|
+
interface ListViewItemProps<T> {
|
|
35
|
+
item: Node<T>,
|
|
36
|
+
isEmphasized: boolean,
|
|
37
|
+
hasActions: boolean
|
|
38
|
+
}
|
|
21
39
|
|
|
22
|
-
export function ListViewItem(props) {
|
|
40
|
+
export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
23
41
|
let {
|
|
24
|
-
item
|
|
42
|
+
item,
|
|
43
|
+
isEmphasized
|
|
25
44
|
} = props;
|
|
26
|
-
let {state} = useContext(ListViewContext);
|
|
27
|
-
let
|
|
45
|
+
let {state, dragState, dropState, isListDraggable, isListDroppable, layout, dragHooks, dropHooks, loadingState} = useContext(ListViewContext);
|
|
46
|
+
let {direction} = useLocale();
|
|
47
|
+
let rowRef = useRef<HTMLDivElement>();
|
|
28
48
|
let {
|
|
29
49
|
isFocusVisible: isFocusVisibleWithin,
|
|
30
50
|
focusProps: focusWithinProps
|
|
31
51
|
} = useFocusRing({within: true});
|
|
32
52
|
let {isFocusVisible, focusProps} = useFocusRing();
|
|
33
|
-
|
|
34
|
-
let {
|
|
53
|
+
|
|
54
|
+
let {
|
|
55
|
+
rowProps,
|
|
56
|
+
gridCellProps,
|
|
57
|
+
isPressed,
|
|
58
|
+
isSelected,
|
|
59
|
+
isDisabled,
|
|
60
|
+
allowsSelection,
|
|
61
|
+
hasAction
|
|
62
|
+
} = useListItem({
|
|
35
63
|
node: item,
|
|
36
64
|
isVirtualized: true,
|
|
37
|
-
|
|
38
|
-
}, state);
|
|
39
|
-
let
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
65
|
+
shouldSelectOnPressUp: isListDraggable
|
|
66
|
+
}, state, rowRef);
|
|
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
|
+
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);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let dragButtonRef = React.useRef();
|
|
91
|
+
let {buttonProps} = useButton({
|
|
92
|
+
...draggableItem?.dragButtonProps,
|
|
93
|
+
elementType: 'div'
|
|
94
|
+
}, dragButtonRef);
|
|
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
|
+
);
|
|
125
|
+
|
|
126
|
+
let showCheckbox = state.selectionManager.selectionMode !== 'none' && state.selectionManager.selectionBehavior === 'toggle';
|
|
127
|
+
let {visuallyHiddenProps} = useVisuallyHidden();
|
|
128
|
+
|
|
44
129
|
const mergedProps = mergeProps(
|
|
45
|
-
|
|
130
|
+
rowProps,
|
|
131
|
+
draggableItem?.dragProps,
|
|
132
|
+
isDroppable && droppableItem?.dropProps,
|
|
46
133
|
hoverProps,
|
|
47
134
|
focusWithinProps,
|
|
48
135
|
focusProps
|
|
49
136
|
);
|
|
50
137
|
|
|
138
|
+
let isFirstRow = item.prevKey == null;
|
|
139
|
+
let isLastRow = item.nextKey == null;
|
|
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
|
|
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
|
|
142
|
+
// with bottom border
|
|
143
|
+
let isFlushWithContainerBottom = false;
|
|
144
|
+
if (isLastRow && loadingState !== 'loadingMore') {
|
|
145
|
+
if (layout.getContentSize()?.height >= layout.virtualizer?.getVisibleRect().height) {
|
|
146
|
+
isFlushWithContainerBottom = true;
|
|
147
|
+
}
|
|
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
|
+
}
|
|
160
|
+
|
|
51
161
|
return (
|
|
52
|
-
<div
|
|
162
|
+
<div
|
|
163
|
+
{...mergedProps}
|
|
164
|
+
className={
|
|
165
|
+
classNames(
|
|
166
|
+
listStyles,
|
|
167
|
+
'react-spectrum-ListView-row',
|
|
168
|
+
{
|
|
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)
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
ref={rowRef}>
|
|
53
178
|
<div
|
|
179
|
+
// TODO: refactor the css here now that we are focusing the row?
|
|
54
180
|
className={
|
|
55
181
|
classNames(
|
|
56
182
|
listStyles,
|
|
57
183
|
'react-spectrum-ListViewItem',
|
|
58
184
|
{
|
|
185
|
+
'is-active': isPressed,
|
|
59
186
|
'is-focused': isFocusVisibleWithin,
|
|
60
187
|
'focus-ring': isFocusVisible,
|
|
61
|
-
'is-hovered': isHovered
|
|
188
|
+
'is-hovered': isHovered,
|
|
189
|
+
'is-selected': isSelected,
|
|
190
|
+
'is-disabled': isDisabled,
|
|
191
|
+
'is-prev-selected': state.selectionManager.isSelected(item.prevKey),
|
|
192
|
+
'is-next-selected': state.selectionManager.isSelected(item.nextKey),
|
|
193
|
+
'react-spectrum-ListViewItem--highlightSelection': state.selectionManager.selectionBehavior === 'replace' && (isSelected || state.selectionManager.isSelected(item.nextKey)),
|
|
194
|
+
'react-spectrum-ListViewItem--dropTarget': !!isDropTarget,
|
|
195
|
+
'react-spectrum-ListViewItem--firstRow': isFirstRow,
|
|
196
|
+
'react-spectrum-ListViewItem--lastRow': isLastRow,
|
|
197
|
+
'react-spectrum-ListViewItem--isFlushBottom': isFlushWithContainerBottom,
|
|
198
|
+
'react-spectrum-ListViewItem--hasDescription': hasDescription
|
|
62
199
|
}
|
|
63
200
|
)
|
|
64
201
|
}
|
|
65
|
-
|
|
66
|
-
{
|
|
67
|
-
|
|
202
|
+
{...gridCellProps}>
|
|
203
|
+
<Grid UNSAFE_className={listStyles['react-spectrum-ListViewItem-grid']}>
|
|
204
|
+
{isListDraggable &&
|
|
205
|
+
<div className={listStyles['react-spectrum-ListViewItem-draghandle-container']}>
|
|
206
|
+
{!isDisabled &&
|
|
207
|
+
<FocusRing focusRingClass={classNames(listStyles, 'focus-ring')}>
|
|
208
|
+
<div
|
|
209
|
+
{...buttonProps as React.HTMLAttributes<HTMLElement>}
|
|
210
|
+
className={
|
|
211
|
+
classNames(
|
|
212
|
+
listStyles,
|
|
213
|
+
'react-spectrum-ListViewItem-draghandle-button'
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
style={!isFocusVisibleWithin ? {...visuallyHiddenProps.style} : {}}
|
|
217
|
+
ref={dragButtonRef}
|
|
218
|
+
draggable="true">
|
|
219
|
+
<ListGripper />
|
|
220
|
+
</div>
|
|
221
|
+
</FocusRing>
|
|
222
|
+
}
|
|
223
|
+
</div>
|
|
224
|
+
}
|
|
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>
|
|
242
|
+
<SlotProvider
|
|
243
|
+
slots={{
|
|
244
|
+
text: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
|
|
245
|
+
description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description']},
|
|
246
|
+
icon: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-icon'], size: 'M'},
|
|
247
|
+
image: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-image']},
|
|
248
|
+
actionButton: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'], isQuiet: true},
|
|
249
|
+
actionGroup: {
|
|
250
|
+
UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'],
|
|
251
|
+
isQuiet: true,
|
|
252
|
+
density: 'compact'
|
|
253
|
+
},
|
|
254
|
+
actionMenu: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actionmenu'], isQuiet: true}
|
|
255
|
+
}}>
|
|
256
|
+
{content}
|
|
257
|
+
<ClearSlots>
|
|
258
|
+
{chevron}
|
|
259
|
+
</ClearSlots>
|
|
260
|
+
</SlotProvider>
|
|
261
|
+
</Grid>
|
|
68
262
|
</div>
|
|
69
263
|
</div>
|
|
70
264
|
);
|