@react-spectrum/list 3.0.0-beta.0 → 3.0.0-nightly-4980928d3-240906
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/DragPreview.main.js +79 -0
- package/dist/DragPreview.main.js.map +1 -0
- package/dist/DragPreview.mjs +74 -0
- package/dist/DragPreview.module.js +74 -0
- package/dist/DragPreview.module.js.map +1 -0
- package/dist/InsertionIndicator.main.js +49 -0
- package/dist/InsertionIndicator.main.js.map +1 -0
- package/dist/InsertionIndicator.mjs +44 -0
- package/dist/InsertionIndicator.module.js +44 -0
- package/dist/InsertionIndicator.module.js.map +1 -0
- package/dist/ListView.main.js +299 -0
- package/dist/ListView.main.js.map +1 -0
- package/dist/ListView.mjs +293 -0
- package/dist/ListView.module.js +293 -0
- package/dist/ListView.module.js.map +1 -0
- package/dist/ListViewItem.main.js +252 -0
- package/dist/ListViewItem.main.js.map +1 -0
- package/dist/ListViewItem.mjs +247 -0
- package/dist/ListViewItem.module.js +247 -0
- package/dist/ListViewItem.module.js.map +1 -0
- package/dist/ListViewLayout.main.js +69 -0
- package/dist/ListViewLayout.main.js.map +1 -0
- package/dist/ListViewLayout.mjs +64 -0
- package/dist/ListViewLayout.module.js +64 -0
- package/dist/ListViewLayout.module.js.map +1 -0
- package/dist/RootDropIndicator.main.js +46 -0
- package/dist/RootDropIndicator.main.js.map +1 -0
- package/dist/RootDropIndicator.mjs +37 -0
- package/dist/RootDropIndicator.module.js +37 -0
- package/dist/RootDropIndicator.module.js.map +1 -0
- package/dist/ar-AE.main.js +7 -0
- package/dist/ar-AE.main.js.map +1 -0
- package/dist/ar-AE.mjs +9 -0
- package/dist/ar-AE.module.js +9 -0
- package/dist/ar-AE.module.js.map +1 -0
- package/dist/bg-BG.main.js +7 -0
- package/dist/bg-BG.main.js.map +1 -0
- package/dist/bg-BG.mjs +9 -0
- package/dist/bg-BG.module.js +9 -0
- package/dist/bg-BG.module.js.map +1 -0
- package/dist/cs-CZ.main.js +7 -0
- package/dist/cs-CZ.main.js.map +1 -0
- package/dist/cs-CZ.mjs +9 -0
- package/dist/cs-CZ.module.js +9 -0
- package/dist/cs-CZ.module.js.map +1 -0
- package/dist/da-DK.main.js +7 -0
- package/dist/da-DK.main.js.map +1 -0
- package/dist/da-DK.mjs +9 -0
- package/dist/da-DK.module.js +9 -0
- package/dist/da-DK.module.js.map +1 -0
- package/dist/de-DE.main.js +7 -0
- package/dist/de-DE.main.js.map +1 -0
- package/dist/de-DE.mjs +9 -0
- package/dist/de-DE.module.js +9 -0
- package/dist/de-DE.module.js.map +1 -0
- package/dist/el-GR.main.js +7 -0
- package/dist/el-GR.main.js.map +1 -0
- package/dist/el-GR.mjs +9 -0
- package/dist/el-GR.module.js +9 -0
- package/dist/el-GR.module.js.map +1 -0
- package/dist/en-US.main.js +7 -0
- package/dist/en-US.main.js.map +1 -0
- package/dist/en-US.mjs +9 -0
- package/dist/en-US.module.js +9 -0
- package/dist/en-US.module.js.map +1 -0
- package/dist/es-ES.main.js +7 -0
- package/dist/es-ES.main.js.map +1 -0
- package/dist/es-ES.mjs +9 -0
- package/dist/es-ES.module.js +9 -0
- package/dist/es-ES.module.js.map +1 -0
- package/dist/et-EE.main.js +7 -0
- package/dist/et-EE.main.js.map +1 -0
- package/dist/et-EE.mjs +9 -0
- package/dist/et-EE.module.js +9 -0
- package/dist/et-EE.module.js.map +1 -0
- package/dist/fi-FI.main.js +7 -0
- package/dist/fi-FI.main.js.map +1 -0
- package/dist/fi-FI.mjs +9 -0
- package/dist/fi-FI.module.js +9 -0
- package/dist/fi-FI.module.js.map +1 -0
- package/dist/fr-FR.main.js +7 -0
- package/dist/fr-FR.main.js.map +1 -0
- package/dist/fr-FR.mjs +9 -0
- package/dist/fr-FR.module.js +9 -0
- package/dist/fr-FR.module.js.map +1 -0
- package/dist/he-IL.main.js +7 -0
- package/dist/he-IL.main.js.map +1 -0
- package/dist/he-IL.mjs +9 -0
- package/dist/he-IL.module.js +9 -0
- package/dist/he-IL.module.js.map +1 -0
- package/dist/hr-HR.main.js +7 -0
- package/dist/hr-HR.main.js.map +1 -0
- package/dist/hr-HR.mjs +9 -0
- package/dist/hr-HR.module.js +9 -0
- package/dist/hr-HR.module.js.map +1 -0
- package/dist/hu-HU.main.js +7 -0
- package/dist/hu-HU.main.js.map +1 -0
- package/dist/hu-HU.mjs +9 -0
- package/dist/hu-HU.module.js +9 -0
- package/dist/hu-HU.module.js.map +1 -0
- package/dist/import.mjs +19 -0
- package/dist/intlStrings.main.js +108 -0
- package/dist/intlStrings.main.js.map +1 -0
- package/dist/intlStrings.mjs +110 -0
- package/dist/intlStrings.module.js +110 -0
- package/dist/intlStrings.module.js.map +1 -0
- package/dist/it-IT.main.js +7 -0
- package/dist/it-IT.main.js.map +1 -0
- package/dist/it-IT.mjs +9 -0
- package/dist/it-IT.module.js +9 -0
- package/dist/it-IT.module.js.map +1 -0
- package/dist/ja-JP.main.js +7 -0
- package/dist/ja-JP.main.js.map +1 -0
- package/dist/ja-JP.mjs +9 -0
- package/dist/ja-JP.module.js +9 -0
- package/dist/ja-JP.module.js.map +1 -0
- package/dist/ko-KR.main.js +7 -0
- package/dist/ko-KR.main.js.map +1 -0
- package/dist/ko-KR.mjs +9 -0
- package/dist/ko-KR.module.js +9 -0
- package/dist/ko-KR.module.js.map +1 -0
- package/dist/lt-LT.main.js +7 -0
- package/dist/lt-LT.main.js.map +1 -0
- package/dist/lt-LT.mjs +9 -0
- package/dist/lt-LT.module.js +9 -0
- package/dist/lt-LT.module.js.map +1 -0
- package/dist/lv-LV.main.js +7 -0
- package/dist/lv-LV.main.js.map +1 -0
- package/dist/lv-LV.mjs +9 -0
- package/dist/lv-LV.module.js +9 -0
- package/dist/lv-LV.module.js.map +1 -0
- package/dist/main.js +14 -962
- package/dist/main.js.map +1 -1
- package/dist/module.js +12 -964
- package/dist/module.js.map +1 -1
- package/dist/nb-NO.main.js +7 -0
- package/dist/nb-NO.main.js.map +1 -0
- package/dist/nb-NO.mjs +9 -0
- package/dist/nb-NO.module.js +9 -0
- package/dist/nb-NO.module.js.map +1 -0
- package/dist/nl-NL.main.js +7 -0
- package/dist/nl-NL.main.js.map +1 -0
- package/dist/nl-NL.mjs +9 -0
- package/dist/nl-NL.module.js +9 -0
- package/dist/nl-NL.module.js.map +1 -0
- package/dist/pl-PL.main.js +7 -0
- package/dist/pl-PL.main.js.map +1 -0
- package/dist/pl-PL.mjs +9 -0
- package/dist/pl-PL.module.js +9 -0
- package/dist/pl-PL.module.js.map +1 -0
- package/dist/pt-BR.main.js +7 -0
- package/dist/pt-BR.main.js.map +1 -0
- package/dist/pt-BR.mjs +9 -0
- package/dist/pt-BR.module.js +9 -0
- package/dist/pt-BR.module.js.map +1 -0
- package/dist/pt-PT.main.js +7 -0
- package/dist/pt-PT.main.js.map +1 -0
- package/dist/pt-PT.mjs +9 -0
- package/dist/pt-PT.module.js +9 -0
- package/dist/pt-PT.module.js.map +1 -0
- package/dist/ro-RO.main.js +7 -0
- package/dist/ro-RO.main.js.map +1 -0
- package/dist/ro-RO.mjs +9 -0
- package/dist/ro-RO.module.js +9 -0
- package/dist/ro-RO.module.js.map +1 -0
- package/dist/ru-RU.main.js +7 -0
- package/dist/ru-RU.main.js.map +1 -0
- package/dist/ru-RU.mjs +9 -0
- package/dist/ru-RU.module.js +9 -0
- package/dist/ru-RU.module.js.map +1 -0
- package/dist/sk-SK.main.js +7 -0
- package/dist/sk-SK.main.js.map +1 -0
- package/dist/sk-SK.mjs +9 -0
- package/dist/sk-SK.module.js +9 -0
- package/dist/sk-SK.module.js.map +1 -0
- package/dist/sl-SI.main.js +7 -0
- package/dist/sl-SI.main.js.map +1 -0
- package/dist/sl-SI.mjs +9 -0
- package/dist/sl-SI.module.js +9 -0
- package/dist/sl-SI.module.js.map +1 -0
- package/dist/sr-SP.main.js +7 -0
- package/dist/sr-SP.main.js.map +1 -0
- package/dist/sr-SP.mjs +9 -0
- package/dist/sr-SP.module.js +9 -0
- package/dist/sr-SP.module.js.map +1 -0
- package/dist/styles.d09a6cc6.css +839 -0
- package/dist/styles.d09a6cc6.css.map +1 -0
- package/dist/styles_css.main.js +197 -0
- package/dist/styles_css.main.js.map +1 -0
- package/dist/styles_css.mjs +199 -0
- package/dist/styles_css.module.js +199 -0
- package/dist/styles_css.module.js.map +1 -0
- package/dist/sv-SE.main.js +7 -0
- package/dist/sv-SE.main.js.map +1 -0
- package/dist/sv-SE.mjs +9 -0
- package/dist/sv-SE.module.js +9 -0
- package/dist/sv-SE.module.js.map +1 -0
- package/dist/tr-TR.main.js +7 -0
- package/dist/tr-TR.main.js.map +1 -0
- package/dist/tr-TR.mjs +9 -0
- package/dist/tr-TR.module.js +9 -0
- package/dist/tr-TR.module.js.map +1 -0
- package/dist/types.d.ts +33 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/uk-UA.main.js +7 -0
- package/dist/uk-UA.main.js.map +1 -0
- package/dist/uk-UA.mjs +9 -0
- package/dist/uk-UA.module.js +9 -0
- package/dist/uk-UA.module.js.map +1 -0
- package/dist/zh-CN.main.js +7 -0
- package/dist/zh-CN.main.js.map +1 -0
- package/dist/zh-CN.mjs +9 -0
- package/dist/zh-CN.module.js +9 -0
- package/dist/zh-CN.module.js.map +1 -0
- package/dist/zh-TW.main.js +7 -0
- package/dist/zh-TW.main.js.map +1 -0
- package/dist/zh-TW.mjs +9 -0
- package/dist/zh-TW.module.js +9 -0
- package/dist/zh-TW.module.js.map +1 -0
- package/package.json +39 -43
- package/src/DragPreview.tsx +20 -8
- package/src/InsertionIndicator.tsx +3 -3
- package/src/ListView.tsx +187 -178
- package/src/ListViewItem.tsx +30 -17
- package/src/ListViewLayout.ts +66 -0
- package/src/RootDropIndicator.tsx +6 -7
- package/src/index.ts +13 -0
- package/src/styles.css +195 -68
- package/LICENSE +0 -201
- package/dist/main.css +0 -1
package/src/ListView.tsx
CHANGED
|
@@ -9,42 +9,70 @@
|
|
|
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
|
+
|
|
13
|
+
import {AriaGridListProps, useGridList} from '@react-aria/gridlist';
|
|
14
|
+
import {AsyncLoadable, DOMRef, Key, LoadingState, Node, SpectrumSelectionProps, StyleProps} from '@react-types/shared';
|
|
12
15
|
import {classNames, useDOMRef, useStyleProps} from '@react-spectrum/utils';
|
|
13
|
-
import {
|
|
16
|
+
import type {DragAndDropHooks} from '@react-spectrum/dnd';
|
|
14
17
|
import type {DraggableCollectionState, DroppableCollectionState} from '@react-stately/dnd';
|
|
15
|
-
import {DragHooks, DropHooks} from '@react-spectrum/dnd';
|
|
16
18
|
import type {DroppableCollectionResult} from '@react-aria/dnd';
|
|
17
|
-
import {filterDOMProps, useLayoutEffect} from '@react-aria/utils';
|
|
19
|
+
import {filterDOMProps, mergeProps, useLayoutEffect} from '@react-aria/utils';
|
|
20
|
+
import {FocusRing, FocusScope} from '@react-aria/focus';
|
|
18
21
|
import InsertionIndicator from './InsertionIndicator';
|
|
19
22
|
// @ts-ignore
|
|
20
23
|
import intlMessages from '../intl/*.json';
|
|
21
|
-
import {
|
|
24
|
+
import {ListKeyboardDelegate} from '@react-aria/selection';
|
|
22
25
|
import {ListState, useListState} from '@react-stately/list';
|
|
23
26
|
import listStyles from './styles.css';
|
|
24
27
|
import {ListViewItem} from './ListViewItem';
|
|
25
|
-
import {
|
|
28
|
+
import {ListViewLayout} from './ListViewLayout';
|
|
26
29
|
import {ProgressCircle} from '@react-spectrum/progress';
|
|
27
|
-
import React, {
|
|
28
|
-
import {Rect} from '@react-stately/virtualizer';
|
|
30
|
+
import React, {JSX, ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
|
|
29
31
|
import RootDropIndicator from './RootDropIndicator';
|
|
30
32
|
import {DragPreview as SpectrumDragPreview} from './DragPreview';
|
|
31
|
-
import {
|
|
32
|
-
import {useCollator, useMessageFormatter} from '@react-aria/i18n';
|
|
33
|
-
import {useList} from '@react-aria/list';
|
|
33
|
+
import {useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
34
34
|
import {useProvider} from '@react-spectrum/provider';
|
|
35
35
|
import {Virtualizer} from '@react-aria/virtualizer';
|
|
36
36
|
|
|
37
|
+
export interface SpectrumListViewProps<T> extends Omit<AriaGridListProps<T>, 'keyboardNavigationBehavior'>, StyleProps, SpectrumSelectionProps, Omit<AsyncLoadable, 'isLoading'> {
|
|
38
|
+
/**
|
|
39
|
+
* Sets the amount of vertical padding within each cell.
|
|
40
|
+
* @default 'regular'
|
|
41
|
+
*/
|
|
42
|
+
density?: 'compact' | 'regular' | 'spacious',
|
|
43
|
+
/** Whether the ListView should be displayed with a quiet style. */
|
|
44
|
+
isQuiet?: boolean,
|
|
45
|
+
/** The current loading state of the ListView. Determines whether or not the progress circle should be shown. */
|
|
46
|
+
loadingState?: LoadingState,
|
|
47
|
+
/**
|
|
48
|
+
* Sets the text behavior for the row contents.
|
|
49
|
+
* @default 'truncate'
|
|
50
|
+
*/
|
|
51
|
+
overflowMode?: 'truncate' | 'wrap',
|
|
52
|
+
/** Sets what the ListView should render when there is no content to display. */
|
|
53
|
+
renderEmptyState?: () => JSX.Element,
|
|
54
|
+
/**
|
|
55
|
+
* Handler that is called when a user performs an action on an item. The exact user event depends on
|
|
56
|
+
* the collection's `selectionStyle` prop and the interaction modality.
|
|
57
|
+
*/
|
|
58
|
+
onAction?: (key: Key) => void,
|
|
59
|
+
/**
|
|
60
|
+
* The drag and drop hooks returned by `useDragAndDrop` used to enable drag and drop behavior for the ListView.
|
|
61
|
+
*/
|
|
62
|
+
dragAndDropHooks?: DragAndDropHooks['dragAndDropHooks']
|
|
63
|
+
}
|
|
64
|
+
|
|
37
65
|
interface ListViewContextValue<T> {
|
|
38
66
|
state: ListState<T>,
|
|
39
67
|
dragState: DraggableCollectionState,
|
|
40
68
|
dropState: DroppableCollectionState,
|
|
41
|
-
|
|
42
|
-
dropHooks: DropHooks,
|
|
69
|
+
dragAndDropHooks: DragAndDropHooks['dragAndDropHooks'],
|
|
43
70
|
onAction:(key: Key) => void,
|
|
44
71
|
isListDraggable: boolean,
|
|
45
72
|
isListDroppable: boolean,
|
|
46
|
-
layout:
|
|
47
|
-
loadingState: LoadingState
|
|
73
|
+
layout: ListViewLayout<T>,
|
|
74
|
+
loadingState: LoadingState,
|
|
75
|
+
renderEmptyState?: () => JSX.Element
|
|
48
76
|
}
|
|
49
77
|
|
|
50
78
|
export const ListViewContext = React.createContext<ListViewContextValue<unknown>>(null);
|
|
@@ -64,156 +92,115 @@ const ROW_HEIGHTS = {
|
|
|
64
92
|
}
|
|
65
93
|
};
|
|
66
94
|
|
|
67
|
-
function useListLayout<T>(state: ListState<T>, density:
|
|
95
|
+
function useListLayout<T>(state: ListState<T>, density: SpectrumListViewProps<T>['density'], overflowMode: SpectrumListViewProps<T>['overflowMode']) {
|
|
68
96
|
let {scale} = useProvider();
|
|
69
|
-
let collator = useCollator({usage: 'search', sensitivity: 'base'});
|
|
70
|
-
let isEmpty = state.collection.size === 0;
|
|
71
97
|
let layout = useMemo(() =>
|
|
72
|
-
new
|
|
73
|
-
estimatedRowHeight: ROW_HEIGHTS[density][scale]
|
|
74
|
-
padding: 0,
|
|
75
|
-
collator,
|
|
76
|
-
loaderHeight: isEmpty ? null : ROW_HEIGHTS[density][scale]
|
|
98
|
+
new ListViewLayout<T>({
|
|
99
|
+
estimatedRowHeight: ROW_HEIGHTS[density][scale]
|
|
77
100
|
})
|
|
78
101
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
79
|
-
, [
|
|
102
|
+
, [scale, density, overflowMode]);
|
|
80
103
|
|
|
81
|
-
layout.collection = state.collection;
|
|
82
|
-
layout.disabledKeys = state.disabledKeys;
|
|
83
104
|
return layout;
|
|
84
105
|
}
|
|
85
106
|
|
|
86
|
-
function ListView<T extends object>(props:
|
|
107
|
+
function ListView<T extends object>(props: SpectrumListViewProps<T>, ref: DOMRef<HTMLDivElement>) {
|
|
87
108
|
let {
|
|
88
109
|
density = 'regular',
|
|
89
|
-
onLoadMore,
|
|
90
110
|
loadingState,
|
|
111
|
+
onLoadMore,
|
|
91
112
|
isQuiet,
|
|
92
113
|
overflowMode = 'truncate',
|
|
93
114
|
onAction,
|
|
94
|
-
|
|
95
|
-
|
|
115
|
+
dragAndDropHooks,
|
|
116
|
+
renderEmptyState,
|
|
96
117
|
...otherProps
|
|
97
118
|
} = props;
|
|
98
|
-
let isListDraggable = !!
|
|
99
|
-
let isListDroppable = !!
|
|
119
|
+
let isListDraggable = !!dragAndDropHooks?.useDraggableCollectionState;
|
|
120
|
+
let isListDroppable = !!dragAndDropHooks?.useDroppableCollectionState;
|
|
100
121
|
let dragHooksProvided = useRef(isListDraggable);
|
|
101
122
|
let dropHooksProvided = useRef(isListDroppable);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (dragHooksProvided.current !== isListDraggable) {
|
|
125
|
+
console.warn('Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
|
|
126
|
+
}
|
|
127
|
+
if (dropHooksProvided.current !== isListDroppable) {
|
|
128
|
+
console.warn('Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
|
|
129
|
+
}
|
|
130
|
+
}, [isListDraggable, isListDroppable]);
|
|
131
|
+
|
|
108
132
|
let domRef = useDOMRef(ref);
|
|
109
133
|
let state = useListState({
|
|
110
134
|
...props,
|
|
111
135
|
selectionBehavior: props.selectionStyle === 'highlight' ? 'replace' : 'toggle'
|
|
112
136
|
});
|
|
113
137
|
let {collection, selectionManager} = state;
|
|
114
|
-
let formatMessage = useMessageFormatter(intlMessages);
|
|
115
138
|
let isLoading = loadingState === 'loading' || loadingState === 'loadingMore';
|
|
116
139
|
|
|
117
140
|
let {styleProps} = useStyleProps(props);
|
|
118
141
|
let dragState: DraggableCollectionState;
|
|
119
142
|
let preview = useRef(null);
|
|
120
143
|
if (isListDraggable) {
|
|
121
|
-
dragState =
|
|
144
|
+
dragState = dragAndDropHooks.useDraggableCollectionState({
|
|
122
145
|
collection,
|
|
123
146
|
selectionManager,
|
|
124
147
|
preview
|
|
125
148
|
});
|
|
149
|
+
dragAndDropHooks.useDraggableCollection({}, dragState, domRef);
|
|
126
150
|
}
|
|
127
151
|
let layout = useListLayout(
|
|
128
152
|
state,
|
|
129
153
|
props.density || 'regular',
|
|
130
154
|
overflowMode
|
|
131
155
|
);
|
|
132
|
-
// !!0 is false, so we can cast size or undefined and they'll be falsy
|
|
133
|
-
layout.allowDisabledKeyFocus = state.selectionManager.disabledBehavior === 'selection' || !!dragState?.draggingKeys.size;
|
|
134
|
-
|
|
135
156
|
|
|
136
|
-
let DragPreview =
|
|
157
|
+
let DragPreview = dragAndDropHooks?.DragPreview;
|
|
137
158
|
let dropState: DroppableCollectionState;
|
|
138
159
|
let droppableCollection: DroppableCollectionResult;
|
|
139
160
|
let isRootDropTarget: boolean;
|
|
140
161
|
if (isListDroppable) {
|
|
141
|
-
dropState =
|
|
162
|
+
dropState = dragAndDropHooks.useDroppableCollectionState({
|
|
142
163
|
collection,
|
|
143
164
|
selectionManager
|
|
144
165
|
});
|
|
145
|
-
droppableCollection =
|
|
146
|
-
keyboardDelegate:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
y += domRef.current.scrollTop;
|
|
154
|
-
|
|
155
|
-
let visible = layout.getVisibleLayoutInfos(new Rect(x - 50, y - 50, x + 50, y + 50));
|
|
156
|
-
|
|
157
|
-
for (let layoutInfo of visible) {
|
|
158
|
-
let r = layoutInfo.rect;
|
|
159
|
-
let points: [number, number, string][] = [
|
|
160
|
-
[r.x, r.y + 4, 'before'],
|
|
161
|
-
[r.maxX, r.y + 4, 'before'],
|
|
162
|
-
[r.x, r.maxY - 8, 'after'],
|
|
163
|
-
[r.maxX, r.maxY - 8, 'after']
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
for (let [px, py, dir] of points) {
|
|
167
|
-
let dx = px - x;
|
|
168
|
-
let dy = py - y;
|
|
169
|
-
let d = dx * dx + dy * dy;
|
|
170
|
-
if (d < closestDistance) {
|
|
171
|
-
closestDistance = d;
|
|
172
|
-
closest = layoutInfo;
|
|
173
|
-
closestDir = dir;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// TODO: Best way to implement only for when closest can be dropped on
|
|
178
|
-
// TODO: Figure out the typescript for this
|
|
179
|
-
// @ts-ignore
|
|
180
|
-
if (y >= r.y + 10 && y <= r.maxY - 10 && collection.getItem(closest.key).value.type === 'folder') {
|
|
181
|
-
closestDir = 'on';
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
let key = closest?.key;
|
|
186
|
-
if (key) {
|
|
187
|
-
return {
|
|
188
|
-
type: 'item',
|
|
189
|
-
key,
|
|
190
|
-
dropPosition: closestDir
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
}
|
|
166
|
+
droppableCollection = dragAndDropHooks.useDroppableCollection({
|
|
167
|
+
keyboardDelegate: new ListKeyboardDelegate({
|
|
168
|
+
collection,
|
|
169
|
+
disabledKeys: dragState?.draggingKeys.size ? null : selectionManager.disabledKeys,
|
|
170
|
+
ref: domRef,
|
|
171
|
+
layoutDelegate: layout
|
|
172
|
+
}),
|
|
173
|
+
dropTargetDelegate: layout
|
|
194
174
|
}, dropState, domRef);
|
|
195
175
|
|
|
196
176
|
isRootDropTarget = dropState.isDropTarget({type: 'root'});
|
|
197
177
|
}
|
|
198
178
|
|
|
199
|
-
let {gridProps} =
|
|
179
|
+
let {gridProps} = useGridList({
|
|
200
180
|
...props,
|
|
201
181
|
isVirtualized: true,
|
|
202
|
-
|
|
182
|
+
layoutDelegate: layout,
|
|
203
183
|
onAction
|
|
204
184
|
}, state, domRef);
|
|
205
185
|
|
|
206
|
-
// Sync loading state into the layout.
|
|
207
|
-
layout.isLoading = isLoading;
|
|
208
|
-
|
|
209
186
|
let focusedKey = selectionManager.focusedKey;
|
|
187
|
+
let dropTargetKey: Key | null = null;
|
|
210
188
|
if (dropState?.target?.type === 'item') {
|
|
211
|
-
|
|
189
|
+
dropTargetKey = dropState.target.key;
|
|
190
|
+
if (dropState.target.dropPosition === 'after') {
|
|
191
|
+
// Normalize to the "before" drop position since we only render those in the DOM.
|
|
192
|
+
dropTargetKey = state.collection.getKeyAfter(dropTargetKey) ?? dropTargetKey;
|
|
193
|
+
}
|
|
212
194
|
}
|
|
213
195
|
|
|
196
|
+
let persistedKeys = useMemo(() => {
|
|
197
|
+
return new Set([focusedKey, dropTargetKey].filter(k => k !== null));
|
|
198
|
+
}, [focusedKey, dropTargetKey]);
|
|
199
|
+
|
|
214
200
|
// wait for layout to get accurate measurements
|
|
215
201
|
let [isVerticalScrollbarVisible, setVerticalScollbarVisible] = useState(false);
|
|
216
202
|
let [isHorizontalScrollbarVisible, setHorizontalScollbarVisible] = useState(false);
|
|
203
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
217
204
|
useLayoutEffect(() => {
|
|
218
205
|
if (domRef.current) {
|
|
219
206
|
// 2 is the width of the border which is not part of the box size
|
|
@@ -225,90 +212,63 @@ function ListView<T extends object>(props: SpectrumListProps<T>, ref: DOMRef<HTM
|
|
|
225
212
|
let hasAnyChildren = useMemo(() => [...collection].some(item => item.hasChildNodes), [collection]);
|
|
226
213
|
|
|
227
214
|
return (
|
|
228
|
-
<ListViewContext.Provider value={{state, dragState, dropState,
|
|
229
|
-
<
|
|
230
|
-
{
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
collection={collection}
|
|
260
|
-
transitionDuration={isLoading ? 160 : 220}>
|
|
261
|
-
{(type, item) => {
|
|
262
|
-
if (type === 'item') {
|
|
263
|
-
return (
|
|
264
|
-
<>
|
|
265
|
-
{isListDroppable && collection.getKeyBefore(item.key) == null &&
|
|
266
|
-
<RootDropIndicator key="root" />
|
|
267
|
-
}
|
|
268
|
-
{isListDroppable &&
|
|
269
|
-
<InsertionIndicator
|
|
270
|
-
key={`${item.key}-before`}
|
|
271
|
-
target={{key: item.key, type: 'item', dropPosition: 'before'}} />
|
|
272
|
-
}
|
|
273
|
-
<ListViewItem item={item} isEmphasized hasActions={!!onAction} />
|
|
274
|
-
{isListDroppable &&
|
|
275
|
-
<InsertionIndicator
|
|
276
|
-
key={`${item.key}-after`}
|
|
277
|
-
target={{key: item.key, type: 'item', dropPosition: 'after'}}
|
|
278
|
-
isPresentationOnly={collection.getKeyAfter(item.key) != null} />
|
|
279
|
-
}
|
|
280
|
-
</>
|
|
281
|
-
);
|
|
282
|
-
} else if (type === 'loader') {
|
|
283
|
-
return (
|
|
284
|
-
<CenteredWrapper>
|
|
285
|
-
<ProgressCircle
|
|
286
|
-
isIndeterminate
|
|
287
|
-
aria-label={collection.size > 0 ? formatMessage('loadingMore') : formatMessage('loading')} />
|
|
288
|
-
</CenteredWrapper>
|
|
289
|
-
);
|
|
290
|
-
} else if (type === 'placeholder') {
|
|
291
|
-
let emptyState = props.renderEmptyState ? props.renderEmptyState() : null;
|
|
292
|
-
if (emptyState == null) {
|
|
293
|
-
return null;
|
|
215
|
+
<ListViewContext.Provider value={{state, dragState, dropState, dragAndDropHooks, onAction, isListDraggable, isListDroppable, layout, loadingState, renderEmptyState}}>
|
|
216
|
+
<FocusScope>
|
|
217
|
+
<FocusRing focusRingClass={classNames(listStyles, 'focus-ring')}>
|
|
218
|
+
<Virtualizer
|
|
219
|
+
{...mergeProps(isListDroppable && droppableCollection?.collectionProps, gridProps)}
|
|
220
|
+
{...filterDOMProps(otherProps)}
|
|
221
|
+
{...gridProps}
|
|
222
|
+
{...styleProps}
|
|
223
|
+
isLoading={isLoading}
|
|
224
|
+
onLoadMore={onLoadMore}
|
|
225
|
+
ref={domRef}
|
|
226
|
+
persistedKeys={persistedKeys}
|
|
227
|
+
scrollDirection="vertical"
|
|
228
|
+
className={
|
|
229
|
+
classNames(
|
|
230
|
+
listStyles,
|
|
231
|
+
'react-spectrum-ListView',
|
|
232
|
+
`react-spectrum-ListView--${density}`,
|
|
233
|
+
'react-spectrum-ListView--emphasized',
|
|
234
|
+
{
|
|
235
|
+
'react-spectrum-ListView--quiet': isQuiet,
|
|
236
|
+
'react-spectrum-ListView--loadingMore': loadingState === 'loadingMore',
|
|
237
|
+
'react-spectrum-ListView--draggable': !!isListDraggable,
|
|
238
|
+
'react-spectrum-ListView--dropTarget': !!isRootDropTarget,
|
|
239
|
+
'react-spectrum-ListView--isVerticalScrollbarVisible': isVerticalScrollbarVisible,
|
|
240
|
+
'react-spectrum-ListView--isHorizontalScrollbarVisible': isHorizontalScrollbarVisible,
|
|
241
|
+
'react-spectrum-ListView--hasAnyChildren': hasAnyChildren,
|
|
242
|
+
'react-spectrum-ListView--wrap': overflowMode === 'wrap'
|
|
243
|
+
},
|
|
244
|
+
styleProps.className
|
|
245
|
+
)
|
|
294
246
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
247
|
+
layout={layout}
|
|
248
|
+
layoutOptions={useMemo(() => ({isLoading}), [isLoading])}
|
|
249
|
+
collection={collection}>
|
|
250
|
+
{useCallback((type, item) => {
|
|
251
|
+
if (type === 'item') {
|
|
252
|
+
return <Item item={item} />;
|
|
253
|
+
} else if (type === 'loader') {
|
|
254
|
+
return <LoadingView />;
|
|
255
|
+
} else if (type === 'placeholder') {
|
|
256
|
+
return <EmptyState />;
|
|
257
|
+
}
|
|
258
|
+
}, [])}
|
|
259
|
+
</Virtualizer>
|
|
260
|
+
</FocusRing>
|
|
261
|
+
</FocusScope>
|
|
305
262
|
{DragPreview && isListDraggable &&
|
|
306
263
|
<DragPreview ref={preview}>
|
|
307
264
|
{() => {
|
|
265
|
+
if (dragAndDropHooks.renderPreview) {
|
|
266
|
+
return dragAndDropHooks.renderPreview(dragState.draggingKeys, dragState.draggedKey);
|
|
267
|
+
}
|
|
308
268
|
let item = state.collection.getItem(dragState.draggedKey);
|
|
309
269
|
let itemCount = dragState.draggingKeys.size;
|
|
310
270
|
let itemHeight = layout.getLayoutInfo(dragState.draggedKey).rect.height;
|
|
311
|
-
return <SpectrumDragPreview item={item} itemCount={itemCount} itemHeight={itemHeight} />;
|
|
271
|
+
return <SpectrumDragPreview item={item} itemCount={itemCount} itemHeight={itemHeight} density={density} />;
|
|
312
272
|
}}
|
|
313
273
|
</DragPreview>
|
|
314
274
|
}
|
|
@@ -316,6 +276,55 @@ function ListView<T extends object>(props: SpectrumListProps<T>, ref: DOMRef<HTM
|
|
|
316
276
|
);
|
|
317
277
|
}
|
|
318
278
|
|
|
279
|
+
function Item({item}: {item: Node<unknown>}) {
|
|
280
|
+
let {isListDroppable, state, onAction} = useContext(ListViewContext);
|
|
281
|
+
return (
|
|
282
|
+
<>
|
|
283
|
+
{isListDroppable && state.collection.getKeyBefore(item.key) == null &&
|
|
284
|
+
<RootDropIndicator key="root" />
|
|
285
|
+
}
|
|
286
|
+
{isListDroppable &&
|
|
287
|
+
<InsertionIndicator
|
|
288
|
+
key={`${item.key}-before`}
|
|
289
|
+
target={{key: item.key, type: 'item', dropPosition: 'before'}} />
|
|
290
|
+
}
|
|
291
|
+
<ListViewItem item={item} isEmphasized hasActions={!!onAction} />
|
|
292
|
+
{isListDroppable &&
|
|
293
|
+
<InsertionIndicator
|
|
294
|
+
key={`${item.key}-after`}
|
|
295
|
+
target={{key: item.key, type: 'item', dropPosition: 'after'}}
|
|
296
|
+
isPresentationOnly={state.collection.getKeyAfter(item.key) != null} />
|
|
297
|
+
}
|
|
298
|
+
</>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function LoadingView() {
|
|
303
|
+
let {state} = useContext(ListViewContext);
|
|
304
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/list');
|
|
305
|
+
return (
|
|
306
|
+
<CenteredWrapper>
|
|
307
|
+
<ProgressCircle
|
|
308
|
+
isIndeterminate
|
|
309
|
+
aria-label={state.collection.size > 0 ? stringFormatter.format('loadingMore') : stringFormatter.format('loading')} />
|
|
310
|
+
</CenteredWrapper>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function EmptyState() {
|
|
315
|
+
let {renderEmptyState} = useContext(ListViewContext);
|
|
316
|
+
let emptyState = renderEmptyState ? renderEmptyState() : null;
|
|
317
|
+
if (emptyState == null) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<CenteredWrapper>
|
|
323
|
+
{emptyState}
|
|
324
|
+
</CenteredWrapper>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
319
328
|
function CenteredWrapper({children}) {
|
|
320
329
|
let {state} = useContext(ListViewContext);
|
|
321
330
|
return (
|
|
@@ -338,7 +347,7 @@ function CenteredWrapper({children}) {
|
|
|
338
347
|
}
|
|
339
348
|
|
|
340
349
|
/**
|
|
341
|
-
*
|
|
350
|
+
* A ListView displays a list of interactive items, and allows a user to navigate, select, or perform an action.
|
|
342
351
|
*/
|
|
343
|
-
const _ListView = React.forwardRef(ListView) as <T>(props:
|
|
352
|
+
const _ListView = React.forwardRef(ListView) as <T>(props: SpectrumListViewProps<T> & {ref?: DOMRef<HTMLDivElement>}) => ReactElement;
|
|
344
353
|
export {_ListView as ListView};
|
package/src/ListViewItem.tsx
CHANGED
|
@@ -27,7 +27,7 @@ import {Provider} from '@react-spectrum/provider';
|
|
|
27
27
|
import React, {useContext, useRef} from 'react';
|
|
28
28
|
import {Text} from '@react-spectrum/text';
|
|
29
29
|
import {useButton} from '@react-aria/button';
|
|
30
|
-
import {
|
|
30
|
+
import {useGridListItem, useGridListSelectionCheckbox} from '@react-aria/gridlist';
|
|
31
31
|
import {useLocale} from '@react-aria/i18n';
|
|
32
32
|
import {useVisuallyHidden} from '@react-aria/visually-hidden';
|
|
33
33
|
|
|
@@ -42,9 +42,19 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
|
42
42
|
item,
|
|
43
43
|
isEmphasized
|
|
44
44
|
} = props;
|
|
45
|
-
let {
|
|
45
|
+
let {
|
|
46
|
+
state,
|
|
47
|
+
dragState,
|
|
48
|
+
dropState,
|
|
49
|
+
isListDraggable,
|
|
50
|
+
isListDroppable,
|
|
51
|
+
layout,
|
|
52
|
+
dragAndDropHooks,
|
|
53
|
+
loadingState
|
|
54
|
+
} = useContext(ListViewContext);
|
|
46
55
|
let {direction} = useLocale();
|
|
47
|
-
let rowRef = useRef<HTMLDivElement>();
|
|
56
|
+
let rowRef = useRef<HTMLDivElement>(undefined);
|
|
57
|
+
let checkboxWrapperRef = useRef<HTMLDivElement>(undefined);
|
|
48
58
|
let {
|
|
49
59
|
isFocusVisible: isFocusVisibleWithin,
|
|
50
60
|
focusProps: focusWithinProps
|
|
@@ -59,7 +69,7 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
|
59
69
|
isDisabled,
|
|
60
70
|
allowsSelection,
|
|
61
71
|
hasAction
|
|
62
|
-
} =
|
|
72
|
+
} = useGridListItem({
|
|
63
73
|
node: item,
|
|
64
74
|
isVirtualized: true,
|
|
65
75
|
shouldSelectOnPressUp: isListDraggable
|
|
@@ -67,13 +77,13 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
|
67
77
|
let isDroppable = isListDroppable && !isDisabled;
|
|
68
78
|
let {hoverProps, isHovered} = useHover({isDisabled: !allowsSelection && !hasAction});
|
|
69
79
|
|
|
70
|
-
let {checkboxProps} =
|
|
80
|
+
let {checkboxProps} = useGridListSelectionCheckbox({key: item.key}, state);
|
|
71
81
|
let hasDescription = useHasChild(`.${listStyles['react-spectrum-ListViewItem-description']}`, rowRef);
|
|
72
82
|
|
|
73
83
|
let draggableItem: DraggableItemResult;
|
|
74
84
|
if (isListDraggable) {
|
|
75
85
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
76
|
-
draggableItem =
|
|
86
|
+
draggableItem = dragAndDropHooks.useDraggableItem({key: item.key, hasDragButton: true}, dragState);
|
|
77
87
|
if (isDisabled) {
|
|
78
88
|
draggableItem = null;
|
|
79
89
|
}
|
|
@@ -81,16 +91,15 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
|
81
91
|
let droppableItem: DroppableItemResult;
|
|
82
92
|
let isDropTarget: boolean;
|
|
83
93
|
let dropIndicator: DropIndicatorAria;
|
|
84
|
-
let dropIndicatorRef = useRef();
|
|
94
|
+
let dropIndicatorRef = useRef(undefined);
|
|
85
95
|
if (isListDroppable) {
|
|
86
96
|
let target = {type: 'item', key: item.key, dropPosition: 'on'} as DropTarget;
|
|
87
97
|
isDropTarget = dropState.isDropTarget(target);
|
|
88
98
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
89
|
-
|
|
90
|
-
dropIndicator = dropHooks.useDropIndicator({target}, dropState, dropIndicatorRef);
|
|
99
|
+
dropIndicator = dragAndDropHooks.useDropIndicator({target}, dropState, dropIndicatorRef);
|
|
91
100
|
}
|
|
92
101
|
|
|
93
|
-
let dragButtonRef = React.useRef();
|
|
102
|
+
let dragButtonRef = React.useRef(undefined);
|
|
94
103
|
let {buttonProps} = useButton({
|
|
95
104
|
...draggableItem?.dragButtonProps,
|
|
96
105
|
elementType: 'div'
|
|
@@ -136,7 +145,10 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
|
136
145
|
dropProps,
|
|
137
146
|
hoverProps,
|
|
138
147
|
focusWithinProps,
|
|
139
|
-
focusProps
|
|
148
|
+
focusProps,
|
|
149
|
+
// Remove tab index from list row if performing a screenreader drag. This prevents TalkBack from focusing the row,
|
|
150
|
+
// allowing for single swipe navigation between row drop indicator
|
|
151
|
+
dragAndDropHooks?.isVirtualDragging() && {tabIndex: null}
|
|
140
152
|
);
|
|
141
153
|
|
|
142
154
|
let isFirstRow = item.prevKey == null;
|
|
@@ -146,7 +158,7 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
|
146
158
|
// with bottom border
|
|
147
159
|
let isFlushWithContainerBottom = false;
|
|
148
160
|
if (isLastRow && loadingState !== 'loadingMore') {
|
|
149
|
-
if (layout.getContentSize()?.height >= layout.virtualizer?.
|
|
161
|
+
if (layout.getContentSize()?.height >= layout.virtualizer?.visibleRect.height) {
|
|
150
162
|
isFlushWithContainerBottom = true;
|
|
151
163
|
}
|
|
152
164
|
}
|
|
@@ -226,7 +238,7 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
|
226
238
|
}
|
|
227
239
|
</div>
|
|
228
240
|
}
|
|
229
|
-
{
|
|
241
|
+
{isListDroppable && !dropIndicator?.isHidden &&
|
|
230
242
|
<div role="button" {...visuallyHiddenProps} {...dropIndicator?.dropIndicatorProps} ref={dropIndicatorRef} />
|
|
231
243
|
}
|
|
232
244
|
<CSSTransition
|
|
@@ -238,8 +250,9 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
|
238
250
|
exit: listStyles['react-spectrum-ListViewItem-checkbox--exit'],
|
|
239
251
|
exitActive: listStyles['react-spectrum-ListViewItem-checkbox--exitActive']
|
|
240
252
|
}}
|
|
241
|
-
timeout={160}
|
|
242
|
-
|
|
253
|
+
timeout={160}
|
|
254
|
+
nodeRef={checkboxWrapperRef} >
|
|
255
|
+
<div ref={checkboxWrapperRef} className={listStyles['react-spectrum-ListViewItem-checkboxWrapper']}>
|
|
243
256
|
<Checkbox
|
|
244
257
|
{...checkboxProps}
|
|
245
258
|
UNSAFE_className={listStyles['react-spectrum-ListViewItem-checkbox']}
|
|
@@ -250,8 +263,8 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
|
|
|
250
263
|
slots={{
|
|
251
264
|
text: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
|
|
252
265
|
description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description'], ...descriptionProps},
|
|
253
|
-
|
|
254
|
-
image: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-
|
|
266
|
+
illustration: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-thumbnail']},
|
|
267
|
+
image: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-thumbnail']},
|
|
255
268
|
actionButton: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'], isQuiet: true},
|
|
256
269
|
actionGroup: {
|
|
257
270
|
UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'],
|