react-arborist 0.1.12 → 0.2.0-beta.0

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.
Files changed (86) hide show
  1. package/dist/{lib/components → components}/drop-cursor.d.ts +1 -0
  2. package/dist/{lib/components → components}/preview.d.ts +1 -0
  3. package/dist/{lib/components → components}/row.d.ts +0 -0
  4. package/dist/{lib/components → components}/tree.d.ts +0 -0
  5. package/dist/{lib/context.d.ts → context.d.ts} +4 -3
  6. package/dist/{lib/data → data}/enrich-tree.d.ts +1 -1
  7. package/dist/{lib/data → data}/flatten-tree.d.ts +0 -0
  8. package/dist/dnd/compute-drop.d.ts +37 -0
  9. package/dist/{lib/dnd → dnd}/drag-hook.d.ts +0 -0
  10. package/dist/{lib/dnd → dnd}/drop-hook.d.ts +0 -0
  11. package/dist/{lib/dnd → dnd}/outer-drop-hook.d.ts +0 -0
  12. package/dist/index.d.ts +4 -0
  13. package/dist/index.js +1354 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/module.js +1346 -0
  16. package/dist/module.js.map +1 -0
  17. package/dist/{lib/provider.d.ts → provider.d.ts} +1 -0
  18. package/dist/{lib/reducer.d.ts → reducer.d.ts} +4 -3
  19. package/dist/{lib/selection → selection}/range.d.ts +0 -0
  20. package/dist/{lib/selection → selection}/selection-hook.d.ts +0 -0
  21. package/dist/{lib/selection → selection}/selection.d.ts +0 -0
  22. package/dist/{lib/tree-api-hook.d.ts → tree-api-hook.d.ts} +0 -0
  23. package/dist/{lib/tree-api.d.ts → tree-api.d.ts} +4 -3
  24. package/dist/{lib/types.d.ts → types.d.ts} +8 -3
  25. package/dist/types.d.ts.map +1 -0
  26. package/dist/{lib/utils.d.ts → utils.d.ts} +2 -0
  27. package/package.json +16 -43
  28. package/src/components/drop-cursor.tsx +47 -0
  29. package/src/components/preview.tsx +108 -0
  30. package/src/components/row.tsx +119 -0
  31. package/src/components/tree.tsx +118 -0
  32. package/src/context.tsx +52 -0
  33. package/src/data/enrich-tree.ts +74 -0
  34. package/src/data/flatten-tree.ts +17 -0
  35. package/src/data/make-tree.ts +37 -0
  36. package/src/dnd/compute-drop.ts +184 -0
  37. package/src/dnd/drag-hook.ts +48 -0
  38. package/src/dnd/drop-hook.ts +66 -0
  39. package/src/dnd/measure-hover.ts +26 -0
  40. package/src/dnd/outer-drop-hook.ts +50 -0
  41. package/src/index.ts +5 -0
  42. package/src/provider.tsx +61 -0
  43. package/src/reducer.ts +161 -0
  44. package/src/selection/range.ts +41 -0
  45. package/src/selection/selection-hook.ts +24 -0
  46. package/src/selection/selection.test.ts +111 -0
  47. package/src/selection/selection.ts +186 -0
  48. package/src/tree-api-hook.ts +34 -0
  49. package/src/tree-api.ts +129 -0
  50. package/src/types.ts +147 -0
  51. package/src/utils.ts +35 -0
  52. package/tsconfig.json +28 -0
  53. package/README.md +0 -197
  54. package/dist/lib/components/drop-cursor.js +0 -53
  55. package/dist/lib/components/preview.js +0 -91
  56. package/dist/lib/components/row.js +0 -122
  57. package/dist/lib/components/tree.js +0 -76
  58. package/dist/lib/context.js +0 -57
  59. package/dist/lib/data/enrich-tree.js +0 -48
  60. package/dist/lib/data/flatten-tree.js +0 -20
  61. package/dist/lib/data/make-tree.d.ts +0 -5
  62. package/dist/lib/data/make-tree.js +0 -40
  63. package/dist/lib/data/visible-nodes-hook.d.ts +0 -2
  64. package/dist/lib/data/visible-nodes-hook.js +0 -19
  65. package/dist/lib/dnd/compute-drop.d.ts +0 -24
  66. package/dist/lib/dnd/compute-drop.js +0 -113
  67. package/dist/lib/dnd/drag-hook.js +0 -36
  68. package/dist/lib/dnd/drop-hook.js +0 -60
  69. package/dist/lib/dnd/measure-hover.d.ts +0 -8
  70. package/dist/lib/dnd/measure-hover.js +0 -21
  71. package/dist/lib/dnd/outer-drop-hook.js +0 -50
  72. package/dist/lib/index.d.ts +0 -3
  73. package/dist/lib/index.js +0 -7
  74. package/dist/lib/provider.js +0 -44
  75. package/dist/lib/reducer.js +0 -149
  76. package/dist/lib/selection/range.js +0 -45
  77. package/dist/lib/selection/selection-hook.js +0 -24
  78. package/dist/lib/selection/selection.js +0 -192
  79. package/dist/lib/selection/selection.test.d.ts +0 -1
  80. package/dist/lib/selection/selection.test.js +0 -102
  81. package/dist/lib/tree-api-hook.js +0 -26
  82. package/dist/lib/tree-api.js +0 -130
  83. package/dist/lib/tree-monitor.d.ts +0 -15
  84. package/dist/lib/tree-monitor.js +0 -32
  85. package/dist/lib/types.js +0 -2
  86. package/dist/lib/utils.js +0 -31
