diff --git a/x-pack/plugins/lens/public/drag_drop/__snapshots__/drag_drop.test.tsx.snap b/x-pack/plugins/lens/public/drag_drop/__snapshots__/drag_drop.test.tsx.snap index b3b695b22ad71..e5594bb0bb769 100644 --- a/x-pack/plugins/lens/public/drag_drop/__snapshots__/drag_drop.test.tsx.snap +++ b/x-pack/plugins/lens/public/drag_drop/__snapshots__/drag_drop.test.tsx.snap @@ -1,12 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DragDrop defined dropType is reflected in the className 1`] = ` - + + `; exports[`DragDrop items that has dropType=undefined get special styling when another item is dragged 1`] = ` @@ -23,6 +27,7 @@ exports[`DragDrop items that has dropType=undefined get special styling when ano exports[`DragDrop renders if nothing is being dragged 1`] = `
+ ); @@ -96,7 +97,7 @@ describe('DragDrop', () => { jest.runAllTimers(); expect(dataTransfer.setData).toBeCalledWith('text', 'hello'); - expect(setDragging).toBeCalledWith(value); + expect(setDragging).toBeCalledWith({ ...value }); expect(setA11yMessage).toBeCalledWith('Lifted hello'); }); @@ -175,7 +176,7 @@ describe('DragDrop', () => { test('items that has dropType=undefined get special styling when another item is dragged', () => { const component = mount( - + @@ -198,7 +199,6 @@ describe('DragDrop', () => { const getAdditionalClassesOnEnter = jest.fn().mockReturnValue('additional'); const getAdditionalClassesOnDroppable = jest.fn().mockReturnValue('droppable'); const setA11yMessage = jest.fn(); - let activeDropTarget; const component = mount( { setDragging={() => { dragging = { id: '1', humanData: { label: 'label1' } }; }} - setActiveDropTarget={(val) => { - activeDropTarget = { activeDropTarget: val }; - }} - activeDropTarget={activeDropTarget} > { , style: {} } }, setActiveDropTarget, setA11yMessage, activeDropTarget: { @@ -376,21 +372,115 @@ describe('DragDrop', () => { .simulate('focus'); act(() => { keyboardHandler.simulate('keydown', { key: 'ArrowRight' }); - expect(setActiveDropTarget).toBeCalledWith({ - ...items[2].value, - onDrop, - dropType: items[2].dropType, - }); - keyboardHandler.simulate('keydown', { key: 'Enter' }); - expect(setA11yMessage).toBeCalledWith( - 'Selected label3 in group at position 1. Press space or enter to replace label3 with label1.' - ); - expect(setActiveDropTarget).toBeCalledWith(undefined); - expect(onDrop).toBeCalledWith( - { humanData: { label: 'label1', position: 1 }, id: '1' }, - 'move_compatible' - ); }); + expect(setActiveDropTarget).toBeCalledWith({ + ...items[2].value, + onDrop, + dropType: items[2].dropType, + }); + keyboardHandler.simulate('keydown', { key: 'Enter' }); + expect(setA11yMessage).toBeCalledWith( + 'Selected label3 in group at position 1. Press space or enter to replace label3 with label1.' + ); + expect(setActiveDropTarget).toBeCalledWith(undefined); + expect(onDrop).toBeCalledWith( + { humanData: { label: 'label1', position: 1 }, id: '1' }, + 'move_compatible' + ); + }); + + test('Keyboard navigation: dragstart sets dragging in the context and calls it with proper params', async () => { + const setDragging = jest.fn(); + + const setA11yMessage = jest.fn(); + const component = mount( + + + + + + ); + + const keyboardHandler = component + .find('[data-test-subj="lnsDragDrop-keyboardHandler"]') + .first() + .simulate('focus'); + + keyboardHandler.simulate('keydown', { key: 'Enter' }); + jest.runAllTimers(); + + expect(setDragging).toBeCalledWith({ + ...value, + ghost: { + children: , + style: { + height: 0, + width: 0, + }, + }, + }); + expect(setA11yMessage).toBeCalledWith('Lifted hello'); + }); + + test('Keyboard navigation: ActiveDropTarget gets ghost image', () => { + const onDrop = jest.fn(); + const setActiveDropTarget = jest.fn(); + const setA11yMessage = jest.fn(); + const items = [ + { + draggable: true, + value: { + id: '1', + humanData: { label: 'label1', position: 1 }, + }, + children: '1', + order: [2, 0, 0, 0], + }, + { + draggable: true, + dragType: 'move' as 'copy' | 'move', + + value: { + id: '2', + + humanData: { label: 'label2', position: 1 }, + }, + onDrop, + dropType: 'move_compatible' as DropType, + order: [2, 0, 1, 0], + }, + ]; + const component = mount( + Hello
, style: {} } }, + setActiveDropTarget, + setA11yMessage, + activeDropTarget: { + activeDropTarget: { ...items[1].value, onDrop, dropType: 'move_compatible' }, + dropTargetsByOrder: { + '2,0,1,0': { ...items[1].value, onDrop, dropType: 'move_compatible' }, + }, + }, + keyboardMode: true, + }} + > + {items.map((props) => ( + +
+ + ))} + + ); + + expect(component.find(DragDrop).at(1).find('.lnsDragDrop_ghost').text()).toEqual('Hello'); }); describe('reordering', () => { @@ -427,7 +517,7 @@ describe('DragDrop', () => { const registerDropTarget = jest.fn(); const baseContext = { dragging, - setDragging: (val?: DragDropIdentifier) => { + setDragging: (val?: DraggingIdentifier) => { dragging = val; }, keyboardMode, @@ -479,7 +569,11 @@ describe('DragDrop', () => { test(`Reorderable group with lifted element renders properly`, () => { const setA11yMessage = jest.fn(); const setDragging = jest.fn(); - const component = mountComponent({ dragging: items[0], setDragging, setA11yMessage }); + const component = mountComponent({ + dragging: { ...items[0] }, + setDragging, + setA11yMessage, + }); act(() => { component .find('[data-test-subj="lnsDragDrop"]') @@ -488,7 +582,7 @@ describe('DragDrop', () => { jest.runAllTimers(); }); - expect(setDragging).toBeCalledWith(items[0]); + expect(setDragging).toBeCalledWith({ ...items[0] }); expect(setA11yMessage).toBeCalledWith('Lifted label1'); expect( component @@ -498,7 +592,7 @@ describe('DragDrop', () => { }); test(`Reordered elements get extra styles to show the reorder effect when dragging`, () => { - const component = mountComponent({ dragging: items[0] }); + const component = mountComponent({ dragging: { ...items[0] } }); act(() => { component @@ -545,7 +639,11 @@ describe('DragDrop', () => { const setA11yMessage = jest.fn(); const setDragging = jest.fn(); - const component = mountComponent({ dragging: items[0], setDragging, setA11yMessage }); + const component = mountComponent({ + dragging: { ...items[0] }, + setDragging, + setA11yMessage, + }); component .find('[data-test-subj="lnsDragDrop-reorderableDropLayer"]') @@ -558,14 +656,14 @@ describe('DragDrop', () => { ); expect(preventDefault).toBeCalled(); expect(stopPropagation).toBeCalled(); - expect(onDrop).toBeCalledWith(items[0], 'reorder'); + expect(onDrop).toBeCalledWith({ ...items[0] }, 'reorder'); }); test(`Keyboard Navigation: User cannot move an element outside of the group`, () => { const setA11yMessage = jest.fn(); const setActiveDropTarget = jest.fn(); const component = mountComponent({ - dragging: items[0], + dragging: { ...items[0] }, keyboardMode: true, activeDropTarget: { activeDropTarget: undefined, @@ -594,7 +692,7 @@ describe('DragDrop', () => { }); test(`Keyboard navigation: user can drop element to an activeDropTarget`, () => { const component = mountComponent({ - dragging: items[0], + dragging: { ...items[0] }, activeDropTarget: { activeDropTarget: { ...items[2], dropType: 'reorder', onDrop }, dropTargetsByOrder: { @@ -621,7 +719,10 @@ describe('DragDrop', () => { test(`Keyboard Navigation: Doesn't call onDrop when movement is cancelled`, () => { const setA11yMessage = jest.fn(); const onDropHandler = jest.fn(); - const component = mountComponent({ dragging: items[0], setA11yMessage }, onDropHandler); + const component = mountComponent( + { dragging: { ...items[0] }, setA11yMessage }, + onDropHandler + ); const keyboardHandler = component.find('[data-test-subj="lnsDragDrop-keyboardHandler"]'); keyboardHandler.simulate('keydown', { key: 'Space' }); keyboardHandler.simulate('keydown', { key: 'Escape' }); @@ -640,7 +741,7 @@ describe('DragDrop', () => { test(`Keyboard Navigation: Reordered elements get extra styles to show the reorder effect`, () => { const setA11yMessage = jest.fn(); const component = mountComponent({ - dragging: items[0], + dragging: { ...items[0] }, keyboardMode: true, activeDropTarget: { activeDropTarget: undefined, @@ -704,7 +805,7 @@ describe('DragDrop', () => { '2,0,1,1': { ...items[1], onDrop, dropType: 'reorder' }, }, }} - dragging={items[0]} + dragging={{ ...items[0] }} setActiveDropTarget={setActiveDropTarget} setA11yMessage={setA11yMessage} > diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx index 07c1368e53456..4b25064320327 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx @@ -177,6 +177,7 @@ export const DragDrop = (props: BaseProps) => { ); const dropProps = { ...props, + keyboardMode, setKeyboardMode, dragging, setDragging, @@ -219,7 +220,10 @@ const DragInner = memo(function DragInner({ ariaDescribedBy, setA11yMessage, }: DragInnerProps) { - const dragStart = (e?: DroppableEvent | React.KeyboardEvent) => { + const dragStart = ( + e: DroppableEvent | React.KeyboardEvent, + keyboardModeOn?: boolean + ) => { // Setting stopPropgagation causes Chrome failures, so // we are manually checking if we've already handled this // in a nested child, and doing nothing if so... @@ -237,9 +241,21 @@ const DragInner = memo(function DragInner({ // dragStart event, so we drop a setTimeout to avoid that. const currentTarget = e?.currentTarget; + setTimeout(() => { - setDragging(value); + setDragging({ + ...value, + ghost: keyboardModeOn + ? { + children, + style: { width: currentTarget.offsetWidth, height: currentTarget.offsetHeight }, + } + : undefined, + }); setA11yMessage(announce.lifted(value.humanData)); + if (keyboardModeOn) { + setKeyboardMode(true); + } if (onDragStart) { onDragStart(currentTarget); } @@ -284,8 +300,19 @@ const DragInner = memo(function DragInner({ : announce.noTarget() ); }; + const shouldShowGhostImageInstead = + isDragging && + dragType === 'move' && + keyboardMode && + activeDropTarget?.activeDropTarget && + activeDropTarget?.activeDropTarget.dropType !== 'reorder'; return ( -
+
, + style: {}, + }, }; const component = mountWithIntl( @@ -463,7 +467,7 @@ describe('LayerPanel', () => { }) ); - component.find('[data-test-subj="lnsGroup"] DragDrop').first().simulate('drop'); + component.find('[data-test-subj="lnsGroup"] DragDrop .lnsDragDrop').first().simulate('drop'); expect(mockDatasource.onDrop).toHaveBeenCalledWith( expect.objectContaining({ @@ -497,6 +501,10 @@ describe('LayerPanel', () => { indexPatternId: 'a', id: '1', humanData: { label: 'Label' }, + ghost: { + children: , + style: {}, + }, }; const component = mountWithIntl( @@ -554,6 +562,10 @@ describe('LayerPanel', () => { groupId: 'a', id: 'a', humanData: { label: 'Label' }, + ghost: { + children: , + style: {}, + }, }; const component = mountWithIntl( @@ -571,7 +583,7 @@ describe('LayerPanel', () => { ); // Simulate drop on the pre-populated dimension - component.find('[data-test-subj="lnsGroupB"] DragDrop').at(0).simulate('drop'); + component.find('[data-test-subj="lnsGroupB"] DragDrop .lnsDragDrop').at(0).simulate('drop'); expect(mockDatasource.onDrop).toHaveBeenCalledWith( expect.objectContaining({ columnId: 'b', @@ -582,7 +594,7 @@ describe('LayerPanel', () => { ); // Simulate drop on the empty dimension - component.find('[data-test-subj="lnsGroupB"] DragDrop').at(1).simulate('drop'); + component.find('[data-test-subj="lnsGroupB"] DragDrop .lnsDragDrop').at(1).simulate('drop'); expect(mockDatasource.onDrop).toHaveBeenCalledWith( expect.objectContaining({ columnId: 'newid', @@ -613,6 +625,10 @@ describe('LayerPanel', () => { groupId: 'a', id: 'a', humanData: { label: 'Label' }, + ghost: { + children: , + style: {}, + }, }; const component = mountWithIntl( @@ -659,6 +675,10 @@ describe('LayerPanel', () => { groupId: 'a', id: 'a', humanData: { label: 'Label' }, + ghost: { + children: , + style: {}, + }, }; const component = mountWithIntl( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index da1d7f6eacd02..108e4aa84418f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -1323,7 +1323,10 @@ describe('editor_frame', () => { getDatasourceSuggestionsForVisualizeField: () => [generateSuggestion()], renderDataPanel: (_element, { dragDropContext: { setDragging, dragging } }) => { if (!dragging || dragging.id !== 'draggedField') { - setDragging({ id: 'draggedField', humanData: { label: 'draggedField' } }); + setDragging({ + id: 'draggedField', + humanData: { label: 'draggedField' }, + }); } }, }, @@ -1425,7 +1428,10 @@ describe('editor_frame', () => { getDatasourceSuggestionsForVisualizeField: () => [generateSuggestion()], renderDataPanel: (_element, { dragDropContext: { setDragging, dragging } }) => { if (!dragging || dragging.id !== 'draggedField') { - setDragging({ id: 'draggedField', humanData: { label: '1' } }); + setDragging({ + id: 'draggedField', + humanData: { label: '1' }, + }); } }, }, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss index ae9294c474b42..0ace88b3d3ab7 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss @@ -2,7 +2,6 @@ .lnsWorkspacePanelWrapper { @include euiScrollBar; - overflow: hidden; // Override panel size padding padding: 0 !important; // sass-lint:disable-line no-important margin-bottom: $euiSize; @@ -10,6 +9,7 @@ flex-direction: column; position: relative; // For positioning the dnd overlay min-height: $euiSizeXXL * 10; + overflow: visible; .lnsWorkspacePanelWrapper__pageContentBody { @include euiScrollBar; @@ -17,7 +17,6 @@ display: flex; align-items: stretch; justify-content: stretch; - overflow: auto; > * { flex: 1 1 100%; @@ -34,6 +33,8 @@ // Color the whole panel instead background-color: transparent !important; // sass-lint:disable-line no-important border: none !important; // sass-lint:disable-line no-important + width: 100%; + height: 100%; } .lnsExpressionRenderer { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts index b374be98748f0..1f0381d92ce64 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { IndexPatternDimensionEditorProps } from './dimension_panel'; import { onDrop, getDropTypes } from './droppable'; @@ -187,7 +186,11 @@ describe('IndexPatternDimensionEditorPanel', () => { groupId, dragDropContext: { ...dragDropContext, - dragging: { name: 'bar', id: 'bar', humanData: { label: 'Label' } }, + dragging: { + name: 'bar', + id: 'bar', + humanData: { label: 'Label' }, + }, }, }) ).toBe(undefined); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss index 8a6e10c8be6e4..19f5b91975202 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss @@ -29,6 +29,13 @@ } } +.kbnFieldButton.lnsDragDrop_ghost { + .lnsFieldItem__infoIcon { + visibility: hidden; + opacity: 0; + } +} + .kbnFieldButton__name { transition: background-color $euiAnimSpeedFast ease-in-out; }