1
1
import { createContext , h } from 'preact' ;
2
- import { useContext } from 'preact/hooks' ;
2
+ import { useCallback , useContext } from 'preact/hooks' ;
3
3
import { signal , useComputed , useSignal , useSignalEffect } from '@preact/signals' ;
4
4
import { useQueryContext } from './QueryProvider.js' ;
5
5
import { usePlatformName } from '../types.js' ;
@@ -11,6 +11,10 @@ import { eventToIntention } from '../utils.js';
11
11
* @typedef {import("../utils.js").Intention } Intention
12
12
*/
13
13
14
+ /**
15
+ * @typedef {{kind: 'set-selections', value: Set<number>, reason: string} } Action
16
+ */
17
+ const SelectionDispatchContext = createContext ( /** @type {(a: Action) => void } */ ( ( a ) => { } ) ) ;
14
18
const SelectionContext = createContext ( {
15
19
selected : signal ( /** @type {Set<number> } */ ( new Set ( [ ] ) ) ) ,
16
20
} ) ;
@@ -23,7 +27,29 @@ const SelectionContext = createContext({
23
27
*/
24
28
export function SelectionProvider ( { children } ) {
25
29
const selected = useSignal ( new Set ( /** @type {number[] } */ ( [ ] ) ) ) ;
26
- return < SelectionContext . Provider value = { { selected } } > { children } </ SelectionContext . Provider > ;
30
+ /** @type {(a: Action) => void } */
31
+ const dispatch = useCallback (
32
+ /**
33
+ * @param {Action } action
34
+ */
35
+ function dispatch ( action ) {
36
+ selected . value = ( ( ) => {
37
+ switch ( action . kind ) {
38
+ case 'set-selections' : {
39
+ return action . value ;
40
+ }
41
+ default :
42
+ return selected . value ;
43
+ }
44
+ } ) ( ) ;
45
+ } ,
46
+ [ selected ] ,
47
+ ) ;
48
+ return (
49
+ < SelectionContext . Provider value = { { selected } } >
50
+ < SelectionDispatchContext . Provider value = { dispatch } > { children } </ SelectionDispatchContext . Provider >
51
+ </ SelectionContext . Provider >
52
+ ) ;
27
53
}
28
54
29
55
// Hook for consuming the context
@@ -37,20 +63,18 @@ export function useSelected() {
37
63
38
64
export function useSelectionEvents ( containerRef ) {
39
65
const { selected } = useContext ( SelectionContext ) ;
40
- /** @type {UpdateSelected } */
41
- const update = ( fn , reason ) => {
42
- // console.log('[❌] clearing selections because', reason);
43
- selected . value = fn ( selected . value ) ;
44
- } ;
66
+ useResetOnQueryChange ( ) ;
67
+ useRowInteractions ( selected , containerRef ) ;
68
+ }
45
69
46
- useResetOnQueryChange ( update ) ;
47
- useRowInteractions ( update , selected , containerRef ) ;
70
+ function useSelectionDispatch ( ) {
71
+ return useContext ( SelectionDispatchContext ) ;
48
72
}
49
73
50
74
/**
51
- * @param {UpdateSelected } update
52
75
*/
53
- function useResetOnQueryChange ( update ) {
76
+ function useResetOnQueryChange ( ) {
77
+ const dispatch = useSelectionDispatch ( ) ;
54
78
const query = useQueryContext ( ) ;
55
79
const { results } = useGlobalState ( ) ;
56
80
const length = useComputed ( ( ) => results . value . items . length ) ;
@@ -61,12 +85,16 @@ function useResetOnQueryChange(update) {
61
85
// when anything about the query changes, reset selections
62
86
// todo: this should not fire on the first value too
63
87
query . subscribe ( ( old ) => {
64
- update ( ( prev ) => new Set ( [ ] ) , 'query changed' ) ;
88
+ dispatch ( { kind : 'set-selections' , value : new Set ( [ ] ) , reason : 'query changed' } ) ;
65
89
} ) ,
66
90
// todo: this should not fire on the first value too
67
91
length . subscribe ( ( newLength ) => {
68
92
if ( newLength < prevLength ) {
69
- update ( ( prev ) => new Set ( [ ] ) , `items length shrank from ${ prevLength } to ${ newLength } ` ) ;
93
+ dispatch ( {
94
+ kind : 'set-selections' ,
95
+ value : new Set ( [ ] ) ,
96
+ reason : `items length shrank from ${ prevLength } to ${ newLength } ` ,
97
+ } ) ;
70
98
}
71
99
prevLength = newLength ;
72
100
} ) ,
@@ -81,12 +109,11 @@ function useResetOnQueryChange(update) {
81
109
}
82
110
83
111
/**
84
- * @param {UpdateSelected } update
85
- * @param {import("@preact/signals").Signal<Set<number>> } selected
112
+ * @param {import("@preact/signals").ReadonlySignal<Set<number>> } selected
86
113
*/
87
- function useRowInteractions ( update , selected , containerRef ) {
114
+ function useRowInteractions ( selected , containerRef ) {
88
115
const platformName = usePlatformName ( ) ;
89
-
116
+ const dispatch = useSelectionDispatch ( ) ;
90
117
const anchorIndex = useSignal ( /** @type {null|number } */ ( null ) ) ;
91
118
const lastShiftRange = useSignal ( { start : /** @type {null|number } */ ( null ) , end : /** @type {null|number } */ ( null ) } ) ;
92
119
const focusedIndex = useSignal ( /** @type {null|number } */ ( null ) ) ;
@@ -101,7 +128,7 @@ function useRowInteractions(update, selected, containerRef) {
101
128
const { index } = selection ;
102
129
switch ( intention ) {
103
130
case 'click' : {
104
- selected . value = new Set ( [ index ] ) ;
131
+ dispatch ( { kind : 'set-selections' , value : new Set ( [ index ] ) , reason : 'row clicked' } ) ;
105
132
anchorIndex . value = index ;
106
133
lastShiftRange . value = { start : null , end : null } ;
107
134
focusedIndex . value = index ;
@@ -114,7 +141,7 @@ function useRowInteractions(update, selected, containerRef) {
114
141
} else {
115
142
newSelected . add ( index ) ;
116
143
}
117
- selected . value = newSelected ;
144
+ dispatch ( { kind : 'set-selections' , value : newSelected , reason : 'row ctrl+clicked' } ) ;
118
145
anchorIndex . value = index ;
119
146
lastShiftRange . value = { start : null , end : null } ;
120
147
focusedIndex . value = index ;
@@ -140,7 +167,7 @@ function useRowInteractions(update, selected, containerRef) {
140
167
}
141
168
142
169
lastShiftRange . value = { start, end } ;
143
- selected . value = newSelected ;
170
+ dispatch ( { kind : 'set-selections' , value : newSelected , reason : 'row shift+clicked' } ) ;
144
171
focusedIndex . value = index ;
145
172
break ;
146
173
}
@@ -182,7 +209,7 @@ function useRowInteractions(update, selected, containerRef) {
182
209
}
183
210
184
211
lastShiftRange . value = { start, end } ;
185
- selected . value = newSelected ;
212
+ dispatch ( { kind : 'set-selections' , value : newSelected , reason : 'shift+up or shift+down pressed' } ) ;
186
213
187
214
if ( anchorIndex . value === null ) {
188
215
anchorIndex . value = newIndex ;
@@ -191,7 +218,7 @@ function useRowInteractions(update, selected, containerRef) {
191
218
}
192
219
case 'up' :
193
220
case 'down' : {
194
- selected . value = new Set ( [ newIndex ] ) ;
221
+ dispatch ( { kind : 'set-selections' , value : new Set ( [ newIndex ] ) , reason : 'up or down pressed without modifier' } ) ;
195
222
anchorIndex . value = newIndex ;
196
223
lastShiftRange . value = { start : null , end : null } ;
197
224
break ;
@@ -218,7 +245,11 @@ function useRowInteractions(update, selected, containerRef) {
218
245
if ( event . target !== document . body ) return ;
219
246
switch ( intention ) {
220
247
case 'escape' : {
221
- update ( ( prev ) => ( prev . size > 0 ? new Set ( [ ] ) : prev ) , 'escape key pressed' ) ;
248
+ dispatch ( {
249
+ kind : 'set-selections' ,
250
+ value : selected . value . size > 0 ? new Set ( [ ] ) : selected . value ,
251
+ reason : 'escape key pressed' ,
252
+ } ) ;
222
253
return true ;
223
254
}
224
255
}
0 commit comments