@@ -0,0 +1,66 @@
1
+ import { RefObject } from "react";
2
+ import { ConnectDropTarget, useDrop } from "react-dnd";
3
+ import { useStaticContext } from "../context";
4
+ import { DragItem, Node } from "../types";
5
+ import { isDecendent, isFolder } from "../utils";
6
+ import { computeDrop } from "./compute-drop";
7
+
8
+ export type DropResult = {
9
+ parentId: string | null;
10
+ index: number;
11
+ };
12
+
13
+ export type CollectedProps = undefined;
14
+
15
+ export function useDropHook(
16
+ el: RefObject<HTMLElement | null>,
17
+ node: Node,
18
+ prev: Node | null,
19
+ next: Node | null
20
+ ): [CollectedProps, ConnectDropTarget] {
21
+ const tree = useStaticContext();
22
+ return useDrop<DragItem, DropResult | null, CollectedProps>(
23
+ () => ({
24
+ accept: "NODE",
25
+ canDrop: (item) => {
26
+ for (let id of item.dragIds) {
27
+ const drag = tree.api.getNode(id);
28
+ if (!drag) return false;
29
+ if (isFolder(drag) && isDecendent(node, drag)) return false;
30
+ }
31
+ return true;
32
+ },
33
+ hover: (item, m) => {
34
+ if (m.canDrop()) {
35
+ const offset = m.getClientOffset();
36
+ if (!el.current || !offset) return;
37
+ const { cursor } = computeDrop({
38
+ element: el.current,
39
+ offset: offset,
40
+ indent: tree.indent,
41
+ node: node,
42
+ prevNode: prev,
43
+ nextNode: next,
44
+ });
45
+ if (cursor) tree.api.showCursor(cursor);
46
+ } else {
47
+ tree.api.hideCursor();
48
+ }
49
+ },
50
+ drop: (item, m): DropResult | undefined | null => {
51
+ const offset = m.getClientOffset();
52
+ if (!el.current || !offset) return;
53
+ const { drop } = computeDrop({
54
+ element: el.current,
55
+ offset: offset,
56
+ indent: tree.indent,
57
+ node: node,
58
+ prevNode: prev,
59
+ nextNode: next,
60
+ });
61
+ return drop;
62
+ },
63
+ }),
64
+ [node, prev, el, tree]
65
+ );
66
+ }
@@ -0,0 +1,26 @@
1
+ import { XYCoord } from "react-dnd";
2
+ import { bound } from "../utils";
3
+
4
+ export function measureHover(el: HTMLElement, offset: XYCoord, indent: number) {
5
+ const nextEl = el.nextElementSibling as HTMLElement | null;
6
+ const prevEl = el.previousElementSibling as HTMLElement | null;
7
+ const rect = el.getBoundingClientRect();
8
+ const x = offset.x - Math.round(rect.x);
9
+ const y = offset.y - Math.round(rect.y);
10
+ const height = rect.height;
11
+ const inTopHalf = y < height / 2;
12
+ const inBottomHalf = !inTopHalf;
13
+ const pad = height / 4;
14
+ const inMiddle = y > pad && y < height - pad;
15
+ const maxLevel = Number(
16
+ inBottomHalf ? el.dataset.level : prevEl ? prevEl.dataset.level : 0
17
+ );
18
+ const minLevel = Number(
19
+ inTopHalf ? el.dataset.level : nextEl ? nextEl.dataset.level : 0
20
+ );
21
+ const level = bound(Math.floor(x / indent), minLevel, maxLevel);
22
+
23
+ return { level, inTopHalf, inBottomHalf, inMiddle };
24
+ }
25
+
26
+ export type HoverData = ReturnType<typeof measureHover>;
@@ -0,0 +1,50 @@
1
+ import { useDrop } from "react-dnd";
2
+ import { useStaticContext } from "../context";
3
+ import { DragItem } from "../types";
4
+ import { computeDrop } from "./compute-drop";
5
+ import { DropResult } from "./drop-hook";
6
+
7
+ export function useOuterDrop() {
8
+ const tree = useStaticContext();
9
+
10
+ // In case we drop an item at the bottom of the list
11
+ const [, drop] = useDrop<DragItem, DropResult | null, { isOver: boolean }>(
12
+ () => ({
13
+ accept: "NODE",
14
+ hover: (item, m) => {
15
+ if (!m.isOver({ shallow: true })) return;
16
+ const offset = m.getClientOffset();
17
+ if (!tree.listEl.current || !offset) return;
18
+ const { cursor } = computeDrop({
19
+ element: tree.listEl.current,
20
+ offset: offset,
21
+ indent: tree.indent,
22
+ node: null,
23
+ prevNode: tree.api.visibleNodes[tree.api.visibleNodes.length - 1],
24
+ nextNode: null,
25
+ });
26
+ if (cursor) tree.api.showCursor(cursor);
27
+ },
28
+ canDrop: (item, m) => {
29
+ return m.isOver({ shallow: true });
30
+ },
31
+ drop: (item, m) => {
32
+ if (m.didDrop()) return;
33
+ const offset = m.getClientOffset();
34
+ if (!tree.listEl.current || !offset) return;
35
+ const { drop } = computeDrop({
36
+ element: tree.listEl.current,
37
+ offset: offset,
38
+ indent: tree.indent,
39
+ node: null,
40
+ prevNode: tree.api.visibleNodes[tree.api.visibleNodes.length - 1],
41
+ nextNode: null,
42
+ });
43
+ return drop;
44
+ },
45
+ }),
46
+ [tree]
47
+ );
48
+
49
+ drop(tree.listEl);
50
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { Tree } from "./components/tree";
2
+ import { TreeApi } from "./tree-api";
3
+ import type { NodeRenderer, NodeState, NodeHandlers } from "./types";
4
+
5
+ export { Tree, TreeApi, NodeRenderer, NodeState, NodeHandlers };
@@ -0,0 +1,61 @@
1
+ import { useImperativeHandle, useMemo, useReducer, useRef } from "react";
2
+ import { FixedSizeList } from "react-window";
3
+ import {
4
+ CursorLocationContext,
5
+ CursorParentId,
6
+ EditingIdContext,
7
+ IsCursorOverFolder,
8
+ SelectionContext,
9
+ Static,
10
+ } from "./context";
11
+ import { Cursor } from "./dnd/compute-drop";
12
+ import { initState, reducer } from "./reducer";
13
+ import { useSelectionKeys } from "./selection/selection-hook";
14
+ import { useTreeApi } from "./tree-api-hook";
15
+ import { StateContext, StaticContext, TreeProviderProps } from "./types";
16
+
17
+ export function TreeViewProvider<T>(props: TreeProviderProps<T>) {
18
+ const [state, dispatch] = useReducer(reducer, initState());
19
+ const list = useRef<FixedSizeList>();
20
+ const api = useTreeApi<T>(state, dispatch, props, list.current);
21
+
22
+ useImperativeHandle(props.imperativeHandle, () => api);
23
+ useSelectionKeys(props.listEl, api);
24
+ const staticValue = useMemo<StaticContext<T>>(
25
+ () => ({ ...props, api, list }),
26
+ [props, api, list]
27
+ );
28
+
29
+ /**
30
+ * This context pattern is ridiculous, next time use redux.
31
+ */
32
+ return (
33
+ // @ts-ignore
34
+ <Static.Provider value={staticValue}>
35
+ <EditingIdContext.Provider value={state.editingId}>
36
+ <SelectionContext.Provider value={state.selection}>
37
+ <CursorParentId.Provider value={getParentId(state.cursor)}>
38
+ <IsCursorOverFolder.Provider value={isOverFolder(state)}>
39
+ <CursorLocationContext.Provider value={state.cursor}>
40
+ {props.children}
41
+ </CursorLocationContext.Provider>
42
+ </IsCursorOverFolder.Provider>
43
+ </CursorParentId.Provider>
44
+ </SelectionContext.Provider>
45
+ </EditingIdContext.Provider>
46
+ </Static.Provider>
47
+ );
48
+ }
49
+
50
+ function getParentId(cursor: Cursor) {
51
+ switch (cursor.type) {
52
+ case "highlight":
53
+ return cursor.id;
54
+ default:
55
+ return null;
56
+ }
57
+ }
58
+
59
+ function isOverFolder(state: StateContext) {
60
+ return state.cursor.type === "highlight";
61
+ }
package/src/reducer.ts ADDED
@@ -0,0 +1,161 @@
1
+ import { Cursor } from "./dnd/compute-drop";
2
+ import { Selection } from "./selection/selection";
3
+ import { StateContext } from "./types";
4
+
5
+ export const initState = (): StateContext => ({
6
+ visibleIds: [],
7
+ cursor: { type: "none" } as Cursor,
8
+ editingId: null,
9
+ selection: {
10
+ data: null,
11
+ ids: [],
12
+ },
13
+ });
14
+
15
+ export const actions = {
16
+ setCursorLocation: (cursor: Cursor) => ({
17
+ type: "SET_CURSOR_LOCATION" as "SET_CURSOR_LOCATION",
18
+ cursor,
19
+ }),
20
+
21
+ setVisibleIds: (
22
+ ids: string[], // index to id
23
+ idMap: { [id: string]: number } // id to index
24
+ ) => ({
25
+ type: "SET_VISIBLE_IDS" as "SET_VISIBLE_IDS",
26
+ ids,
27
+ idMap,
28
+ }),
29
+
30
+ select: (index: number | null, meta: boolean, shift: boolean) => ({
31
+ type: "SELECT" as "SELECT",
32
+ index,
33
+ meta,
34
+ shift,
35
+ }),
36
+
37
+ selectId: (id: string) => ({
38
+ type: "SELECT_ID" as "SELECT_ID",
39
+ id,
40
+ }),
41
+
42
+ edit: (id: string | null) => ({
43
+ type: "EDIT" as "EDIT",
44
+ id,
45
+ }),
46
+
47
+ stepUp: (shift: boolean, ids: string[]) => ({
48
+ type: "STEP_UP" as "STEP_UP",
49
+ shift,
50
+ }),
51
+
52
+ stepDown: (shift: boolean, ids: string[]) => ({
53
+ type: "STEP_DOWN" as "STEP_DOWN",
54
+ shift,
55
+ }),
56
+ };
57
+
58
+ type ActionObj = {
59
+ [Prop in keyof typeof actions]: ReturnType<typeof actions[Prop]>;
60
+ };
61
+ export type Action = ActionObj[keyof ActionObj];
62
+
63
+ export function reducer(state: StateContext, action: Action): StateContext {
64
+ switch (action.type) {
65
+ case "EDIT":
66
+ return {
67
+ ...state,
68
+ editingId: action.id,
69
+ };
70
+ case "SET_CURSOR_LOCATION":
71
+ if (equal(state.cursor, action.cursor)) {
72
+ return state;
73
+ } else {
74
+ return { ...state, cursor: action.cursor };
75
+ }
76
+ case "SELECT":
77
+ var s = Selection.parse(state.selection.data, state.visibleIds);
78
+ if (action.index === null) {
79
+ s.clear();
80
+ } else if (action.meta) {
81
+ if (s.contains(action.index)) {
82
+ s.deselect(action.index);
83
+ } else {
84
+ s.multiSelect(action.index);
85
+ }
86
+ } else if (action.shift) {
87
+ s.extend(action.index);
88
+ } else {
89
+ s.select(action.index);
90
+ }
91
+ return {
92
+ ...state,
93
+ selection: {
94
+ data: s.serialize(),
95
+ ids: s.getSelectedItems(),
96
+ },
97
+ };
98
+ case "SELECT_ID":
99
+ return {
100
+ ...state,
101
+ selection: {
102
+ ...state.selection,
103
+ ids: [action.id],
104
+ },
105
+ };
106
+ case "STEP_UP":
107
+ var s3 = Selection.parse(state.selection.data, state.visibleIds);
108
+ var f = s3.getFocus();
109
+ if (action.shift) {
110
+ s3.extend(f - 1);
111
+ } else {
112
+ s3.select(f - 1);
113
+ }
114
+ return {
115
+ ...state,
116
+ selection: {
117
+ data: s3.serialize(),
118
+ ids: s3.getSelectedItems(),
119
+ },
120
+ };
121
+ case "STEP_DOWN":
122
+ var s6 = Selection.parse(state.selection.data, state.visibleIds);
123
+ var f2 = s6.getFocus();
124
+ if (action.shift) {
125
+ s6.extend(f2 + 1);
126
+ } else {
127
+ s6.select(f2 + 1);
128
+ }
129
+ return {
130
+ ...state,
131
+ selection: {
132
+ data: s6.serialize(),
133
+ ids: s6.getSelectedItems(),
134
+ },
135
+ };
136
+ case "SET_VISIBLE_IDS":
137
+ // The visible ids changed
138
+ var ids = state.selection.ids;
139
+ // Start with a blank selection
140
+ var s2 = new Selection([], null, "none", state.visibleIds);
141
+ // Add each of the old selected ids to this new selection
142
+ for (let id of ids) {
143
+ if (id in action.idMap) s2.multiSelect(action.idMap[id]);
144
+ }
145
+ return {
146
+ ...state,
147
+ visibleIds: action.ids,
148
+ selection: {
149
+ ids,
150
+ data: s2.serialize(),
151
+ },
152
+ };
153
+ default:
154
+ return state;
155
+ }
156
+ }
157
+
158
+ function equal(a: Cursor | null, b: Cursor | null) {
159
+ if (a === null || b === null) return false;
160
+ return JSON.stringify(a) === JSON.stringify(b);
161
+ }
@@ -0,0 +1,41 @@
1
+ export class Range {
2
+ constructor(public start: number, public end: number) {
3
+ if (this.start > this.end)
4
+ throw new Error("Invalid range: start larger than end");
5
+ }
6
+
7
+ serialize(): [number, number] {
8
+ return [this.start, this.end];
9
+ }
10
+
11
+ contains(n: number) {
12
+ return n >= this.start && n <= this.end;
13
+ }
14
+
15
+ overlaps(r: Range) {
16
+ return this.contains(r.start - 1) || this.contains(r.end + 1);
17
+ }
18
+
19
+ combine(r: Range) {
20
+ this.start = Math.min(r.start, this.start);
21
+ this.end = Math.max(r.end, this.end);
22
+ }
23
+
24
+ get size() {
25
+ return this.end - this.start + 1;
26
+ }
27
+
28
+ clone() {
29
+ return new Range(this.start, this.end);
30
+ }
31
+
32
+ map(fn: (index: any) => string): any {
33
+ let returns = [];
34
+ for (let i = this.start; i <= this.end; i++) returns.push(fn(i));
35
+ return returns;
36
+ }
37
+
38
+ isEqual(other: Range) {
39
+ return this.start === other.start && this.end === other.end;
40
+ }
41
+ }
@@ -0,0 +1,24 @@
1
+ import { MutableRefObject, useEffect } from "react";
2
+ import { TreeApi } from "../tree-api";
3
+
4
+ export function useSelectionKeys<T>(
5
+ ref: MutableRefObject<HTMLDivElement | null>,
6
+ api: TreeApi<T>
7
+ ) {
8
+ useEffect(() => {
9
+ const el = ref.current;
10
+ const cb = (e: KeyboardEvent) => {
11
+ if (e.code === "ArrowDown") {
12
+ e.preventDefault();
13
+ api.selectDownwards(e.shiftKey);
14
+ } else if (e.code === "ArrowUp") {
15
+ e.preventDefault();
16
+ api.selectUpwards(e.shiftKey);
17
+ }
18
+ };
19
+ el?.addEventListener("keydown", cb);
20
+ return () => {
21
+ el?.removeEventListener("keydown", cb);
22
+ };
23
+ }, [ref, api]);
24
+ }
@@ -0,0 +1,111 @@
1
+ import { Selection } from "./selection";
2
+
3
+ const createSelection = (...ranges: [number, number][]) => {
4
+ return new Selection(ranges);
5
+ };
6
+
7
+ describe("select", () => {
8
+ test("select one after end", () => {
9
+ const s = createSelection([0, 0]);
10
+ s.multiSelect(1);
11
+ expect(s.getRanges()).toEqual([[0, 1]]);
12
+ expect(s.direction).toEqual("forward");
13
+ });
14
+
15
+ test("select one before start", () => {
16
+ const s = createSelection([1, 1]);
17
+ s.multiSelect(0);
18
+ expect(s.getRanges()).toEqual([[0, 1]]);
19
+ expect(s.direction).toEqual("backward");
20
+ });
21
+
22
+ test("select between two ranges", () => {
23
+ const s = createSelection([0, 0], [2, 2]);
24
+ s.multiSelect(1);
25
+ expect(s.getRanges()).toEqual([[0, 2]]);
26
+ expect(s.direction).toEqual("forward");
27
+ });
28
+
29
+ test("select new spot", () => {
30
+ const s = createSelection([0, 0]);
31
+ s.multiSelect(5);
32
+ expect(s.getRanges()).toEqual([
33
+ [0, 0],
34
+ [5, 5],
35
+ ]);
36
+ expect(s.direction).toEqual("none");
37
+ });
38
+ });
39
+
40
+ describe("deselect", () => {
41
+ test("one", () => {
42
+ const s = createSelection([0, 0]);
43
+ s.deselect(0);
44
+ expect(s.getRanges()).toEqual([]);
45
+ });
46
+
47
+ test("start of a range", () => {
48
+ const s = createSelection([0, 5]);
49
+ s.deselect(0);
50
+ expect(s.getRanges()).toEqual([[1, 5]]);
51
+ });
52
+
53
+ test("end of a range", () => {
54
+ const s = createSelection([0, 5]);
55
+ s.deselect(5);
56
+ expect(s.getRanges()).toEqual([[0, 4]]);
57
+ });
58
+
59
+ test("between a range", () => {
60
+ const s = createSelection([0, 5]);
61
+ s.deselect(3);
62
+ expect(s.getRanges()).toEqual([
63
+ [0, 2],
64
+ [4, 5],
65
+ ]);
66
+ });
67
+ });
68
+
69
+ describe("extend", () => {
70
+ test("up", () => {
71
+ const s = createSelection();
72
+ s.multiSelect(5);
73
+ s.extend(6);
74
+ expect(s.getRanges()).toEqual([[5, 6]]);
75
+ });
76
+
77
+ test("down", () => {
78
+ const s = createSelection();
79
+ s.multiSelect(5);
80
+ s.extend(4);
81
+ expect(s.getRanges()).toEqual([[4, 5]]);
82
+ });
83
+
84
+ test("around anchor", () => {
85
+ const s = createSelection([5, 10]);
86
+ s.extend(1);
87
+ expect(s.getRanges()).toEqual([[1, 5]]);
88
+ });
89
+
90
+ test("through other ranges", () => {
91
+ const s = createSelection([0, 0], [5, 5], [9, 10]);
92
+ s.multiSelect(2);
93
+ s.extend(20);
94
+ expect(s.getRanges()).toEqual([
95
+ [0, 0],
96
+ [2, 20],
97
+ ]);
98
+ });
99
+
100
+ test("clicking backward", () => {
101
+ const s = createSelection([15, 15]);
102
+ s.extend(3);
103
+ expect(s.getRanges()).toEqual([[3, 15]]);
104
+ });
105
+
106
+ test("split range then extend", () => {
107
+ const s = createSelection([5, 10]);
108
+ s.deselect(8);
109
+ expect(s.currentIndex).toEqual(1);
110
+ });
111
+ });