From 016ba49a906ab4860c7779a330ae76d4a2fa8ca6 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 22 Jun 2020 15:25:26 +0800 Subject: [PATCH 1/5] ctrl + click behavior --- .../constants/ScreenReaderMessage.ts | 1 + .../hooks/useEditorEventApi.ts | 15 +++++++++++++++ .../renderers/NodeWrapper.tsx | 3 +++ .../constants/NodeEventTypes.ts | 1 + 4 files changed, 20 insertions(+) diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts index ea96d76b2c..34c0d5a503 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts @@ -4,6 +4,7 @@ export enum ScreenReaderMessage { EventFocused = 'Event focused', ActionFocused = 'Action focused', + ActionUnfocused = 'Action unfocused', DialogOpened = 'Dialog opened', ActionDeleted = 'Action deleted', ActionsDeleted = 'Actions deleted', diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts index e53a4220a3..258cf1ea9a 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts @@ -80,6 +80,21 @@ export const useEditorEventApi = ( announce(ScreenReaderMessage.ActionFocused); }; break; + case NodeEventTypes.CtrlClick: + handler = (e: { id: string }) => { + // Toggle the selection state of clicked id + const alreadySelected = selectedIds.some((x) => x === e.id); + if (alreadySelected) { + const shrinkedSelection = selectedIds.filter((x) => x !== e.id); + setSelectedIds(shrinkedSelection); + announce(ScreenReaderMessage.ActionUnfocused); + } else { + const expandedSelection = [...selectedIds, e.id]; + setSelectedIds(expandedSelection); + announce(ScreenReaderMessage.ActionFocused); + } + }; + break; case NodeEventTypes.FocusEvent: handler = (eventData) => { onFocusEvent(eventData); diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx index bd0c0fde20..bc08a7cef1 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx @@ -75,6 +75,9 @@ export const ActionNodeWrapper: FC = ({ id, tab, data, onEvent aria-label={generateSDKTitle(data, '', tab)} onClick={(e) => { e.stopPropagation(); + if (e.ctrlKey) { + return onEvent(NodeEventTypes.CtrlClick, { id, tab }); + } onEvent(NodeEventTypes.Focus, { id, tab }); }} > diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts index f33407749d..bdf4b1af6b 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts @@ -3,6 +3,7 @@ export enum NodeEventTypes { Focus = 'event.view.focus', + CtrlClick = 'event.view.ctrl-click', FocusEvent = 'event.view.focus-event', MoveCursor = 'event.view.move-cursor', OpenDialog = 'event.nav.opendialog', From f775171cd0acca878a74563db55c03e5064dd198 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 22 Jun 2020 15:47:42 +0800 Subject: [PATCH 2/5] enhance the focus behavior --- .../adaptive-flow-editor/hooks/useEditorEventApi.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts index 258cf1ea9a..d60e4eed36 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts @@ -81,16 +81,24 @@ export const useEditorEventApi = ( }; break; case NodeEventTypes.CtrlClick: - handler = (e: { id: string }) => { + handler = (e: { id: string; tab?: string }) => { + if (!focusedId && !selectedIds.length) { + return handleEditorEvent(NodeEventTypes.Focus, e); + } + // Toggle the selection state of clicked id const alreadySelected = selectedIds.some((x) => x === e.id); if (alreadySelected) { const shrinkedSelection = selectedIds.filter((x) => x !== e.id); setSelectedIds(shrinkedSelection); + if (focusedId === e.id) { + onFocusSteps([shrinkedSelection[0] || '']); + } announce(ScreenReaderMessage.ActionUnfocused); } else { const expandedSelection = [...selectedIds, e.id]; setSelectedIds(expandedSelection); + onFocusSteps([e.id], e.tab); announce(ScreenReaderMessage.ActionFocused); } }; From a925dec3da3dcd5698826c84accb5ff1f7db2d43 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 22 Jun 2020 16:38:01 +0800 Subject: [PATCH 3/5] range selection e2e --- .../constants/ScreenReaderMessage.ts | 1 + .../hooks/useEditorEventApi.ts | 17 +++++++++++++++++ .../renderers/NodeWrapper.tsx | 13 +++++++++++-- .../utils/calculateRangeSelection.ts | 6 ++++++ .../constants/NodeEventTypes.ts | 1 + 5 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/utils/calculateRangeSelection.ts diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts index 34c0d5a503..87fd4bf7d9 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts @@ -5,6 +5,7 @@ export enum ScreenReaderMessage { EventFocused = 'Event focused', ActionFocused = 'Action focused', ActionUnfocused = 'Action unfocused', + RangeSelection = 'Range Selection', DialogOpened = 'Dialog opened', ActionDeleted = 'Action deleted', ActionsDeleted = 'Actions deleted', diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts index d60e4eed36..69c0f676b1 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts @@ -16,6 +16,7 @@ import { moveCursor } from '../utils/cursorTracker'; import { AttrNames } from '../constants/ElementAttributes'; import { NodeRendererContextValue } from '../contexts/NodeRendererContext'; import { SelectionContextData } from '../contexts/SelectionContext'; +import { calculateRangeSelection } from '../utils/calculateRangeSelection'; export const useEditorEventApi = ( state: { path: string; data: any; nodeContext: NodeRendererContextValue; selectionContext: SelectionContextData }, @@ -103,6 +104,22 @@ export const useEditorEventApi = ( } }; break; + case NodeEventTypes.ShiftClick: + handler = (e: { id: string; tab?: string }) => { + if (!focusedId && !selectedIds.length) { + return handleEditorEvent(NodeEventTypes.Focus, e); + } + + if (!focusedId) { + return handleEditorEvent(NodeEventTypes.CtrlClick, e); + } + + // Range selection from 'focusedId' to Shift-Clicked id. + const newSelectedIds = calculateRangeSelection(focusedId, e.id, data); + setSelectedIds(newSelectedIds); + announce(ScreenReaderMessage.RangeSelection); + }; + break; case NodeEventTypes.FocusEvent: handler = (eventData) => { onFocusEvent(eventData); diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx index bc08a7cef1..d9cf6075ff 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx @@ -58,10 +58,13 @@ export const ActionNodeWrapper: FC = ({ id, tab, data, onEvent addCoachMarkRef({ action }); }, []); + // Set 'use-select' to none to disable browser's default + // text selection effect when pressing Shift + Click. return (
= ({ id, tab, data, onEvent aria-label={generateSDKTitle(data, '', tab)} onClick={(e) => { e.stopPropagation(); + e.preventDefault(); + + const payload = { id, tab }; if (e.ctrlKey) { - return onEvent(NodeEventTypes.CtrlClick, { id, tab }); + return onEvent(NodeEventTypes.CtrlClick, payload); + } + if (e.shiftKey) { + return onEvent(NodeEventTypes.ShiftClick, payload); } - onEvent(NodeEventTypes.Focus, { id, tab }); + onEvent(NodeEventTypes.Focus, payload); }} > {children} diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/utils/calculateRangeSelection.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/utils/calculateRangeSelection.ts new file mode 100644 index 0000000000..b5b77fb225 --- /dev/null +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/utils/calculateRangeSelection.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export const calculateRangeSelection = (fromId: string, toId: string, adaptiveJson: any): string[] => { + return [fromId, toId]; +}; diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts index bdf4b1af6b..795a295b70 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts @@ -4,6 +4,7 @@ export enum NodeEventTypes { Focus = 'event.view.focus', CtrlClick = 'event.view.ctrl-click', + ShiftClick = 'event.view.shift-click', FocusEvent = 'event.view.focus-event', MoveCursor = 'event.view.move-cursor', OpenDialog = 'event.nav.opendialog', From ecafe277985e8e8af0d1606293485872564a45b7 Mon Sep 17 00:00:00 2001 From: zeye Date: Mon, 22 Jun 2020 17:35:38 +0800 Subject: [PATCH 4/5] calculate accurate range selection --- .../adaptive-flow-editor/contexts/SelectionContext.ts | 2 ++ .../adaptive-flow-editor/hooks/useEditorEventApi.ts | 4 +++- .../adaptive-flow-editor/hooks/useSelectionEffect.ts | 2 ++ .../utils/calculateRangeSelection.ts | 10 ++++++++-- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/contexts/SelectionContext.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/contexts/SelectionContext.ts index 861ba45696..a7b576af26 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/contexts/SelectionContext.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/contexts/SelectionContext.ts @@ -7,6 +7,7 @@ import { SelectorElement } from '../utils/cursorTracker'; export interface SelectionContextData { getNodeIndex: (id: string) => number; + getSelectableIds: () => string[]; selectedIds: string[]; setSelectedIds: (ids: string[]) => any; selectableElements: SelectorElement[]; @@ -14,6 +15,7 @@ export interface SelectionContextData { export const SelectionContext = React.createContext({ getNodeIndex: (_: string): number => 0, + getSelectableIds: () => [], selectedIds: [] as string[], setSelectedIds: () => null, selectableElements: [], diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts index 69c0f676b1..de87dba0b5 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts @@ -114,8 +114,10 @@ export const useEditorEventApi = ( return handleEditorEvent(NodeEventTypes.CtrlClick, e); } + // Maintained by NodeIndexGenerator, `selectableIds` is in pre-order natively. + const selectableIds = selectionContext.getSelectableIds(); // Range selection from 'focusedId' to Shift-Clicked id. - const newSelectedIds = calculateRangeSelection(focusedId, e.id, data); + const newSelectedIds = calculateRangeSelection(focusedId, e.id, selectableIds); setSelectedIds(newSelectedIds); announce(ScreenReaderMessage.RangeSelection); }; diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useSelectionEffect.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useSelectionEffect.ts index a56e2c7196..613dd91169 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useSelectionEffect.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useSelectionEffect.ts @@ -17,6 +17,7 @@ export const useSelectionEffect = (state: { data: any; nodeContext: NodeRenderer const [selectedIds, setSelectedIds] = useState([]); const [selectableElements, setSelectableElements] = useState(querySelectableElements()); const nodeIndexGenerator = useRef(new NodeIndexGenerator()); + const getSelectableIds = () => nodeIndexGenerator.current.getItemList().map((x) => x.key as string); useEffect((): void => { // Notify container at every selection change. @@ -60,5 +61,6 @@ export const useSelectionEffect = (state: { data: any; nodeContext: NodeRenderer setSelectedIds, selectableElements, getNodeIndex, + getSelectableIds, }; }; diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/utils/calculateRangeSelection.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/utils/calculateRangeSelection.ts index b5b77fb225..dbbea65467 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/utils/calculateRangeSelection.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/utils/calculateRangeSelection.ts @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -export const calculateRangeSelection = (fromId: string, toId: string, adaptiveJson: any): string[] => { - return [fromId, toId]; +export const calculateRangeSelection = ( + focusedId: string, + clickedId: string, + orderedSelectableIds: string[] +): string[] => { + const range = [focusedId, clickedId].map((id) => orderedSelectableIds.findIndex((x) => x === id)); + const [fromIndex, toIndex] = range.sort(); + return orderedSelectableIds.slice(fromIndex, toIndex + 1); }; From 5a78d06cbfde834715d6a8016cd704a7a69b2acd Mon Sep 17 00:00:00 2001 From: Ze Ye Date: Tue, 23 Jun 2020 16:19:15 +0800 Subject: [PATCH 5/5] MacOS Meta + Click support --- .../src/adaptive-flow-editor/renderers/NodeWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx index d9cf6075ff..3255718da8 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/renderers/NodeWrapper.tsx @@ -81,7 +81,7 @@ export const ActionNodeWrapper: FC = ({ id, tab, data, onEvent e.preventDefault(); const payload = { id, tab }; - if (e.ctrlKey) { + if (e.ctrlKey || e.metaKey) { return onEvent(NodeEventTypes.CtrlClick, payload); } if (e.shiftKey) {