diff --git a/packages/components/src/board-index.ts b/packages/components/src/board-index.ts index f37cdd04..5cbe43ae 100644 --- a/packages/components/src/board-index.ts +++ b/packages/components/src/board-index.ts @@ -31,6 +31,7 @@ import searchable_text from './searchable-text/boards/searchable-text.board.js'; import tree from './tree/boards/tree.board.js'; import tree_focus from './tree/boards/tree-focus.board.js'; import tree_keyboard from './tree/boards/tree-keyboard.board.js'; +import tree_multi_selection from './tree/boards/tree-multi-selection.board.js'; import tree_with_lanes from './tree/boards/tree-with-lanes.board.js'; import use_element_size from './_codux/boards/hooks/use-element-size/use-element-size.board.js'; import use_scroll_horizontal_window from './hooks/boards/use-scroll/use-scroll-horizontal-window.board.js'; @@ -66,6 +67,7 @@ export default [ tree, tree_focus, tree_keyboard, + tree_multi_selection, tree_with_lanes, use_element_size, use_scroll_horizontal_window, diff --git a/packages/components/src/board-plugins/scenario-plugin/scenario-plugin.tsx b/packages/components/src/board-plugins/scenario-plugin/scenario-plugin.tsx index adb92758..70691f0b 100644 --- a/packages/components/src/board-plugins/scenario-plugin/scenario-plugin.tsx +++ b/packages/components/src/board-plugins/scenario-plugin/scenario-plugin.tsx @@ -278,7 +278,7 @@ export const hoverAction = (selector?: string, timeout = 2_000): Action => { }; }; -export const clickAction = (selector?: string, timeout = 2_000): Action => { +export const clickAction = (selector?: string, timeout = 2_000, eventData: MouseEventInit = {}): Action => { return { title: 'Click ' + (selector || 'window'), execute: () => { @@ -288,18 +288,21 @@ export const clickAction = (selector?: string, timeout = 2_000): Action => { new MouseEvent('mousedown', { bubbles: true, relatedTarget: target, + ...eventData, }), ); target.dispatchEvent( new MouseEvent('click', { bubbles: true, relatedTarget: target, + ...eventData, }), ); target.dispatchEvent( new MouseEvent('mouseup', { bubbles: true, relatedTarget: target, + ...eventData, }), ); } @@ -309,7 +312,7 @@ export const clickAction = (selector?: string, timeout = 2_000): Action => { }; }; -export const keyDownAction = (selector: string, keyCode: string, which: number) => { +export const keyDownAction = (selector: string, keyCode: string, eventData: KeyboardEventInit = {}) => { return { title: `key down ${keyCode}`, execute: () => { @@ -323,7 +326,7 @@ export const keyDownAction = (selector: string, keyCode: string, which: number) key: keyCode, bubbles: true, composed: true, - which: which, + ...eventData, }), ); }, diff --git a/packages/components/src/tree/boards/tree-focus.board.tsx b/packages/components/src/tree/boards/tree-focus.board.tsx index a54f4601..31076320 100644 --- a/packages/components/src/tree/boards/tree-focus.board.tsx +++ b/packages/components/src/tree/boards/tree-focus.board.tsx @@ -67,16 +67,16 @@ export default createBoard({ title: 'tree focus should follow select and not hover', events: [ clickAction('[data-id="1"]'), - keyDownAction('[data-id="1"]', KeyCodes.ArrowRight, 39), + keyDownAction('[data-id="1"]', KeyCodes.ArrowRight, { which: 39 }), expectElement('[data-id="2"]'), hoverAction('[data-id="3"]'), - keyDownAction('[data-id="1"]', KeyCodes.ArrowDown, 40), + keyDownAction('[data-id="1"]', KeyCodes.ArrowDown, { which: 40 }), expectElementStyle('[data-id="2"]', FOCUSED_STYLE), // blue (focused) clickAction('#clear'), expectElementStyle('[data-id="2"]', DEFAULT_STYLE), // not focused clickAction('#select'), focusAction('#LIST'), - keyDownAction('[data-id="5"]', KeyCodes.ArrowDown, 40), + keyDownAction('[data-id="5"]', KeyCodes.ArrowDown, { which: 40 }), expectElementStyle('[data-id="6"]', FOCUSED_STYLE), // blue (focused) ], }), diff --git a/packages/components/src/tree/boards/tree-keyboard.board.tsx b/packages/components/src/tree/boards/tree-keyboard.board.tsx index 1d7151a3..47c7d693 100644 --- a/packages/components/src/tree/boards/tree-keyboard.board.tsx +++ b/packages/components/src/tree/boards/tree-keyboard.board.tsx @@ -53,17 +53,17 @@ export default createBoard({ title: 'tree focus test', events: [ clickAction('[data-id="1"]'), - keyDownAction('#LIST', KeyCodes.ArrowRight, 39), + keyDownAction('#LIST', KeyCodes.ArrowRight, { which: 39 }), expectElement('[data-id="2"]'), - keyDownAction('[data-id="1"]', KeyCodes.ArrowDown, 40), + keyDownAction('[data-id="1"]', KeyCodes.ArrowDown, { which: 40 }), expectElementStyle('[data-id="2"]', FOCUSED_STYLE), //blue (focused) - keyDownAction('[data-id="2"]', KeyCodes.Space, 32), + keyDownAction('[data-id="2"]', KeyCodes.Space, { which: 32 }), expectElementStyle('[data-id="2"]', SELECTED_STYLE), - keyDownAction('[data-id="2"]', KeyCodes.Home, 36), + keyDownAction('[data-id="2"]', KeyCodes.Home, { which: 36 }), expectElementStyle('[data-id="1"]', FOCUSED_STYLE), //blue (focused) - keyDownAction('[data-id="1"]', KeyCodes.End, 35), + keyDownAction('[data-id="1"]', KeyCodes.End, { which: 35 }), expectElementStyle('[data-id="6"]', FOCUSED_STYLE), //blue (focused) - keyDownAction('[data-id="6"]', KeyCodes.Enter, 13), + keyDownAction('[data-id="6"]', KeyCodes.Enter, { which: 13 }), expectElementStyle('[data-id="6"]', SELECTED_STYLE), // selected ], }), diff --git a/packages/components/src/tree/boards/tree-multi-selection.board.tsx b/packages/components/src/tree/boards/tree-multi-selection.board.tsx new file mode 100644 index 00000000..7e70b6ea --- /dev/null +++ b/packages/components/src/tree/boards/tree-multi-selection.board.tsx @@ -0,0 +1,148 @@ +import { createBoard } from '@wixc3/react-board'; +import React, { useRef, useState } from 'react'; +import { Tree } from '../tree.js'; +import { TreeItemData } from '../../board-assets/index.js'; +import { TreeItemRenderer } from '../../board-assets/tree-items/tree-item-renderer.js'; +import { + scenarioPlugin, + clickAction, + keyDownAction, + expectElement, + expectElementStyle, + expectElementsStyle, +} from '../../board-plugins/index.js'; +import { KeyCodes } from '../../common/keycodes.js'; +import { DEFAULT_STYLE, FOCUSED_STYLE, SELECTED_STYLE } from './consts.js'; + +const data: TreeItemData = { + id: '1', + title: 'item 1', + children: [ + { + id: '2', + title: 'item 2', + children: [ + { + id: '2.1', + title: 'item 2.1', + children: [ + { id: '2.1.1', title: 'item 2.1.1' }, + { id: '2.1.2', title: 'item 2.1.2' }, + { id: '2.1.3', title: 'item 2.1.3' }, + ], + }, + ], + }, + { id: '3', title: 'item 3' }, + { id: '4', title: 'item 4' }, + { id: '5', title: 'item 5' }, + { id: '6', title: 'item 6' }, + ], +}; + +export default createBoard({ + name: 'Tree multi-selection', + Board: () => { + const openItemsControl = useState([]); + const scrollRef = useRef(null); + return ( + + data={data} + getId={(it) => it.id} + ItemRenderer={TreeItemRenderer} + getChildren={(it) => it.children || []} + openItemsControls={openItemsControl} + overlay={{ el: () => null, props: {} }} + listRoot={{ + props: { + ref: scrollRef, + id: 'LIST', + style: { outline: 'none', width: '12rem' }, + }, + }} + eventRoots={[scrollRef]} + /> + ); + }, + plugins: [ + scenarioPlugin.use({ + title: 'tree multi selection test', + events: [ + clickAction('[data-id="1"]'), + keyDownAction('#LIST', KeyCodes.ArrowRight, { which: 39 }), + expectElement('[data-id="2"]'), + // basic range multi-selection using the shift key + keyDownAction('[data-id="1"]', KeyCodes.ArrowDown, { which: 40, shiftKey: true }), + expectElementsStyle({ + [`data-id="1"`]: SELECTED_STYLE, + [`data-id="2"`]: SELECTED_STYLE, + }), + // checking two sets of ranges + // 1 <- selected + // 2 <- selected + // 3 + // 4 <- selected + keyDownAction('[data-id="2"]', KeyCodes.ArrowDown, { which: 40 }), + expectElement('[data-id="3"]'), + keyDownAction('[data-id="3"]', KeyCodes.ArrowDown, { which: 40, shiftKey: true }), + expectElementsStyle({ + [`data-id="1"`]: SELECTED_STYLE, + [`data-id="2"`]: SELECTED_STYLE, + [`data-id="3"`]: DEFAULT_STYLE, + [`data-id="4"`]: SELECTED_STYLE, + }), + // clears multi selection on click + clickAction('[data-id="3"]'), + expectElementsStyle({ + [`data-id="1"`]: DEFAULT_STYLE, + [`data-id="2"`]: DEFAULT_STYLE, + [`data-id="3"`]: SELECTED_STYLE, + [`data-id="4"`]: DEFAULT_STYLE, + [`data-id="5"`]: DEFAULT_STYLE, + [`data-id="6"`]: DEFAULT_STYLE, + }), + // selects the range from up to down using click and shift + clickAction('[data-id="5"]', 2000, { shiftKey: true }), + expectElementsStyle({ + [`data-id="3"`]: SELECTED_STYLE, + [`data-id="4"`]: SELECTED_STYLE, + [`data-id="5"`]: SELECTED_STYLE, + }), + // validates the anchor functionality + // selects the range from down to up using click and shift. + clickAction('[data-id="1"]', 2000, { shiftKey: true }), + expectElementsStyle({ + [`data-id="1"`]: SELECTED_STYLE, + [`data-id="2"`]: SELECTED_STYLE, + [`data-id="3"`]: SELECTED_STYLE, + }), + // removes already selected element with shift + clickAction('[data-id="2"]', 2000, { shiftKey: true }), + expectElementsStyle({ + [`data-id="1"`]: DEFAULT_STYLE, + [`data-id="2"`]: SELECTED_STYLE, + [`data-id="3"`]: SELECTED_STYLE, + }), + // validates multi-selection using click+ctrl key + clickAction('[data-id="1"]'), + clickAction('[data-id="3"]', 2000, { ctrlKey: true }), + clickAction('[data-id="5"]', 2000, { ctrlKey: true }), + expectElementsStyle({ + [`data-id="1"`]: SELECTED_STYLE, + [`data-id="2"`]: SELECTED_STYLE, + [`data-id="3"`]: DEFAULT_STYLE, + [`data-id="4"`]: SELECTED_STYLE, + [`data-id="5"`]: DEFAULT_STYLE, + [`data-id="6"`]: SELECTED_STYLE, + }), + // validates clearing selected item using click+ctrl key + clickAction('[data-id="1"]', 2000, { ctrlKey: true }), + expectElementStyle('[data-id="1"]', FOCUSED_STYLE), + ], + }), + ], + environmentProps: { + windowWidth: 600, + windowHeight: 400, + }, +}); diff --git a/packages/components/src/tree/boards/tree-multi-selection.tsx b/packages/components/src/tree/boards/tree-multi-selection.tsx deleted file mode 100644 index 58d1d70c..00000000 --- a/packages/components/src/tree/boards/tree-multi-selection.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { createBoard } from '@wixc3/react-board'; -import React, { useRef, useState } from 'react'; -import { Tree } from '../tree.js'; -import { TreeItemData } from '../../board-assets/index.js'; -import { TreeItemRenderer } from '../../board-assets/tree-items/tree-item-renderer.js'; - -const data: TreeItemData = { - id: '1', - title: 'item 1', - children: [ - { - id: '2', - title: 'item 2', - children: [ - { - id: '2.1', - title: 'item 2.1', - children: [ - { id: '2.1.1', title: 'item 2.1.1' }, - { id: '2.1.2', title: 'item 2.1.2' }, - { id: '2.1.3', title: 'item 2.1.3' }, - ], - }, - ], - }, - { id: '3', title: 'item 3' }, - { id: '4', title: 'item 4' }, - { id: '5', title: 'item 5' }, - { id: '6', title: 'item 6' }, - ], -}; - -export default createBoard({ - name: 'Tree multi-selection', - Board: () => { - const openItemsControl = useState([]); - const scrollRef = useRef(null); - return ( - - data={data} - getId={(it) => it.id} - ItemRenderer={TreeItemRenderer} - getChildren={(it) => it.children || []} - openItemsControls={openItemsControl} - overlay={{ el: () => null, props: {} }} - listRoot={{ - props: { - ref: scrollRef, - id: 'LIST', - style: { outline: 'none', width: '12rem' }, - }, - }} - eventRoots={[scrollRef]} - /> - ); - }, - // plugins: [ - // scenarioPlugin.use({ - // title: 'tree focus test', - // events: [ - // clickAction('[data-id="1"]'), - // keyDownAction('#LIST', KeyCodes.ArrowRight, 39), - // expectElement('[data-id="2"]'), - // keyDownAction('[data-id="1"]', KeyCodes.ArrowDown, 40), - // expectElementStyle('[data-id="2"]', { color: 'rgb(0, 0, 255)' }), //blue (focused) - // keyDownAction('[data-id="2"]', KeyCodes.Space, 32), - // expectElementStyle('[data-id="2"]', { textDecorationLine: 'underline' }), - // keyDownAction('[data-id="2"]', KeyCodes.Home, 36), - // expectElementStyle('[data-id="1"]', { color: 'rgb(0, 0, 255)' }), //blue (focused) - // keyDownAction('[data-id="1"]', KeyCodes.End, 35), - // expectElementStyle('[data-id="6"]', { color: 'rgb(0, 0, 255)' }), //blue (focused) - // keyDownAction('[data-id="6"]', KeyCodes.Enter, 13), - // expectElementStyle('[data-id="6"]', { textDecorationLine: 'underline' }), //selected - // ], - // }), - // ], - environmentProps: { - windowWidth: 600, - windowHeight: 400, - }, -});