Skip to content

Commit 24b80e5

Browse files
committed
select previous item #10
edge case of select next #9
1 parent 2ccdd4f commit 24b80e5

File tree

4 files changed

+165
-10
lines changed

4 files changed

+165
-10
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "macos-multi-select",
3-
"version": "0.1.3",
3+
"version": "0.1.4",
44
"description": "Given a list of ids, and an action, return a list of selected items with the same behaviour of macOS finder list view selection.",
55
"main": "dist/index.js",
66
"repository": "[email protected]:codingedgar/macos-multi-select.git",

src/index.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type Command =
1313
| { type: "DESELECT ALL" }
1414
| { type: "SELECT ADJACENT", id: string }
1515
| { type: "SELECT NEXT" }
16+
| { type: "SELECT PREVIOUS" }
1617

1718
function listIncludesAndIsNotEmpty(list: string[], item: string) {
1819
return list.length > 0 && list.includes(item)
@@ -35,11 +36,10 @@ export function multiselect(context: Context, command: Command): Context {
3536
context.selected.includes(command.id) &&
3637
context.selected.length === 1
3738
) {
38-
const index = context.selected.indexOf(command.id)
3939
return {
4040
...context,
4141
selected: [],
42-
adjacentPivot: head(context.list)!,
42+
adjacentPivot: head(context.list),
4343
};
4444
} else if (
4545
command.type === 'TOGGLE SELECTION' &&
@@ -112,7 +112,7 @@ export function multiselect(context: Context, command: Command): Context {
112112
} else if(
113113
command.type === "SELECT NEXT" &&
114114
context.list.length &&
115-
context.adjacentPivot === undefined
115+
context.selected.length === 0
116116
) {
117117
return {
118118
...context,
@@ -134,7 +134,6 @@ export function multiselect(context: Context, command: Command): Context {
134134
adjacentPivot: nextItem
135135
}
136136
} else if (
137-
!context.selected.length ||
138137
!(
139138
context.selected.length === 1 &&
140139
last(context.selected) === last(context.list)
@@ -147,6 +146,46 @@ export function multiselect(context: Context, command: Command): Context {
147146
} else {
148147
return context;
149148
}
149+
} else if (
150+
command.type === "SELECT PREVIOUS" &&
151+
context.list.length &&
152+
context.selected.length === 0
153+
) {
154+
const pivot = last(context.list)!;
155+
return {
156+
...context,
157+
selected: [pivot],
158+
adjacentPivot: pivot,
159+
}
160+
} else if (
161+
command.type === "SELECT PREVIOUS" &&
162+
context.list.length &&
163+
context.adjacentPivot !== undefined
164+
) {
165+
const pivotIndex = context.list.indexOf(context.adjacentPivot)
166+
167+
if (pivotIndex > 0) {
168+
const prevItem = context.list[pivotIndex - 1];
169+
return {
170+
...context,
171+
selected: [prevItem],
172+
adjacentPivot: prevItem
173+
}
174+
} else if (
175+
!(
176+
context.selected.length === 1 &&
177+
context.selected[0] === head(context.list)
178+
)
179+
) {
180+
const pivot = head(context.list)!;
181+
return {
182+
...context,
183+
selected: [pivot],
184+
adjacentPivot: pivot
185+
}
186+
} else {
187+
return context;
188+
}
150189
} else {
151190
return context;
152191
}

src/spec/selectNext.spec.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import fc from 'fast-check';
2-
import { last } from 'ramda';
2+
import { head, last } from 'ramda';
33
import { Context, multiselect } from '../index';
44
import { nonEmptySubsequentSubarray } from './arbitraries';
55

@@ -22,10 +22,13 @@ describe('Select Next Item', () => {
2222

2323
fc.assert(
2424
fc.property(
25-
fc.set(fc.string(), { minLength: 1 }),
26-
(list) => {
25+
fc.tuple(
26+
fc.set(fc.string(), { minLength: 1 }),
27+
fc.boolean()
28+
),
29+
([list, undefOrTop]) => {
2730
expect(multiselect({
28-
adjacentPivot: undefined,
31+
adjacentPivot: undefOrTop ? undefined : head(list),
2932
list,
3033
selected: []
3134
}, {
@@ -98,6 +101,6 @@ describe('Select Next Item', () => {
98101
}
99102
)
100103
)
101-
})
104+
});
102105

103106
})

src/spec/selectPrevious.spec.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import fc from 'fast-check';
2+
import { head, last } from 'ramda';
3+
import { Context, multiselect } from '../index';
4+
5+
describe('Select Previous Item', () => {
6+
test('should do nothing if the list is empty', () => {
7+
8+
const initialContext: Context = {
9+
adjacentPivot: undefined,
10+
list: [],
11+
selected: []
12+
};
13+
14+
expect(multiselect(initialContext, {
15+
type: "SELECT PREVIOUS",
16+
}))
17+
.toEqual(initialContext)
18+
19+
});
20+
21+
test('should start from the bottom', () => {
22+
23+
fc.assert(
24+
fc.property(
25+
fc.tuple(
26+
fc.set(fc.string(), { minLength: 1 }),
27+
fc.boolean()
28+
)
29+
.map(([list, undefOrTop]) => ({
30+
list,
31+
adjacentPivot: undefOrTop ? undefined : head(list),
32+
selected: [],
33+
})),
34+
(initialContext) => {
35+
const nextAdjacentPivot = last(initialContext.list)!;
36+
37+
expect(multiselect(initialContext, {
38+
type: "SELECT PREVIOUS",
39+
}))
40+
.toEqual({
41+
list: initialContext.list,
42+
selected: [nextAdjacentPivot],
43+
adjacentPivot: nextAdjacentPivot,
44+
})
45+
}
46+
)
47+
)
48+
49+
});
50+
51+
52+
test('should never select beyond first item', () => {
53+
fc.assert(
54+
fc.property(
55+
fc.tuple(
56+
fc.integer(1, 20),
57+
fc.set(
58+
fc.string(), { minLength: 1 }
59+
)
60+
)
61+
.chain(([extra, list]) =>
62+
fc.shuffledSubarray(list)
63+
.map(selected => ({
64+
list,
65+
selected,
66+
adjacentPivot: last(selected),
67+
extra
68+
}))
69+
)
70+
,
71+
({
72+
adjacentPivot,
73+
list,
74+
selected,
75+
extra
76+
}) => {
77+
78+
let prevContext: Context = {
79+
adjacentPivot,
80+
list,
81+
selected
82+
}
83+
84+
const lastSelected = last(selected);
85+
const startOn = lastSelected !== undefined
86+
? list.indexOf(lastSelected) - 1
87+
: list.length - 1
88+
89+
for (let index = startOn; index > -list.length - extra; index--) {
90+
91+
const nextContext = multiselect(prevContext, {
92+
type: "SELECT PREVIOUS",
93+
});
94+
95+
const pivot = index >= 0 ? list[index]: head(list);
96+
97+
expect(nextContext)
98+
.toEqual({
99+
list,
100+
selected: [pivot],
101+
adjacentPivot: pivot,
102+
})
103+
104+
prevContext = nextContext;
105+
106+
}
107+
108+
}
109+
)
110+
)
111+
})
112+
113+
});

0 commit comments

Comments
 (0)