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`] = `
-
- Hello!
-
+
+ Hello!
+
+
`;
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`] = `
{
expect(preventDefault).not.toBeCalled();
});
- test('dragstart sets dragging in the context', async () => {
+ test('dragstart sets dragging in the context and calls it with proper params', async () => {
const setDragging = jest.fn();
const setA11yMessage = jest.fn();
const component = mount(
- Hello!
+ Hi!
);
@@ -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(
-
+
Hello!
@@ -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(
+
+
+ Hi!
+
+
+ );
+
+ 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: Hi! ,
+ 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 (
-
+
+
{React.cloneElement(children, {
'data-test-subj': dataTestSubj || 'lnsDragDrop',
className: classNames(children.props.className, classes, className),
@@ -426,7 +460,13 @@ const DropInner = memo(function DropInner(props: DropInnerProps) {
onDrop: drop,
draggable,
})}
- >
+ {ghost
+ ? React.cloneElement(ghost.children, {
+ className: classNames(ghost.children.props.className, 'lnsDragDrop_ghost'),
+ style: ghost.style,
+ })
+ : null}
+
);
});
diff --git a/x-pack/plugins/lens/public/drag_drop/providers.tsx b/x-pack/plugins/lens/public/drag_drop/providers.tsx
index 0478765328de4..1a056902a474d 100644
--- a/x-pack/plugins/lens/public/drag_drop/providers.tsx
+++ b/x-pack/plugins/lens/public/drag_drop/providers.tsx
@@ -25,6 +25,13 @@ export type DragDropIdentifier = Record & {
humanData: HumanData;
};
+export type DraggingIdentifier = DragDropIdentifier & {
+ ghost?: {
+ children: React.ReactElement;
+ style: React.CSSProperties;
+ };
+};
+
export type DropIdentifier = DragDropIdentifier & {
dropType: DropType;
onDrop: DropHandler;
@@ -41,7 +48,7 @@ export interface DragContextState {
/**
* The item being dragged or undefined.
*/
- dragging?: DragDropIdentifier;
+ dragging?: DraggingIdentifier;
/**
* keyboard mode
@@ -54,7 +61,7 @@ export interface DragContextState {
/**
* Set the item being dragged.
*/
- setDragging: (dragging?: DragDropIdentifier) => void;
+ setDragging: (dragging?: DraggingIdentifier) => void;
activeDropTarget?: DropTargets;
@@ -99,13 +106,13 @@ export interface ProviderProps {
* The item being dragged. If unspecified, the provider will
* behave as if it is the root provider.
*/
- dragging?: DragDropIdentifier;
+ dragging?: DraggingIdentifier;
/**
* Sets the item being dragged. If unspecified, the provider
* will behave as if it is the root provider.
*/
- setDragging: (dragging?: DragDropIdentifier) => void;
+ setDragging: (dragging?: DraggingIdentifier) => void;
activeDropTarget?: {
activeDropTarget?: DropIdentifier;
@@ -132,7 +139,7 @@ export interface ProviderProps {
* @param props
*/
export function RootDragDropProvider({ children }: { children: React.ReactNode }) {
- const [draggingState, setDraggingState] = useState<{ dragging?: DragDropIdentifier }>({
+ const [draggingState, setDraggingState] = useState<{ dragging?: DraggingIdentifier }>({
dragging: undefined,
});
const [keyboardModeState, setKeyboardModeState] = useState(false);
@@ -146,7 +153,7 @@ export function RootDragDropProvider({ children }: { children: React.ReactNode }
});
const setDragging = useMemo(
- () => (dragging?: DragDropIdentifier) => setDraggingState({ dragging }),
+ () => (dragging?: DraggingIdentifier) => setDraggingState({ dragging }),
[setDraggingState]
);
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx
index 6cc49ce5d0ce5..aece9188d7cf4 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx
@@ -447,6 +447,10 @@ describe('LayerPanel', () => {
indexPatternId: 'a',
id: '1',
humanData: { label: 'Label' },
+ ghost: {
+ children: Hello! ,
+ 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: Hello! ,
+ style: {},
+ },
};
const component = mountWithIntl(
@@ -554,6 +562,10 @@ describe('LayerPanel', () => {
groupId: 'a',
id: 'a',
humanData: { label: 'Label' },
+ ghost: {
+ children: Hello! ,
+ 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: Hello! ,
+ style: {},
+ },
};
const component = mountWithIntl(
@@ -659,6 +675,10 @@ describe('LayerPanel', () => {
groupId: 'a',
id: 'a',
humanData: { label: 'Label' },
+ ghost: {
+ children: Hello! ,
+ 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;
}