mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Lens] Refactor drag and drop (#161257)
## Summary When I created drag and drop for Lens, the API I went for was not the most readable one. It was designed this way because I wanted to gain some performance, but it was very hard to maintain the performance gain with a lot of changes in the drag and drop area because all the pieces of the code needed to memoized in a tricky way and it wasn't communicated well. In the end it works even without these tricks so I decided to simplify it in this PR. The main changes include: 1. Instead of multiple `useState` per parameter, we keep all the state in reducer both for `ReorderProvider` and `RootDragDropProvider`. Thanks to that we get multiple improvements: 2. The code in `DragDrop` component becomes more descriptive as we don't run multiple state updates when user executes an action but one state update describing what actually happens (eg. `dispatchDnd({type: 'selectDropTarget' ....})`. The internal logic of the update lives in the reducer. 3. We don't have to pass `trackUiCounterEvents` as another prop to `DragDrop` and run it wherever we need - instead we pass it as a middleware to the context and run before dispatching (and it's very easy to add more middlewares if we need extra integrations at some point!) 4. We also run a11y announcements as a middleware instead of inside `DragDrop` component 5. The `ChildDragDropProvider` props look much cleaner: before: ``` <ChildDragDropProvider keyboardMode={keyboardModeState} setKeyboardMode={setKeyboardModeState} dragging={draggingState.dragging} setA11yMessage={setA11yMessage} setDragging={setDragging} activeDropTarget={activeDropTargetState} setActiveDropTarget={setActiveDropTarget} registerDropTarget={registerDropTarget} dropTargetsByOrder={dropTargetsByOrderState} dataTestSubjPrefix={dataTestSubj} onTrackUICounterEvent={onTrackUICounterEvent} > {children} </ChildDragDropProvider> ``` after: ``` <ChildDragDropProvider value={[state, dispatch]}>{children}</ChildDragDropProvider> ``` 6. Created custom hook `useDragDropContext` instead of using `useContext(DragContext)` and making DragContext private. This way we will avoid potential problems with using context outside of root. 7. Bonus thing - if we ever decide to move to redux, the structure is there already What I am still not happy with is that the tests are very domain-dependant instead of user-driven - instead of checking the store actions, I should check the interface from the user perspective. I will try to work on it once I find some time between more important tasks though.
This commit is contained in:
parent
203c9b04b6
commit
91a0d2f454
33 changed files with 1208 additions and 1109 deletions
|
@ -14,8 +14,8 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useContext, useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { DragContext, DragDrop, DropOverlayWrapper, DropType } from '@kbn/dom-drag-drop';
|
import { DragDrop, DropOverlayWrapper, DropType, useDragDropContext } from '@kbn/dom-drag-drop';
|
||||||
import { EuiEmptyPrompt, EuiPanel } from '@elastic/eui';
|
import { EuiEmptyPrompt, EuiPanel } from '@elastic/eui';
|
||||||
|
|
||||||
const DROP_PROPS = {
|
const DROP_PROPS = {
|
||||||
|
@ -34,8 +34,8 @@ export interface ExampleDropZoneProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExampleDropZone: React.FC<ExampleDropZoneProps> = ({ onDropField }) => {
|
export const ExampleDropZone: React.FC<ExampleDropZoneProps> = ({ onDropField }) => {
|
||||||
const dragDropContext = useContext(DragContext);
|
const [{ dragging }] = useDragDropContext();
|
||||||
const draggingFieldName = dragDropContext.dragging?.id;
|
const draggingFieldName = dragging?.id;
|
||||||
|
|
||||||
const onDroppingField = useMemo(() => {
|
const onDroppingField = useMemo(() => {
|
||||||
if (!draggingFieldName) {
|
if (!draggingFieldName) {
|
||||||
|
|
|
@ -14,10 +14,10 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useContext, useMemo, useRef } from 'react';
|
import React, { useCallback, useMemo, useRef } from 'react';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||||
import { generateFilters } from '@kbn/data-plugin/public';
|
import { generateFilters } from '@kbn/data-plugin/public';
|
||||||
import { ChildDragDropProvider, DragContext } from '@kbn/dom-drag-drop';
|
import { ChildDragDropProvider, useDragDropContext } from '@kbn/dom-drag-drop';
|
||||||
import {
|
import {
|
||||||
UnifiedFieldListSidebarContainer,
|
UnifiedFieldListSidebarContainer,
|
||||||
type UnifiedFieldListSidebarContainerProps,
|
type UnifiedFieldListSidebarContainerProps,
|
||||||
|
@ -54,7 +54,7 @@ export const FieldListSidebar: React.FC<FieldListSidebarProps> = ({
|
||||||
onAddFieldToWorkspace,
|
onAddFieldToWorkspace,
|
||||||
onRemoveFieldFromWorkspace,
|
onRemoveFieldFromWorkspace,
|
||||||
}) => {
|
}) => {
|
||||||
const dragDropContext = useContext(DragContext);
|
const dragDropContext = useDragDropContext();
|
||||||
const unifiedFieldListContainerRef = useRef<UnifiedFieldListSidebarContainerApi>(null);
|
const unifiedFieldListContainerRef = useRef<UnifiedFieldListSidebarContainerApi>(null);
|
||||||
const filterManager = services.data?.query?.filterManager;
|
const filterManager = services.data?.query?.filterManager;
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ export const FieldListSidebar: React.FC<FieldListSidebarProps> = ({
|
||||||
}, [unifiedFieldListContainerRef]);
|
}, [unifiedFieldListContainerRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChildDragDropProvider {...dragDropContext}>
|
<ChildDragDropProvider value={dragDropContext}>
|
||||||
<UnifiedFieldListSidebarContainer
|
<UnifiedFieldListSidebarContainer
|
||||||
ref={unifiedFieldListContainerRef}
|
ref={unifiedFieldListContainerRef}
|
||||||
variant="responsive"
|
variant="responsive"
|
||||||
|
|
|
@ -9,7 +9,7 @@ We aren't using EUI or another library, due to the fact that Lens visualizations
|
||||||
First, place a RootDragDropProvider at the root of your application.
|
First, place a RootDragDropProvider at the root of your application.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
<RootDragDropProvider onTrackUICounterEvent={...}>
|
<RootDragDropProvider customMiddleware={...}>
|
||||||
... your app here ...
|
... your app here ...
|
||||||
</RootDragDropProvider>
|
</RootDragDropProvider>
|
||||||
```
|
```
|
||||||
|
@ -17,13 +17,13 @@ First, place a RootDragDropProvider at the root of your application.
|
||||||
If you have a child React application (e.g. a visualization), you will need to pass the drag / drop context down into it. This can be obtained like so:
|
If you have a child React application (e.g. a visualization), you will need to pass the drag / drop context down into it. This can be obtained like so:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const context = useContext(DragContext);
|
const context = useDragDropContext();
|
||||||
```
|
```
|
||||||
|
|
||||||
In your child application, place a `ChildDragDropProvider` at the root of that, and spread the context into it:
|
In your child application, place a `ChildDragDropProvider` at the root of that, and assign the context into it:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
<ChildDragDropProvider {...context}>... your child app here ...</ChildDragDropProvider>
|
<ChildDragDropProvider value={context}>... your child app here ...</ChildDragDropProvider>
|
||||||
```
|
```
|
||||||
|
|
||||||
This enables your child application to share the same drag / drop context as the root application.
|
This enables your child application to share the same drag / drop context as the root application.
|
||||||
|
@ -49,7 +49,7 @@ To enable dragging an item, use `DragDrop` with both a `draggable` and a `value`
|
||||||
To enable dropping, use `DragDrop` with both a `dropTypes` attribute that should be an array with at least one value and an `onDrop` handler attribute. `dropType` should only be truthy if is an item being dragged, and if a drop of the dragged item is supported.
|
To enable dropping, use `DragDrop` with both a `dropTypes` attribute that should be an array with at least one value and an `onDrop` handler attribute. `dropType` should only be truthy if is an item being dragged, and if a drop of the dragged item is supported.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { dragging } = useContext(DragContext);
|
const [ dndState ] = useDragDropContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragDrop
|
<DragDrop
|
||||||
|
@ -69,13 +69,13 @@ return (
|
||||||
To create a reordering group, surround the elements from the same group with a `ReorderProvider`:
|
To create a reordering group, surround the elements from the same group with a `ReorderProvider`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
<ReorderProvider id="groupId">... elements from one group here ...</ReorderProvider>
|
<ReorderProvider>... elements from one group here ...</ReorderProvider>
|
||||||
```
|
```
|
||||||
|
|
||||||
The children `DragDrop` components must have props defined as in the example:
|
The children `DragDrop` components must have props defined as in the example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
<ReorderProvider id="groupId">
|
<ReorderProvider>
|
||||||
<div className="field-list">
|
<div className="field-list">
|
||||||
{fields.map((f) => (
|
{fields.map((f) => (
|
||||||
<DragDrop
|
<DragDrop
|
||||||
|
|
|
@ -8,16 +8,18 @@
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type DragDropIdentifier,
|
type DragDropIdentifier,
|
||||||
|
type DragContextValue,
|
||||||
type DragContextState,
|
type DragContextState,
|
||||||
type DropType,
|
type DropType,
|
||||||
type DraggingIdentifier,
|
type DraggingIdentifier,
|
||||||
|
type DragDropAction,
|
||||||
|
type DropOverlayWrapperProps,
|
||||||
DragDrop,
|
DragDrop,
|
||||||
DragContext,
|
useDragDropContext,
|
||||||
RootDragDropProvider,
|
RootDragDropProvider,
|
||||||
ChildDragDropProvider,
|
ChildDragDropProvider,
|
||||||
ReorderProvider,
|
ReorderProvider,
|
||||||
DropOverlayWrapper,
|
DropOverlayWrapper,
|
||||||
type DropOverlayWrapperProps,
|
|
||||||
} from './src';
|
} from './src';
|
||||||
|
|
||||||
export { DropTargetSwapDuplicateCombine } from './src/drop_targets';
|
export { DropTargetSwapDuplicateCombine } from './src/drop_targets';
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,14 +14,14 @@ import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect';
|
||||||
import {
|
import {
|
||||||
DragDropIdentifier,
|
DragDropIdentifier,
|
||||||
DropIdentifier,
|
DropIdentifier,
|
||||||
DragContext,
|
|
||||||
DragContextState,
|
|
||||||
nextValidDropTarget,
|
nextValidDropTarget,
|
||||||
ReorderContext,
|
ReorderContext,
|
||||||
ReorderState,
|
|
||||||
DropHandler,
|
DropHandler,
|
||||||
announce,
|
|
||||||
Ghost,
|
Ghost,
|
||||||
|
RegisteredDropTargets,
|
||||||
|
DragDropAction,
|
||||||
|
DragContextState,
|
||||||
|
useDragDropContext,
|
||||||
} from './providers';
|
} from './providers';
|
||||||
import { DropType } from './types';
|
import { DropType } from './types';
|
||||||
import { REORDER_ITEM_MARGIN } from './constants';
|
import { REORDER_ITEM_MARGIN } from './constants';
|
||||||
|
@ -63,11 +63,6 @@ interface BaseProps {
|
||||||
*/
|
*/
|
||||||
value: DragDropIdentifier;
|
value: DragDropIdentifier;
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional comparison function to check whether a value is the dragged one
|
|
||||||
*/
|
|
||||||
isValueEqual?: (value1: unknown, value2: unknown) => boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The React element which will be passed the draggable handlers
|
* The React element which will be passed the draggable handlers
|
||||||
*/
|
*/
|
||||||
|
@ -125,17 +120,13 @@ interface BaseProps {
|
||||||
* The props for a draggable instance of that component.
|
* The props for a draggable instance of that component.
|
||||||
*/
|
*/
|
||||||
interface DragInnerProps extends BaseProps {
|
interface DragInnerProps extends BaseProps {
|
||||||
setKeyboardMode: DragContextState['setKeyboardMode'];
|
dndDispatch: React.Dispatch<DragDropAction>;
|
||||||
setDragging: DragContextState['setDragging'];
|
dataTestSubjPrefix?: string;
|
||||||
setActiveDropTarget: DragContextState['setActiveDropTarget'];
|
|
||||||
setA11yMessage: DragContextState['setA11yMessage'];
|
|
||||||
activeDraggingProps?: {
|
activeDraggingProps?: {
|
||||||
keyboardMode: DragContextState['keyboardMode'];
|
keyboardMode: boolean;
|
||||||
activeDropTarget: DragContextState['activeDropTarget'];
|
activeDropTarget?: DropIdentifier;
|
||||||
dropTargetsByOrder: DragContextState['dropTargetsByOrder'];
|
dropTargetsByOrder: RegisteredDropTargets;
|
||||||
};
|
};
|
||||||
dataTestSubjPrefix: DragContextState['dataTestSubjPrefix'];
|
|
||||||
onTrackUICounterEvent: DragContextState['onTrackUICounterEvent'] | undefined;
|
|
||||||
extraKeyboardHandler?: (e: KeyboardEvent<HTMLButtonElement>) => void;
|
extraKeyboardHandler?: (e: KeyboardEvent<HTMLButtonElement>) => void;
|
||||||
ariaDescribedBy?: string;
|
ariaDescribedBy?: string;
|
||||||
}
|
}
|
||||||
|
@ -144,16 +135,8 @@ interface DragInnerProps extends BaseProps {
|
||||||
* The props for a non-draggable instance of that component.
|
* The props for a non-draggable instance of that component.
|
||||||
*/
|
*/
|
||||||
interface DropsInnerProps extends BaseProps {
|
interface DropsInnerProps extends BaseProps {
|
||||||
dragging: DragContextState['dragging'];
|
dndState: DragContextState;
|
||||||
keyboardMode: DragContextState['keyboardMode'];
|
dndDispatch: React.Dispatch<DragDropAction>;
|
||||||
setKeyboardMode: DragContextState['setKeyboardMode'];
|
|
||||||
setDragging: DragContextState['setDragging'];
|
|
||||||
setActiveDropTarget: DragContextState['setActiveDropTarget'];
|
|
||||||
setA11yMessage: DragContextState['setA11yMessage'];
|
|
||||||
registerDropTarget: DragContextState['registerDropTarget'];
|
|
||||||
activeDropTarget: DragContextState['activeDropTarget'];
|
|
||||||
dataTestSubjPrefix: DragContextState['dataTestSubjPrefix'];
|
|
||||||
onTrackUICounterEvent: DragContextState['onTrackUICounterEvent'] | undefined;
|
|
||||||
isNotDroppable: boolean;
|
isNotDroppable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,19 +148,9 @@ const REORDER_OFFSET = REORDER_ITEM_MARGIN / 2;
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
export const DragDrop = (props: BaseProps) => {
|
export const DragDrop = (props: BaseProps) => {
|
||||||
const {
|
const [dndState, dndDispatch] = useDragDropContext();
|
||||||
dragging,
|
|
||||||
setDragging,
|
const { dragging, dropTargetsByOrder } = dndState;
|
||||||
keyboardMode,
|
|
||||||
registerDropTarget,
|
|
||||||
dropTargetsByOrder,
|
|
||||||
setKeyboardMode,
|
|
||||||
activeDropTarget,
|
|
||||||
setActiveDropTarget,
|
|
||||||
setA11yMessage,
|
|
||||||
dataTestSubjPrefix,
|
|
||||||
onTrackUICounterEvent,
|
|
||||||
} = useContext(DragContext);
|
|
||||||
|
|
||||||
if (props.isDisabled) {
|
if (props.isDisabled) {
|
||||||
return props.children;
|
return props.children;
|
||||||
|
@ -188,8 +161,8 @@ export const DragDrop = (props: BaseProps) => {
|
||||||
|
|
||||||
const activeDraggingProps = isDragging
|
const activeDraggingProps = isDragging
|
||||||
? {
|
? {
|
||||||
keyboardMode,
|
keyboardMode: dndState.keyboardMode,
|
||||||
activeDropTarget,
|
activeDropTarget: dndState.activeDropTarget,
|
||||||
dropTargetsByOrder,
|
dropTargetsByOrder,
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
@ -198,12 +171,8 @@ export const DragDrop = (props: BaseProps) => {
|
||||||
const dragProps = {
|
const dragProps = {
|
||||||
...props,
|
...props,
|
||||||
activeDraggingProps,
|
activeDraggingProps,
|
||||||
setKeyboardMode,
|
dataTestSubjPrefix: dndState.dataTestSubjPrefix,
|
||||||
setDragging,
|
dndDispatch,
|
||||||
setActiveDropTarget,
|
|
||||||
setA11yMessage,
|
|
||||||
dataTestSubjPrefix,
|
|
||||||
onTrackUICounterEvent,
|
|
||||||
};
|
};
|
||||||
if (reorderableGroup && reorderableGroup.length > 1) {
|
if (reorderableGroup && reorderableGroup.length > 1) {
|
||||||
return <ReorderableDrag {...dragProps} reorderableGroup={reorderableGroup} />;
|
return <ReorderableDrag {...dragProps} reorderableGroup={reorderableGroup} />;
|
||||||
|
@ -214,16 +183,8 @@ export const DragDrop = (props: BaseProps) => {
|
||||||
|
|
||||||
const dropProps = {
|
const dropProps = {
|
||||||
...props,
|
...props,
|
||||||
keyboardMode,
|
dndState,
|
||||||
setKeyboardMode,
|
dndDispatch,
|
||||||
dragging,
|
|
||||||
setDragging,
|
|
||||||
activeDropTarget,
|
|
||||||
setActiveDropTarget,
|
|
||||||
registerDropTarget,
|
|
||||||
setA11yMessage,
|
|
||||||
dataTestSubjPrefix,
|
|
||||||
onTrackUICounterEvent,
|
|
||||||
isNotDroppable:
|
isNotDroppable:
|
||||||
// If the configuration has provided a droppable flag, but this particular item is not
|
// If the configuration has provided a droppable flag, but this particular item is not
|
||||||
// droppable, then it should be less prominent. Ignores items that are both
|
// droppable, then it should be less prominent. Ignores items that are both
|
||||||
|
@ -253,39 +214,35 @@ const DragInner = memo(function DragInner({
|
||||||
className,
|
className,
|
||||||
value,
|
value,
|
||||||
children,
|
children,
|
||||||
setDragging,
|
dndDispatch,
|
||||||
setKeyboardMode,
|
|
||||||
setActiveDropTarget,
|
|
||||||
order,
|
order,
|
||||||
activeDraggingProps,
|
activeDraggingProps,
|
||||||
|
dataTestSubjPrefix,
|
||||||
dragType,
|
dragType,
|
||||||
onDragStart,
|
onDragStart,
|
||||||
onDragEnd,
|
onDragEnd,
|
||||||
extraKeyboardHandler,
|
extraKeyboardHandler,
|
||||||
ariaDescribedBy,
|
ariaDescribedBy,
|
||||||
setA11yMessage,
|
|
||||||
dataTestSubjPrefix,
|
|
||||||
onTrackUICounterEvent,
|
|
||||||
}: DragInnerProps) {
|
}: DragInnerProps) {
|
||||||
const keyboardMode = activeDraggingProps?.keyboardMode;
|
const { keyboardMode, activeDropTarget, dropTargetsByOrder } = activeDraggingProps || {};
|
||||||
const activeDropTarget = activeDraggingProps?.activeDropTarget;
|
|
||||||
const dropTargetsByOrder = activeDraggingProps?.dropTargetsByOrder;
|
|
||||||
|
|
||||||
const setTarget = useCallback(
|
const setTarget = useCallback(
|
||||||
(target?: DropIdentifier, announceModifierKeys = false) => {
|
(target?: DropIdentifier) => {
|
||||||
setActiveDropTarget(target);
|
if (!target) {
|
||||||
setA11yMessage(
|
dndDispatch({
|
||||||
target
|
type: 'leaveDropTarget',
|
||||||
? announce.selectedTarget(
|
});
|
||||||
value.humanData,
|
} else {
|
||||||
target?.humanData,
|
dndDispatch({
|
||||||
target?.dropType,
|
type: 'selectDropTarget',
|
||||||
announceModifierKeys
|
payload: {
|
||||||
)
|
dropTarget: target,
|
||||||
: announce.noTarget()
|
dragging: value,
|
||||||
);
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[setActiveDropTarget, setA11yMessage, value.humanData]
|
[dndDispatch, value]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setTargetOfIndex = useCallback(
|
const setTargetOfIndex = useCallback(
|
||||||
|
@ -293,11 +250,7 @@ const DragInner = memo(function DragInner({
|
||||||
const dropTargetsForActiveId =
|
const dropTargetsForActiveId =
|
||||||
dropTargetsByOrder &&
|
dropTargetsByOrder &&
|
||||||
Object.values(dropTargetsByOrder).filter((dropTarget) => dropTarget?.id === id);
|
Object.values(dropTargetsByOrder).filter((dropTarget) => dropTarget?.id === id);
|
||||||
if (index > 0 && dropTargetsForActiveId?.[index]) {
|
setTarget(dropTargetsForActiveId?.[index]);
|
||||||
setTarget(dropTargetsForActiveId[index]);
|
|
||||||
} else {
|
|
||||||
setTarget(dropTargetsForActiveId?.[0], true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[dropTargetsByOrder, setTarget]
|
[dropTargetsByOrder, setTarget]
|
||||||
);
|
);
|
||||||
|
@ -339,58 +292,64 @@ const DragInner = memo(function DragInner({
|
||||||
return { onKeyDown, onKeyUp };
|
return { onKeyDown, onKeyUp };
|
||||||
}, [activeDropTarget, setTargetOfIndex]);
|
}, [activeDropTarget, setTargetOfIndex]);
|
||||||
|
|
||||||
const dragStart = (
|
const dragStart = useCallback(
|
||||||
e: DroppableEvent | KeyboardEvent<HTMLButtonElement>,
|
(e: DroppableEvent | KeyboardEvent<HTMLButtonElement>, keyboardModeOn?: boolean) => {
|
||||||
keyboardModeOn?: boolean
|
// Setting stopPropgagation causes Chrome failures, so
|
||||||
) => {
|
// we are manually checking if we've already handled this
|
||||||
// Setting stopPropgagation causes Chrome failures, so
|
// in a nested child, and doing nothing if so...
|
||||||
// we are manually checking if we've already handled this
|
if (e && 'dataTransfer' in e && e.dataTransfer.getData('text')) {
|
||||||
// in a nested child, and doing nothing if so...
|
return;
|
||||||
if (e && 'dataTransfer' in e && e.dataTransfer.getData('text')) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only can reach the dragStart method if the element is draggable,
|
// We only can reach the dragStart method if the element is draggable,
|
||||||
// so we know we have DraggableProps if we reach this code.
|
// so we know we have DraggableProps if we reach this code.
|
||||||
if (e && 'dataTransfer' in e) {
|
if (e && 'dataTransfer' in e) {
|
||||||
e.dataTransfer.setData('text', value.humanData.label);
|
e.dataTransfer.setData('text', value.humanData.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chrome causes issues if you try to render from within a
|
// Chrome causes issues if you try to render from within a
|
||||||
// dragStart event, so we drop a setTimeout to avoid that.
|
// dragStart event, so we drop a setTimeout to avoid that.
|
||||||
|
|
||||||
const currentTarget = e?.currentTarget;
|
const currentTarget = e?.currentTarget;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setDragging({
|
dndDispatch({
|
||||||
...value,
|
type: 'startDragging',
|
||||||
ghost: keyboardModeOn
|
payload: {
|
||||||
? {
|
...(keyboardModeOn ? { keyboardMode: true } : {}),
|
||||||
children,
|
dragging: {
|
||||||
style: { width: currentTarget.offsetWidth, minHeight: currentTarget?.offsetHeight },
|
...value,
|
||||||
}
|
ghost: keyboardModeOn
|
||||||
: undefined,
|
? {
|
||||||
|
children,
|
||||||
|
style: {
|
||||||
|
width: currentTarget.offsetWidth,
|
||||||
|
minHeight: currentTarget?.offsetHeight,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
onDragStart?.(currentTarget);
|
||||||
});
|
});
|
||||||
setA11yMessage(announce.lifted(value.humanData));
|
},
|
||||||
if (keyboardModeOn) {
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
setKeyboardMode(true);
|
[dndDispatch, value, onDragStart]
|
||||||
}
|
);
|
||||||
if (onDragStart) {
|
|
||||||
onDragStart(currentTarget);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const dragEnd = (e?: DroppableEvent) => {
|
const dragEnd = useCallback(
|
||||||
e?.stopPropagation();
|
(e?: DroppableEvent) => {
|
||||||
setDragging(undefined);
|
e?.stopPropagation();
|
||||||
setActiveDropTarget(undefined);
|
|
||||||
setKeyboardMode(false);
|
dndDispatch({
|
||||||
setA11yMessage(announce.cancelled(value.humanData));
|
type: 'endDragging',
|
||||||
if (onDragEnd) {
|
payload: { dragging: value },
|
||||||
onDragEnd();
|
});
|
||||||
}
|
onDragEnd?.();
|
||||||
};
|
},
|
||||||
|
[dndDispatch, value, onDragEnd]
|
||||||
|
);
|
||||||
|
|
||||||
const setNextTarget = (e: KeyboardEvent<HTMLButtonElement>, reversed = false) => {
|
const setNextTarget = (e: KeyboardEvent<HTMLButtonElement>, reversed = false) => {
|
||||||
const nextTarget = nextValidDropTarget(
|
const nextTarget = nextValidDropTarget(
|
||||||
|
@ -408,16 +367,23 @@ const DragInner = memo(function DragInner({
|
||||||
} else if (e.ctrlKey && nextTarget?.id) {
|
} else if (e.ctrlKey && nextTarget?.id) {
|
||||||
setTargetOfIndex(nextTarget.id, 3);
|
setTargetOfIndex(nextTarget.id, 3);
|
||||||
} else {
|
} else {
|
||||||
setTarget(nextTarget, true);
|
setTarget(nextTarget);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const dropToActiveDropTarget = () => {
|
const dropToActiveDropTarget = () => {
|
||||||
if (activeDropTarget) {
|
if (activeDropTarget) {
|
||||||
onTrackUICounterEvent?.('drop_total');
|
const { dropType, onDrop } = activeDropTarget;
|
||||||
const { dropType, humanData, onDrop: onTargetDrop } = activeDropTarget;
|
setTimeout(() => {
|
||||||
setTimeout(() => setA11yMessage(announce.dropped(value.humanData, humanData, dropType)));
|
dndDispatch({
|
||||||
onTargetDrop(value, dropType);
|
type: 'dropToTarget',
|
||||||
|
payload: {
|
||||||
|
dragging: value,
|
||||||
|
dropTarget: activeDropTarget,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
onDrop(value, dropType);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -501,38 +467,35 @@ const DropsInner = memo(function DropsInner(props: DropsInnerProps) {
|
||||||
value,
|
value,
|
||||||
children,
|
children,
|
||||||
draggable,
|
draggable,
|
||||||
dragging,
|
dndState,
|
||||||
|
dndDispatch,
|
||||||
isNotDroppable,
|
isNotDroppable,
|
||||||
dropTypes,
|
dropTypes,
|
||||||
order,
|
order,
|
||||||
getAdditionalClassesOnEnter,
|
getAdditionalClassesOnEnter,
|
||||||
getAdditionalClassesOnDroppable,
|
getAdditionalClassesOnDroppable,
|
||||||
activeDropTarget,
|
|
||||||
registerDropTarget,
|
|
||||||
setActiveDropTarget,
|
|
||||||
keyboardMode,
|
|
||||||
setKeyboardMode,
|
|
||||||
setDragging,
|
|
||||||
setA11yMessage,
|
|
||||||
getCustomDropTarget,
|
getCustomDropTarget,
|
||||||
dataTestSubjPrefix,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const { dragging, activeDropTarget, dataTestSubjPrefix, keyboardMode } = dndState;
|
||||||
|
|
||||||
const [isInZone, setIsInZone] = useState(false);
|
const [isInZone, setIsInZone] = useState(false);
|
||||||
const mainTargetRef = useRef<HTMLDivElement>(null);
|
const mainTargetRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useShallowCompareEffect(() => {
|
useShallowCompareEffect(() => {
|
||||||
if (dropTypes && dropTypes?.[0] && onDrop && keyboardMode) {
|
if (dropTypes && dropTypes?.[0] && onDrop && keyboardMode) {
|
||||||
dropTypes.forEach((dropType, index) => {
|
dndDispatch({
|
||||||
registerDropTarget([...order, index], { ...value, onDrop, dropType });
|
type: 'registerDropTargets',
|
||||||
|
payload: dropTypes.reduce(
|
||||||
|
(acc, dropType, index) => ({
|
||||||
|
...acc,
|
||||||
|
[[...props.order, index].join(',')]: { ...value, onDrop, dropType },
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
),
|
||||||
});
|
});
|
||||||
return () => {
|
|
||||||
dropTypes.forEach((_, index) => {
|
|
||||||
registerDropTarget([...order, index], undefined);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}, [order, registerDropTarget, dropTypes, keyboardMode]);
|
}, [order, dndDispatch, dropTypes, keyboardMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
|
@ -586,15 +549,18 @@ const DropsInner = memo(function DropsInner(props: DropsInnerProps) {
|
||||||
);
|
);
|
||||||
// An optimization to prevent a bunch of React churn.
|
// An optimization to prevent a bunch of React churn.
|
||||||
if (!isActiveDropTarget) {
|
if (!isActiveDropTarget) {
|
||||||
setActiveDropTarget({ ...value, dropType: modifiedDropType, onDrop });
|
dndDispatch({
|
||||||
setA11yMessage(
|
type: 'selectDropTarget',
|
||||||
announce.selectedTarget(dragging.humanData, value.humanData, modifiedDropType)
|
payload: {
|
||||||
);
|
dropTarget: { ...value, dropType: modifiedDropType, onDrop },
|
||||||
|
dragging,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const dragLeave = () => {
|
const dragLeave = () => {
|
||||||
setActiveDropTarget(undefined);
|
dndDispatch({ type: 'leaveDropTarget' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const drop = (e: DroppableEvent, dropType: DropType) => {
|
const drop = (e: DroppableEvent, dropType: DropType) => {
|
||||||
|
@ -604,14 +570,17 @@ const DropsInner = memo(function DropsInner(props: DropsInnerProps) {
|
||||||
if (onDrop && dragging) {
|
if (onDrop && dragging) {
|
||||||
const modifiedDropType = getModifiedDropType(e, dropType);
|
const modifiedDropType = getModifiedDropType(e, dropType);
|
||||||
onDrop(dragging, modifiedDropType);
|
onDrop(dragging, modifiedDropType);
|
||||||
setTimeout(() =>
|
setTimeout(() => {
|
||||||
setA11yMessage(announce.dropped(dragging.humanData, value.humanData, modifiedDropType))
|
dndDispatch({
|
||||||
);
|
type: 'dropToTarget',
|
||||||
|
payload: {
|
||||||
|
dragging,
|
||||||
|
dropTarget: { ...value, dropType: modifiedDropType, onDrop },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
dndDispatch({ type: 'resetState' });
|
||||||
setDragging(undefined);
|
|
||||||
setActiveDropTarget(undefined);
|
|
||||||
setKeyboardMode(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProps = (dropType?: DropType, dropChildren?: ReactElement) => {
|
const getProps = (dropType?: DropType, dropChildren?: ReactElement) => {
|
||||||
|
@ -746,23 +715,11 @@ const SingleDropInner = ({
|
||||||
const ReorderableDrag = memo(function ReorderableDrag(
|
const ReorderableDrag = memo(function ReorderableDrag(
|
||||||
props: DragInnerProps & { reorderableGroup: Array<{ id: string }>; dragging?: DragDropIdentifier }
|
props: DragInnerProps & { reorderableGroup: Array<{ id: string }>; dragging?: DragDropIdentifier }
|
||||||
) {
|
) {
|
||||||
const {
|
const [{ isReorderOn, reorderedItems, direction }, reorderDispatch] = useContext(ReorderContext);
|
||||||
reorderState: { isReorderOn, reorderedItems, direction },
|
|
||||||
setReorderState,
|
|
||||||
} = useContext(ReorderContext);
|
|
||||||
|
|
||||||
const {
|
const { value, activeDraggingProps, reorderableGroup, dndDispatch, dataTestSubjPrefix } = props;
|
||||||
value,
|
|
||||||
setActiveDropTarget,
|
|
||||||
activeDraggingProps,
|
|
||||||
reorderableGroup,
|
|
||||||
setA11yMessage,
|
|
||||||
dataTestSubjPrefix,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const keyboardMode = activeDraggingProps?.keyboardMode;
|
const { keyboardMode, activeDropTarget, dropTargetsByOrder } = activeDraggingProps || {};
|
||||||
const activeDropTarget = activeDraggingProps?.activeDropTarget;
|
|
||||||
const dropTargetsByOrder = activeDraggingProps?.dropTargetsByOrder;
|
|
||||||
const isDragging = !!activeDraggingProps;
|
const isDragging = !!activeDraggingProps;
|
||||||
|
|
||||||
const isFocusInGroup = keyboardMode
|
const isFocusInGroup = keyboardMode
|
||||||
|
@ -771,11 +728,11 @@ const ReorderableDrag = memo(function ReorderableDrag(
|
||||||
: isDragging;
|
: isDragging;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setReorderState((s: ReorderState) => ({
|
reorderDispatch({
|
||||||
...s,
|
type: 'setIsReorderOn',
|
||||||
isReorderOn: isFocusInGroup,
|
payload: isFocusInGroup,
|
||||||
}));
|
});
|
||||||
}, [setReorderState, isFocusInGroup]);
|
}, [reorderDispatch, isFocusInGroup]);
|
||||||
|
|
||||||
const onReorderableDragStart = (
|
const onReorderableDragStart = (
|
||||||
currentTarget?:
|
currentTarget?:
|
||||||
|
@ -783,24 +740,19 @@ const ReorderableDrag = memo(function ReorderableDrag(
|
||||||
| KeyboardEvent<HTMLButtonElement>['currentTarget']
|
| KeyboardEvent<HTMLButtonElement>['currentTarget']
|
||||||
) => {
|
) => {
|
||||||
if (currentTarget) {
|
if (currentTarget) {
|
||||||
const height = currentTarget.offsetHeight + REORDER_OFFSET;
|
setTimeout(() => {
|
||||||
setReorderState((s: ReorderState) => ({
|
reorderDispatch({
|
||||||
...s,
|
type: 'registerDraggingItemHeight',
|
||||||
draggingHeight: height,
|
payload: currentTarget.offsetHeight + REORDER_OFFSET,
|
||||||
}));
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onReorderableDragEnd = () => {
|
const onReorderableDragEnd = () => {
|
||||||
resetReorderState();
|
reorderDispatch({ type: 'reset' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetReorderState = () =>
|
|
||||||
setReorderState((s: ReorderState) => ({
|
|
||||||
...s,
|
|
||||||
reorderedItems: [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
const extraKeyboardHandler = (e: KeyboardEvent<HTMLButtonElement>) => {
|
const extraKeyboardHandler = (e: KeyboardEvent<HTMLButtonElement>) => {
|
||||||
if (isReorderOn && keyboardMode) {
|
if (isReorderOn && keyboardMode) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -811,8 +763,7 @@ const ReorderableDrag = memo(function ReorderableDrag(
|
||||||
if (index !== -1) activeDropTargetIndex = index;
|
if (index !== -1) activeDropTargetIndex = index;
|
||||||
}
|
}
|
||||||
if (e.key === keys.ARROW_LEFT || e.key === keys.ARROW_RIGHT) {
|
if (e.key === keys.ARROW_LEFT || e.key === keys.ARROW_RIGHT) {
|
||||||
resetReorderState();
|
reorderDispatch({ type: 'reset' });
|
||||||
setActiveDropTarget(undefined);
|
|
||||||
} else if (keys.ARROW_DOWN === e.key) {
|
} else if (keys.ARROW_DOWN === e.key) {
|
||||||
if (activeDropTargetIndex < reorderableGroup.length - 1) {
|
if (activeDropTargetIndex < reorderableGroup.length - 1) {
|
||||||
const nextTarget = nextValidDropTarget(
|
const nextTarget = nextValidDropTarget(
|
||||||
|
@ -840,12 +791,8 @@ const ReorderableDrag = memo(function ReorderableDrag(
|
||||||
|
|
||||||
const onReorderableDragOver = (target?: DropIdentifier) => {
|
const onReorderableDragOver = (target?: DropIdentifier) => {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
setReorderState((s: ReorderState) => ({
|
reorderDispatch({ type: 'reset' });
|
||||||
...s,
|
dndDispatch({ type: 'leaveDropTarget' });
|
||||||
reorderedItems: [],
|
|
||||||
}));
|
|
||||||
setA11yMessage(announce.selectedTarget(value.humanData, value.humanData, 'reorder'));
|
|
||||||
setActiveDropTarget(target);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const droppingIndex = reorderableGroup.findIndex((i) => i.id === target.id);
|
const droppingIndex = reorderableGroup.findIndex((i) => i.id === target.id);
|
||||||
|
@ -853,40 +800,34 @@ const ReorderableDrag = memo(function ReorderableDrag(
|
||||||
if (draggingIndex === -1) {
|
if (draggingIndex === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setActiveDropTarget(target);
|
|
||||||
|
|
||||||
setA11yMessage(announce.selectedTarget(value.humanData, target.humanData, 'reorder'));
|
dndDispatch({
|
||||||
|
type: 'selectDropTarget',
|
||||||
setReorderState((s: ReorderState) =>
|
payload: {
|
||||||
draggingIndex < droppingIndex
|
dropTarget: target,
|
||||||
? {
|
dragging: value,
|
||||||
...s,
|
},
|
||||||
reorderedItems: reorderableGroup.slice(draggingIndex + 1, droppingIndex + 1),
|
});
|
||||||
direction: '-',
|
reorderDispatch({
|
||||||
}
|
type: 'setReorderedItems',
|
||||||
: {
|
payload: { draggingIndex, droppingIndex, items: reorderableGroup },
|
||||||
...s,
|
});
|
||||||
reorderedItems: reorderableGroup.slice(droppingIndex, draggingIndex),
|
|
||||||
direction: '+',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const areItemsReordered = isDragging && keyboardMode && reorderedItems.length;
|
const areItemsReordered = keyboardMode && isDragging && reorderedItems.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-test-subj={`${dataTestSubjPrefix}-reorderableDrag`}
|
data-test-subj={`${dataTestSubjPrefix}-reorderableDrag`}
|
||||||
className={
|
className={classNames('domDragDrop-reorderable', {
|
||||||
isDragging
|
['domDragDrop-translatableDrag']: isDragging,
|
||||||
? 'domDragDrop-reorderable domDragDrop-translatableDrag'
|
['domDragDrop-isKeyboardReorderInProgress']: keyboardMode && isDragging,
|
||||||
: 'domDragDrop-reorderable'
|
})}
|
||||||
}
|
|
||||||
style={
|
style={
|
||||||
areItemsReordered
|
areItemsReordered
|
||||||
? {
|
? {
|
||||||
transform: `translateY(${direction === '+' ? '-' : '+'}${reorderedItems.reduce(
|
transform: `translateY(${direction === '+' ? '-' : '+'}${reorderedItems.reduce(
|
||||||
(acc, cur) => acc + Number(cur.height || 0) + REORDER_OFFSET,
|
(acc, el) => acc + (el.height ?? 0) + REORDER_OFFSET,
|
||||||
0
|
0
|
||||||
)}px)`,
|
)}px)`,
|
||||||
}
|
}
|
||||||
|
@ -907,26 +848,13 @@ const ReorderableDrag = memo(function ReorderableDrag(
|
||||||
const ReorderableDrop = memo(function ReorderableDrop(
|
const ReorderableDrop = memo(function ReorderableDrop(
|
||||||
props: DropsInnerProps & { reorderableGroup: Array<{ id: string }> }
|
props: DropsInnerProps & { reorderableGroup: Array<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
const {
|
const { onDrop, value, dndState, dndDispatch, reorderableGroup } = props;
|
||||||
onDrop,
|
|
||||||
value,
|
|
||||||
dragging,
|
|
||||||
setDragging,
|
|
||||||
setKeyboardMode,
|
|
||||||
activeDropTarget,
|
|
||||||
setActiveDropTarget,
|
|
||||||
reorderableGroup,
|
|
||||||
setA11yMessage,
|
|
||||||
dataTestSubjPrefix,
|
|
||||||
onTrackUICounterEvent,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
|
const { dragging, dataTestSubjPrefix, activeDropTarget } = dndState;
|
||||||
const currentIndex = reorderableGroup.findIndex((i) => i.id === value.id);
|
const currentIndex = reorderableGroup.findIndex((i) => i.id === value.id);
|
||||||
|
|
||||||
const {
|
const [{ isReorderOn, reorderedItems, draggingHeight, direction }, reorderDispatch] =
|
||||||
reorderState: { isReorderOn, reorderedItems, draggingHeight, direction },
|
useContext(ReorderContext);
|
||||||
setReorderState,
|
|
||||||
} = useContext(ReorderContext);
|
|
||||||
|
|
||||||
const heightRef = useRef<HTMLDivElement>(null);
|
const heightRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
@ -935,52 +863,38 @@ const ReorderableDrop = memo(function ReorderableDrop(
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isReordered && heightRef.current?.clientHeight) {
|
if (isReordered && heightRef.current?.clientHeight) {
|
||||||
setReorderState((s) => ({
|
reorderDispatch({
|
||||||
...s,
|
type: 'registerReorderedItemHeight',
|
||||||
reorderedItems: s.reorderedItems.map((el) =>
|
payload: { id: value.id, height: heightRef.current.clientHeight },
|
||||||
el.id === value.id
|
});
|
||||||
? {
|
|
||||||
...el,
|
|
||||||
height: heightRef.current?.clientHeight,
|
|
||||||
}
|
|
||||||
: el
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}, [isReordered, setReorderState, value.id]);
|
}, [isReordered, reorderDispatch, value.id]);
|
||||||
|
|
||||||
const onReorderableDragOver = (e: DroppableEvent) => {
|
const onReorderableDragOver = (e: DroppableEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// An optimization to prevent a bunch of React churn.
|
// An optimization to prevent a bunch of React churn.
|
||||||
if (activeDropTarget?.id !== value?.id && onDrop) {
|
if (activeDropTarget?.id !== value?.id && onDrop) {
|
||||||
setActiveDropTarget({ ...value, dropType: 'reorder', onDrop });
|
|
||||||
|
|
||||||
const draggingIndex = reorderableGroup.findIndex((i) => i.id === dragging?.id);
|
const draggingIndex = reorderableGroup.findIndex((i) => i.id === dragging?.id);
|
||||||
|
|
||||||
if (!dragging || draggingIndex === -1) {
|
if (!dragging || draggingIndex === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const droppingIndex = currentIndex;
|
const droppingIndex = currentIndex;
|
||||||
if (draggingIndex === droppingIndex) {
|
if (draggingIndex === droppingIndex) {
|
||||||
setReorderState((s: ReorderState) => ({
|
reorderDispatch({ type: 'reset' });
|
||||||
...s,
|
|
||||||
reorderedItems: [],
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setReorderState((s: ReorderState) =>
|
reorderDispatch({
|
||||||
draggingIndex < droppingIndex
|
type: 'setReorderedItems',
|
||||||
? {
|
payload: { draggingIndex, droppingIndex, items: reorderableGroup },
|
||||||
...s,
|
});
|
||||||
reorderedItems: reorderableGroup.slice(draggingIndex + 1, droppingIndex + 1),
|
dndDispatch({
|
||||||
direction: '-',
|
type: 'selectDropTarget',
|
||||||
}
|
payload: {
|
||||||
: {
|
dropTarget: { ...value, dropType: 'reorder', onDrop },
|
||||||
...s,
|
dragging,
|
||||||
reorderedItems: reorderableGroup.slice(droppingIndex, draggingIndex),
|
},
|
||||||
direction: '+',
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -988,18 +902,20 @@ const ReorderableDrop = memo(function ReorderableDrop(
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
setActiveDropTarget(undefined);
|
|
||||||
setDragging(undefined);
|
|
||||||
setKeyboardMode(false);
|
|
||||||
|
|
||||||
if (onDrop && dragging) {
|
if (onDrop && dragging) {
|
||||||
onTrackUICounterEvent?.('drop_total');
|
|
||||||
onDrop(dragging, 'reorder');
|
onDrop(dragging, 'reorder');
|
||||||
// setTimeout ensures it will run after dragEnd messaging
|
// setTimeout ensures it will run after dragEnd messaging
|
||||||
setTimeout(() =>
|
setTimeout(() => {
|
||||||
setA11yMessage(announce.dropped(dragging.humanData, value.humanData, 'reorder'))
|
dndDispatch({
|
||||||
);
|
type: 'dropToTarget',
|
||||||
|
payload: {
|
||||||
|
dragging,
|
||||||
|
dropTarget: { ...value, dropType: 'reorder', onDrop },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
dndDispatch({ type: 'resetState' });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1027,11 +943,8 @@ const ReorderableDrop = memo(function ReorderableDrop(
|
||||||
onDrop={onReorderableDrop}
|
onDrop={onReorderableDrop}
|
||||||
onDragOver={onReorderableDragOver}
|
onDragOver={onReorderableDragOver}
|
||||||
onDragLeave={() => {
|
onDragLeave={() => {
|
||||||
setActiveDropTarget(undefined);
|
dndDispatch({ type: 'leaveDropTarget' });
|
||||||
setReorderState((s: ReorderState) => ({
|
reorderDispatch({ type: 'reset' });
|
||||||
...s,
|
|
||||||
reorderedItems: [],
|
|
||||||
}));
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,11 +10,7 @@ import { i18n } from '@kbn/i18n';
|
||||||
import { DropType } from '../types';
|
import { DropType } from '../types';
|
||||||
import { HumanData } from '.';
|
import { HumanData } from '.';
|
||||||
|
|
||||||
type AnnouncementFunction = (
|
type AnnouncementFunction = (draggedElement: HumanData, dropElement: HumanData) => string;
|
||||||
draggedElement: HumanData,
|
|
||||||
dropElement: HumanData,
|
|
||||||
announceModifierKeys?: boolean
|
|
||||||
) => string;
|
|
||||||
|
|
||||||
interface CustomAnnouncementsType {
|
interface CustomAnnouncementsType {
|
||||||
dropped: Partial<{ [dropType in DropType]: AnnouncementFunction }>;
|
dropped: Partial<{ [dropType in DropType]: AnnouncementFunction }>;
|
||||||
|
@ -32,10 +28,9 @@ const replaceAnnouncement = {
|
||||||
canDuplicate,
|
canDuplicate,
|
||||||
canCombine,
|
canCombine,
|
||||||
layerNumber: dropLayerNumber,
|
layerNumber: dropLayerNumber,
|
||||||
}: HumanData,
|
}: HumanData
|
||||||
announceModifierKeys?: boolean
|
|
||||||
) => {
|
) => {
|
||||||
if (announceModifierKeys && (canSwap || canDuplicate)) {
|
if (canSwap || canDuplicate) {
|
||||||
return i18n.translate('domDragDrop.announce.selectedTarget.replaceMain', {
|
return i18n.translate('domDragDrop.announce.selectedTarget.replaceMain', {
|
||||||
defaultMessage: `You're dragging {label} from {groupLabel} at position {position} in layer {layerNumber} over {dropLabel} from {dropGroupLabel} group at position {dropPosition} in layer {dropLayerNumber}. Press space or enter to replace {dropLabel} with {label}.{duplicateCopy}{swapCopy}{combineCopy}`,
|
defaultMessage: `You're dragging {label} from {groupLabel} at position {position} in layer {layerNumber} over {dropLabel} from {dropGroupLabel} group at position {dropPosition} in layer {dropLayerNumber}. Press space or enter to replace {dropLabel} with {label}.{duplicateCopy}{swapCopy}{combineCopy}`,
|
||||||
values: {
|
values: {
|
||||||
|
@ -168,10 +163,9 @@ const combineAnnouncement = {
|
||||||
canDuplicate,
|
canDuplicate,
|
||||||
canCombine,
|
canCombine,
|
||||||
layerNumber: dropLayerNumber,
|
layerNumber: dropLayerNumber,
|
||||||
}: HumanData,
|
}: HumanData
|
||||||
announceModifierKeys?: boolean
|
|
||||||
) => {
|
) => {
|
||||||
if (announceModifierKeys && (canSwap || canDuplicate || canCombine)) {
|
if (canSwap || canDuplicate || canCombine) {
|
||||||
return i18n.translate('domDragDrop.announce.selectedTarget.combineMain', {
|
return i18n.translate('domDragDrop.announce.selectedTarget.combineMain', {
|
||||||
defaultMessage: `You're dragging {label} from {groupLabel} at position {position} in layer {layerNumber} over {dropLabel} from {dropGroupLabel} group at position {dropPosition} in layer {dropLayerNumber}. Press space or enter to combine {dropLabel} with {label}.{duplicateCopy}{swapCopy}{combineCopy}`,
|
defaultMessage: `You're dragging {label} from {groupLabel} at position {position} in layer {layerNumber} over {dropLabel} from {dropGroupLabel} group at position {dropPosition} in layer {dropLayerNumber}. Press space or enter to combine {dropLabel} with {label}.{duplicateCopy}{swapCopy}{combineCopy}`,
|
||||||
values: {
|
values: {
|
||||||
|
@ -247,10 +241,9 @@ export const announcements: CustomAnnouncementsType = {
|
||||||
canDuplicate,
|
canDuplicate,
|
||||||
canCombine,
|
canCombine,
|
||||||
layerNumber: dropLayerNumber,
|
layerNumber: dropLayerNumber,
|
||||||
}: HumanData,
|
}: HumanData
|
||||||
announceModifierKeys?: boolean
|
|
||||||
) => {
|
) => {
|
||||||
if (announceModifierKeys && (canSwap || canDuplicate || canCombine)) {
|
if (canSwap || canDuplicate || canCombine) {
|
||||||
return i18n.translate('domDragDrop.announce.selectedTarget.replaceIncompatibleMain', {
|
return i18n.translate('domDragDrop.announce.selectedTarget.replaceIncompatibleMain', {
|
||||||
defaultMessage: `You're dragging {label} from {groupLabel} at position {position} in layer {layerNumber} over {dropLabel} from {dropGroupLabel} group at position {dropPosition} in layer {dropLayerNumber}. Press space or enter to convert {label} to {nextLabel} and replace {dropLabel}.{duplicateCopy}{swapCopy}{combineCopy}`,
|
defaultMessage: `You're dragging {label} from {groupLabel} at position {position} in layer {layerNumber} over {dropLabel} from {dropGroupLabel} group at position {dropPosition} in layer {dropLayerNumber}. Press space or enter to convert {label} to {nextLabel} and replace {dropLabel}.{duplicateCopy}{swapCopy}{combineCopy}`,
|
||||||
values: {
|
values: {
|
||||||
|
@ -290,10 +283,9 @@ export const announcements: CustomAnnouncementsType = {
|
||||||
canSwap,
|
canSwap,
|
||||||
canDuplicate,
|
canDuplicate,
|
||||||
layerNumber: dropLayerNumber,
|
layerNumber: dropLayerNumber,
|
||||||
}: HumanData,
|
}: HumanData
|
||||||
announceModifierKeys?: boolean
|
|
||||||
) => {
|
) => {
|
||||||
if (announceModifierKeys && (canSwap || canDuplicate)) {
|
if (canSwap || canDuplicate) {
|
||||||
return i18n.translate('domDragDrop.announce.selectedTarget.moveIncompatibleMain', {
|
return i18n.translate('domDragDrop.announce.selectedTarget.moveIncompatibleMain', {
|
||||||
defaultMessage: `You're dragging {label} from {groupLabel} at position {position} in layer {layerNumber} over position {dropPosition} in {dropGroupLabel} group in layer {dropLayerNumber}. Press space or enter to convert {label} to {nextLabel} and move.{duplicateCopy}`,
|
defaultMessage: `You're dragging {label} from {groupLabel} at position {position} in layer {layerNumber} over position {dropPosition} in {dropGroupLabel} group in layer {dropLayerNumber}. Press space or enter to convert {label} to {nextLabel} and move.{duplicateCopy}`,
|
||||||
values: {
|
values: {
|
||||||
|
@ -329,10 +321,9 @@ export const announcements: CustomAnnouncementsType = {
|
||||||
canSwap,
|
canSwap,
|
||||||
canDuplicate,
|
canDuplicate,
|
||||||
layerNumber: dropLayerNumber,
|
layerNumber: dropLayerNumber,
|
||||||
}: HumanData,
|
}: HumanData
|
||||||
announceModifierKeys?: boolean
|
|
||||||
) => {
|
) => {
|
||||||
if (announceModifierKeys && (canSwap || canDuplicate)) {
|
if (canSwap || canDuplicate) {
|
||||||
return i18n.translate('domDragDrop.announce.selectedTarget.moveCompatibleMain', {
|
return i18n.translate('domDragDrop.announce.selectedTarget.moveCompatibleMain', {
|
||||||
defaultMessage: `You're dragging {label} from {groupLabel} at position {position} over position {dropPosition} in {dropGroupLabel} group in layer {dropLayerNumber}. Press space or enter to move.{duplicateCopy}`,
|
defaultMessage: `You're dragging {label} from {groupLabel} at position {position} over position {dropPosition} in {dropGroupLabel} group in layer {dropLayerNumber}. Press space or enter to move.{duplicateCopy}`,
|
||||||
values: {
|
values: {
|
||||||
|
@ -790,19 +781,9 @@ export const announce = {
|
||||||
dropped: (draggedElement: HumanData, dropElement: HumanData, type?: DropType) =>
|
dropped: (draggedElement: HumanData, dropElement: HumanData, type?: DropType) =>
|
||||||
(type && announcements.dropped?.[type]?.(draggedElement, dropElement)) ||
|
(type && announcements.dropped?.[type]?.(draggedElement, dropElement)) ||
|
||||||
defaultAnnouncements.dropped(draggedElement, dropElement),
|
defaultAnnouncements.dropped(draggedElement, dropElement),
|
||||||
selectedTarget: (
|
selectedTarget: (draggedElement: HumanData, dropElement: HumanData, type?: DropType) => {
|
||||||
draggedElement: HumanData,
|
|
||||||
dropElement: HumanData,
|
|
||||||
type?: DropType,
|
|
||||||
announceModifierKeys?: boolean
|
|
||||||
) => {
|
|
||||||
return (
|
return (
|
||||||
(type &&
|
(type && announcements.selectedTarget?.[type]?.(draggedElement, dropElement)) ||
|
||||||
announcements.selectedTarget?.[type]?.(
|
|
||||||
draggedElement,
|
|
||||||
dropElement,
|
|
||||||
announceModifierKeys
|
|
||||||
)) ||
|
|
||||||
defaultAnnouncements.selectedTarget(draggedElement, dropElement)
|
defaultAnnouncements.selectedTarget(draggedElement, dropElement)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useContext } from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { RootDragDropProvider, DragContext } from '.';
|
import { RootDragDropProvider, useDragDropContext } from '.';
|
||||||
|
|
||||||
jest.useFakeTimers({ legacyFakeTimers: true });
|
jest.useFakeTimers({ legacyFakeTimers: true });
|
||||||
|
|
||||||
|
@ -16,11 +16,11 @@ describe('RootDragDropProvider', () => {
|
||||||
test('reuses contexts for each render', () => {
|
test('reuses contexts for each render', () => {
|
||||||
const contexts: Array<{}> = [];
|
const contexts: Array<{}> = [];
|
||||||
const TestComponent = ({ name }: { name: string }) => {
|
const TestComponent = ({ name }: { name: string }) => {
|
||||||
const context = useContext(DragContext);
|
const context = useDragDropContext();
|
||||||
contexts.push(context);
|
contexts.push(context);
|
||||||
return (
|
return (
|
||||||
<div data-test-subj="test-component">
|
<div data-test-subj="test-component">
|
||||||
{name} {!!context.dragging}
|
{name} {!!context[0].dragging}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,45 +6,54 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useMemo } from 'react';
|
import React, { Reducer, useReducer } from 'react';
|
||||||
import { EuiScreenReaderOnly } from '@elastic/eui';
|
import { EuiScreenReaderOnly } from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import {
|
import {
|
||||||
DropIdentifier,
|
DropIdentifier,
|
||||||
DraggingIdentifier,
|
|
||||||
DragDropIdentifier,
|
DragDropIdentifier,
|
||||||
RegisteredDropTargets,
|
RegisteredDropTargets,
|
||||||
|
DragContextValue,
|
||||||
DragContextState,
|
DragContextState,
|
||||||
|
CustomMiddleware,
|
||||||
|
DraggingIdentifier,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { DEFAULT_DATA_TEST_SUBJ } from '../constants';
|
import { DEFAULT_DATA_TEST_SUBJ } from '../constants';
|
||||||
|
import { announce } from './announcements';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
dragging: undefined,
|
||||||
|
activeDropTarget: undefined,
|
||||||
|
keyboardMode: false,
|
||||||
|
dropTargetsByOrder: {},
|
||||||
|
dataTestSubjPrefix: DEFAULT_DATA_TEST_SUBJ,
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* The drag / drop context singleton, used like so:
|
* The drag / drop context singleton, used like so:
|
||||||
*
|
*
|
||||||
* const { dragging, setDragging } = useContext(DragContext);
|
* const [ state, dispatch ] = useDragDropContext();
|
||||||
*/
|
*/
|
||||||
export const DragContext = React.createContext<DragContextState>({
|
const DragContext = React.createContext<DragContextValue>([initialState, () => {}]);
|
||||||
dragging: undefined,
|
|
||||||
setDragging: () => {},
|
export function useDragDropContext() {
|
||||||
keyboardMode: false,
|
const context = React.useContext(DragContext);
|
||||||
setKeyboardMode: () => {},
|
if (context === undefined) {
|
||||||
activeDropTarget: undefined,
|
throw new Error(
|
||||||
setActiveDropTarget: () => {},
|
'useDragDropContext must be used within a <RootDragDropProvider/> or <ChildDragDropProvider/>'
|
||||||
setA11yMessage: () => {},
|
);
|
||||||
dropTargetsByOrder: undefined,
|
}
|
||||||
registerDropTarget: () => {},
|
return context;
|
||||||
dataTestSubjPrefix: DEFAULT_DATA_TEST_SUBJ,
|
}
|
||||||
onTrackUICounterEvent: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The argument to DragDropProvider.
|
* The argument to DragDropProvider.
|
||||||
*/
|
*/
|
||||||
export interface ProviderProps extends DragContextState {
|
export interface ProviderProps {
|
||||||
/**
|
/**
|
||||||
* The React children.
|
* The React children.
|
||||||
*/
|
*/
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
value: DragContextValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,74 +63,193 @@ export interface ProviderProps extends DragContextState {
|
||||||
*
|
*
|
||||||
* @param props
|
* @param props
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
interface ResetStateAction {
|
||||||
|
type: 'resetState';
|
||||||
|
payload?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EndDraggingAction {
|
||||||
|
type: 'endDragging';
|
||||||
|
payload: {
|
||||||
|
dragging: DraggingIdentifier;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StartDraggingAction {
|
||||||
|
type: 'startDragging';
|
||||||
|
payload: {
|
||||||
|
dragging: DraggingIdentifier;
|
||||||
|
keyboardMode?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LeaveDropTargetAction {
|
||||||
|
type: 'leaveDropTarget';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SelectDropTargetAction {
|
||||||
|
type: 'selectDropTarget';
|
||||||
|
payload: {
|
||||||
|
dropTarget: DropIdentifier;
|
||||||
|
dragging: DragDropIdentifier;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DragToTargetAction {
|
||||||
|
type: 'dropToTarget';
|
||||||
|
payload: {
|
||||||
|
dragging: DragDropIdentifier;
|
||||||
|
dropTarget: DropIdentifier;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegisterDropTargetAction {
|
||||||
|
type: 'registerDropTargets';
|
||||||
|
payload: RegisteredDropTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DragDropAction =
|
||||||
|
| ResetStateAction
|
||||||
|
| RegisterDropTargetAction
|
||||||
|
| LeaveDropTargetAction
|
||||||
|
| SelectDropTargetAction
|
||||||
|
| DragToTargetAction
|
||||||
|
| StartDraggingAction
|
||||||
|
| EndDraggingAction;
|
||||||
|
|
||||||
|
const dragDropReducer = (state: DragContextState, action: DragDropAction) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'resetState':
|
||||||
|
case 'endDragging':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
dropTargetsByOrder: undefined,
|
||||||
|
dragging: undefined,
|
||||||
|
keyboardMode: false,
|
||||||
|
activeDropTarget: undefined,
|
||||||
|
};
|
||||||
|
case 'registerDropTargets':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
dropTargetsByOrder: {
|
||||||
|
...state.dropTargetsByOrder,
|
||||||
|
...action.payload,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'dropToTarget':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
dropTargetsByOrder: undefined,
|
||||||
|
dragging: undefined,
|
||||||
|
keyboardMode: false,
|
||||||
|
activeDropTarget: undefined,
|
||||||
|
};
|
||||||
|
case 'leaveDropTarget':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeDropTarget: undefined,
|
||||||
|
};
|
||||||
|
case 'selectDropTarget':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeDropTarget: action.payload.dropTarget,
|
||||||
|
};
|
||||||
|
case 'startDragging':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.payload,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useReducerWithMiddleware = (
|
||||||
|
reducer: Reducer<DragContextState, DragDropAction>,
|
||||||
|
initState: DragContextState,
|
||||||
|
middlewareFns?: Array<(action: DragDropAction) => void>
|
||||||
|
) => {
|
||||||
|
const [state, dispatch] = useReducer(reducer, initState);
|
||||||
|
|
||||||
|
const dispatchWithMiddleware = React.useCallback(
|
||||||
|
(action: DragDropAction) => {
|
||||||
|
if (middlewareFns !== undefined && middlewareFns.length > 0) {
|
||||||
|
middlewareFns.forEach((middlewareFn) => middlewareFn(action));
|
||||||
|
}
|
||||||
|
dispatch(action);
|
||||||
|
},
|
||||||
|
[middlewareFns]
|
||||||
|
);
|
||||||
|
|
||||||
|
return [state, dispatchWithMiddleware] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useA11yMiddleware = () => {
|
||||||
|
const [a11yMessage, setA11yMessage] = React.useState('');
|
||||||
|
const a11yMiddleware = React.useCallback((action: DragDropAction) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'startDragging':
|
||||||
|
setA11yMessage(announce.lifted(action.payload.dragging.humanData));
|
||||||
|
return;
|
||||||
|
case 'selectDropTarget':
|
||||||
|
setA11yMessage(
|
||||||
|
announce.selectedTarget(
|
||||||
|
action.payload.dragging.humanData,
|
||||||
|
action.payload.dropTarget.humanData,
|
||||||
|
action.payload.dropTarget.dropType
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
case 'leaveDropTarget':
|
||||||
|
setA11yMessage(announce.noTarget());
|
||||||
|
return;
|
||||||
|
case 'dropToTarget':
|
||||||
|
const { dragging, dropTarget } = action.payload;
|
||||||
|
setA11yMessage(
|
||||||
|
announce.dropped(dragging.humanData, dropTarget.humanData, dropTarget.dropType)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
case 'endDragging':
|
||||||
|
setA11yMessage(announce.cancelled(action.payload.dragging.humanData));
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
return { a11yMessage, a11yMiddleware };
|
||||||
|
};
|
||||||
|
|
||||||
export function RootDragDropProvider({
|
export function RootDragDropProvider({
|
||||||
children,
|
children,
|
||||||
dataTestSubj = DEFAULT_DATA_TEST_SUBJ,
|
dataTestSubj = DEFAULT_DATA_TEST_SUBJ,
|
||||||
onTrackUICounterEvent,
|
customMiddleware,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
dataTestSubj?: string;
|
dataTestSubj?: string;
|
||||||
onTrackUICounterEvent?: DragContextState['onTrackUICounterEvent'];
|
customMiddleware?: CustomMiddleware;
|
||||||
}) {
|
}) {
|
||||||
const [draggingState, setDraggingState] = useState<{ dragging?: DraggingIdentifier }>({
|
const { a11yMessage, a11yMiddleware } = useA11yMiddleware();
|
||||||
dragging: undefined,
|
const middlewareFns = React.useMemo(() => {
|
||||||
});
|
return customMiddleware ? [customMiddleware, a11yMiddleware] : [a11yMiddleware];
|
||||||
const [keyboardModeState, setKeyboardModeState] = useState(false);
|
}, [customMiddleware, a11yMiddleware]);
|
||||||
const [a11yMessageState, setA11yMessageState] = useState('');
|
|
||||||
const [activeDropTargetState, setActiveDropTargetState] = useState<DropIdentifier | undefined>(
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
const [dropTargetsByOrderState, setDropTargetsByOrderState] = useState<RegisteredDropTargets>({});
|
const [state, dispatch] = useReducerWithMiddleware(
|
||||||
|
dragDropReducer,
|
||||||
const setDragging = useMemo(
|
{
|
||||||
() => (dragging?: DraggingIdentifier) => setDraggingState({ dragging }),
|
...initialState,
|
||||||
[setDraggingState]
|
dataTestSubjPrefix: dataTestSubj,
|
||||||
);
|
|
||||||
|
|
||||||
const setA11yMessage = useMemo(
|
|
||||||
() => (message: string) => setA11yMessageState(message),
|
|
||||||
[setA11yMessageState]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setActiveDropTarget = useMemo(
|
|
||||||
() => (activeDropTarget?: DropIdentifier) => setActiveDropTargetState(activeDropTarget),
|
|
||||||
[setActiveDropTargetState]
|
|
||||||
);
|
|
||||||
|
|
||||||
const registerDropTarget = useMemo(
|
|
||||||
() => (order: number[], dropTarget?: DropIdentifier) => {
|
|
||||||
return setDropTargetsByOrderState((s) => {
|
|
||||||
return {
|
|
||||||
...s,
|
|
||||||
[order.join(',')]: dropTarget,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[setDropTargetsByOrderState]
|
middlewareFns
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChildDragDropProvider
|
<ChildDragDropProvider value={[state, dispatch]}>{children}</ChildDragDropProvider>
|
||||||
keyboardMode={keyboardModeState}
|
|
||||||
setKeyboardMode={setKeyboardModeState}
|
|
||||||
dragging={draggingState.dragging}
|
|
||||||
setA11yMessage={setA11yMessage}
|
|
||||||
setDragging={setDragging}
|
|
||||||
activeDropTarget={activeDropTargetState}
|
|
||||||
setActiveDropTarget={setActiveDropTarget}
|
|
||||||
registerDropTarget={registerDropTarget}
|
|
||||||
dropTargetsByOrder={dropTargetsByOrderState}
|
|
||||||
dataTestSubjPrefix={dataTestSubj}
|
|
||||||
onTrackUICounterEvent={onTrackUICounterEvent}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ChildDragDropProvider>
|
|
||||||
<EuiScreenReaderOnly>
|
<EuiScreenReaderOnly>
|
||||||
<div>
|
<div>
|
||||||
<p aria-live="assertive" aria-atomic={true}>
|
<p aria-live="assertive" aria-atomic={true}>
|
||||||
{a11yMessageState}
|
{a11yMessage}
|
||||||
</p>
|
</p>
|
||||||
<p id={`${dataTestSubj}-keyboardInstructionsWithReorder`}>
|
<p id={`${dataTestSubj}-keyboardInstructionsWithReorder`}>
|
||||||
{i18n.translate('domDragDrop.keyboardInstructionsReorder', {
|
{i18n.translate('domDragDrop.keyboardInstructionsReorder', {
|
||||||
|
@ -206,47 +334,6 @@ export function nextValidDropTarget(
|
||||||
*
|
*
|
||||||
* @param props
|
* @param props
|
||||||
*/
|
*/
|
||||||
export function ChildDragDropProvider({
|
export function ChildDragDropProvider({ value, children }: ProviderProps) {
|
||||||
dragging,
|
|
||||||
setDragging,
|
|
||||||
setKeyboardMode,
|
|
||||||
keyboardMode,
|
|
||||||
activeDropTarget,
|
|
||||||
setActiveDropTarget,
|
|
||||||
setA11yMessage,
|
|
||||||
registerDropTarget,
|
|
||||||
dropTargetsByOrder,
|
|
||||||
dataTestSubjPrefix,
|
|
||||||
onTrackUICounterEvent,
|
|
||||||
children,
|
|
||||||
}: ProviderProps) {
|
|
||||||
const value = useMemo(
|
|
||||||
() => ({
|
|
||||||
setKeyboardMode,
|
|
||||||
keyboardMode,
|
|
||||||
dragging,
|
|
||||||
setDragging,
|
|
||||||
activeDropTarget,
|
|
||||||
setActiveDropTarget,
|
|
||||||
setA11yMessage,
|
|
||||||
dropTargetsByOrder,
|
|
||||||
registerDropTarget,
|
|
||||||
dataTestSubjPrefix,
|
|
||||||
onTrackUICounterEvent,
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
setDragging,
|
|
||||||
dragging,
|
|
||||||
activeDropTarget,
|
|
||||||
setActiveDropTarget,
|
|
||||||
setKeyboardMode,
|
|
||||||
keyboardMode,
|
|
||||||
setA11yMessage,
|
|
||||||
dropTargetsByOrder,
|
|
||||||
registerDropTarget,
|
|
||||||
dataTestSubjPrefix,
|
|
||||||
onTrackUICounterEvent,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
return <DragContext.Provider value={value}>{children}</DragContext.Provider>;
|
return <DragContext.Provider value={value}>{children}</DragContext.Provider>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useMemo } from 'react';
|
import React, { useReducer, Reducer, Dispatch } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { DEFAULT_DATA_TEST_SUBJ, REORDER_ITEM_HEIGHT } from '../constants';
|
import { DEFAULT_DATA_TEST_SUBJ, REORDER_ITEM_HEIGHT } from '../constants';
|
||||||
|
|
||||||
|
@ -31,69 +31,116 @@ export interface ReorderState {
|
||||||
* indicates that user is in keyboard mode
|
* indicates that user is in keyboard mode
|
||||||
*/
|
*/
|
||||||
isReorderOn: boolean;
|
isReorderOn: boolean;
|
||||||
/**
|
|
||||||
* reorder group needed for screen reader aria-described-by attribute
|
|
||||||
*/
|
|
||||||
groupId: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SetReorderStateDispatch = (prevState: ReorderState) => ReorderState;
|
const initialState: ReorderState = {
|
||||||
|
reorderedItems: [],
|
||||||
|
direction: '-' as const,
|
||||||
|
draggingHeight: REORDER_ITEM_HEIGHT,
|
||||||
|
isReorderOn: false,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reorder context state
|
* Reorder context state
|
||||||
*/
|
*/
|
||||||
export interface ReorderContextState {
|
export type ReorderContextState = [ReorderState, Dispatch<ReorderAction>];
|
||||||
reorderState: ReorderState;
|
|
||||||
setReorderState: (dispatch: SetReorderStateDispatch) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reorder context
|
* Reorder context
|
||||||
*/
|
*/
|
||||||
export const ReorderContext = React.createContext<ReorderContextState>({
|
export const ReorderContext = React.createContext<ReorderContextState>([
|
||||||
reorderState: {
|
initialState,
|
||||||
reorderedItems: [],
|
() => () => {},
|
||||||
direction: '-',
|
]);
|
||||||
draggingHeight: REORDER_ITEM_HEIGHT,
|
|
||||||
isReorderOn: false,
|
|
||||||
groupId: '',
|
|
||||||
},
|
|
||||||
setReorderState: () => () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To create a reordering group, surround the elements from the same group with a `ReorderProvider`
|
* To create a reordering group, surround the elements from the same group with a `ReorderProvider`
|
||||||
* @param id
|
|
||||||
* @param children
|
* @param children
|
||||||
* @param className
|
* @param className
|
||||||
* @param draggingHeight
|
|
||||||
* @param dataTestSubj
|
* @param dataTestSubj
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
interface ResetAction {
|
||||||
|
type: 'reset';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegisterDraggingItemHeightAction {
|
||||||
|
type: 'registerDraggingItemHeight';
|
||||||
|
payload: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegisterReorderedItemHeightAction {
|
||||||
|
type: 'registerReorderedItemHeight';
|
||||||
|
payload: { id: string; height: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SetIsReorderOnAction {
|
||||||
|
type: 'setIsReorderOn';
|
||||||
|
payload: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SetReorderedItemsAction {
|
||||||
|
type: 'setReorderedItems';
|
||||||
|
payload: {
|
||||||
|
items: ReorderState['reorderedItems'];
|
||||||
|
draggingIndex: number;
|
||||||
|
droppingIndex: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReorderAction =
|
||||||
|
| ResetAction
|
||||||
|
| RegisterDraggingItemHeightAction
|
||||||
|
| RegisterReorderedItemHeightAction
|
||||||
|
| SetIsReorderOnAction
|
||||||
|
| SetReorderedItemsAction;
|
||||||
|
|
||||||
|
const reorderReducer = (state: ReorderState, action: ReorderAction) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'reset':
|
||||||
|
return { ...state, reorderedItems: [] };
|
||||||
|
case 'registerDraggingItemHeight':
|
||||||
|
return { ...state, draggingHeight: action.payload };
|
||||||
|
case 'registerReorderedItemHeight':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
reorderedItems: state.reorderedItems.map((i) =>
|
||||||
|
i.id === action.payload.id ? { ...i, height: action.payload.height } : i
|
||||||
|
),
|
||||||
|
};
|
||||||
|
case 'setIsReorderOn':
|
||||||
|
return { ...state, isReorderOn: action.payload };
|
||||||
|
case 'setReorderedItems':
|
||||||
|
const { items, draggingIndex, droppingIndex } = action.payload;
|
||||||
|
return draggingIndex < droppingIndex
|
||||||
|
? {
|
||||||
|
...state,
|
||||||
|
reorderedItems: items.slice(draggingIndex + 1, droppingIndex + 1),
|
||||||
|
direction: '-' as const,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...state,
|
||||||
|
reorderedItems: items.slice(droppingIndex, draggingIndex),
|
||||||
|
direction: '+' as const,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export function ReorderProvider({
|
export function ReorderProvider({
|
||||||
id,
|
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
draggingHeight = REORDER_ITEM_HEIGHT,
|
|
||||||
dataTestSubj = DEFAULT_DATA_TEST_SUBJ,
|
dataTestSubj = DEFAULT_DATA_TEST_SUBJ,
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
draggingHeight?: number;
|
|
||||||
dataTestSubj?: string;
|
dataTestSubj?: string;
|
||||||
}) {
|
}) {
|
||||||
const [state, setState] = useState<ReorderContextState['reorderState']>({
|
const [state, dispatch] = useReducer<Reducer<ReorderState, ReorderAction>>(
|
||||||
reorderedItems: [],
|
reorderReducer,
|
||||||
direction: '-',
|
initialState
|
||||||
draggingHeight,
|
|
||||||
isReorderOn: false,
|
|
||||||
groupId: id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const setReorderState = useMemo(
|
|
||||||
() => (dispatch: SetReorderStateDispatch) => setState(dispatch),
|
|
||||||
[setState]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -103,9 +150,7 @@ export function ReorderProvider({
|
||||||
'domDragDrop-isActiveGroup': state.isReorderOn && React.Children.count(children) > 1,
|
'domDragDrop-isActiveGroup': state.isReorderOn && React.Children.count(children) > 1,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ReorderContext.Provider value={{ reorderState: state, setReorderState }}>
|
<ReorderContext.Provider value={[state, dispatch]}>{children}</ReorderContext.Provider>
|
||||||
{children}
|
|
||||||
</ReorderContext.Provider>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DropType } from '../types';
|
import { DropType } from '../types';
|
||||||
|
import { DragDropAction } from './providers';
|
||||||
|
|
||||||
export interface HumanData {
|
export interface HumanData {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -57,45 +58,34 @@ export type DropHandler = (dropped: DragDropIdentifier, dropType?: DropType) =>
|
||||||
|
|
||||||
export type RegisteredDropTargets = Record<string, DropIdentifier | undefined> | undefined;
|
export type RegisteredDropTargets = Record<string, DropIdentifier | undefined> | undefined;
|
||||||
|
|
||||||
/**
|
|
||||||
* The shape of the drag / drop context.
|
|
||||||
*/
|
|
||||||
export interface DragContextState {
|
export interface DragContextState {
|
||||||
/**
|
/**
|
||||||
* The item being dragged or undefined.
|
* The item being dragged or undefined.
|
||||||
*/
|
*/
|
||||||
dragging?: DraggingIdentifier;
|
dragging?: DraggingIdentifier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* keyboard mode
|
* keyboard mode
|
||||||
*/
|
*/
|
||||||
keyboardMode: boolean;
|
keyboardMode: boolean;
|
||||||
/**
|
/**
|
||||||
* keyboard mode
|
* currently selected drop target
|
||||||
*/
|
*/
|
||||||
setKeyboardMode: (mode: boolean) => void;
|
|
||||||
/**
|
|
||||||
* Set the item being dragged.
|
|
||||||
*/
|
|
||||||
setDragging: (dragging?: DraggingIdentifier) => void;
|
|
||||||
|
|
||||||
activeDropTarget?: DropIdentifier;
|
activeDropTarget?: DropIdentifier;
|
||||||
|
/**
|
||||||
|
* currently registered drop targets
|
||||||
|
*/
|
||||||
dropTargetsByOrder: RegisteredDropTargets;
|
dropTargetsByOrder: RegisteredDropTargets;
|
||||||
|
|
||||||
setActiveDropTarget: (newTarget?: DropIdentifier) => void;
|
|
||||||
|
|
||||||
setA11yMessage: (message: string) => void;
|
|
||||||
registerDropTarget: (order: number[], dropTarget?: DropIdentifier) => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customizable data-test-subj prefix
|
* Customizable data-test-subj prefix
|
||||||
*/
|
*/
|
||||||
dataTestSubjPrefix: string;
|
dataTestSubjPrefix: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* A custom callback for telemetry
|
|
||||||
* @param event
|
|
||||||
*/
|
|
||||||
onTrackUICounterEvent?: (event: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CustomMiddleware = (action: DragDropAction) => void;
|
||||||
|
|
||||||
|
export type DragContextValue = [
|
||||||
|
state: DragContextState,
|
||||||
|
dispatch: React.Dispatch<DragDropAction>,
|
||||||
|
customMiddleware?: CustomMiddleware
|
||||||
|
];
|
||||||
|
|
|
@ -62,6 +62,7 @@ export interface FieldItemButtonProps<T extends FieldListItem> {
|
||||||
* @param otherProps
|
* @param otherProps
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function FieldItemButton<T extends FieldListItem = DataViewField>({
|
export function FieldItemButton<T extends FieldListItem = DataViewField>({
|
||||||
field,
|
field,
|
||||||
fieldSearchHighlight,
|
fieldSearchHighlight,
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
import './discover_layout.scss';
|
import './discover_layout.scss';
|
||||||
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
EuiButtonIcon,
|
EuiButtonIcon,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
|
@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n';
|
||||||
import { METRIC_TYPE } from '@kbn/analytics';
|
import { METRIC_TYPE } from '@kbn/analytics';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { generateFilters } from '@kbn/data-plugin/public';
|
import { generateFilters } from '@kbn/data-plugin/public';
|
||||||
import { DragContext } from '@kbn/dom-drag-drop';
|
import { useDragDropContext } from '@kbn/dom-drag-drop';
|
||||||
import { DataViewField, DataViewType } from '@kbn/data-views-plugin/public';
|
import { DataViewField, DataViewType } from '@kbn/data-views-plugin/public';
|
||||||
import { useSavedSearchInitial } from '../../services/discover_state_provider';
|
import { useSavedSearchInitial } from '../../services/discover_state_provider';
|
||||||
import { DiscoverStateContainer } from '../../services/discover_state';
|
import { DiscoverStateContainer } from '../../services/discover_state';
|
||||||
|
@ -182,8 +182,8 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
||||||
|
|
||||||
const resizeRef = useRef<HTMLDivElement>(null);
|
const resizeRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const dragDropContext = useContext(DragContext);
|
const [{ dragging }] = useDragDropContext();
|
||||||
const draggingFieldName = dragDropContext.dragging?.id;
|
const draggingFieldName = dragging?.id;
|
||||||
|
|
||||||
const onDropFieldToTable = useMemo(() => {
|
const onDropFieldToTable = useMemo(() => {
|
||||||
if (!draggingFieldName || currentColumns.includes(draggingFieldName)) {
|
if (!draggingFieldName || currentColumns.includes(draggingFieldName)) {
|
||||||
|
|
|
@ -7,13 +7,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import { DragContext, DragDrop, DropTargetSwapDuplicateCombine } from '@kbn/dom-drag-drop';
|
import {
|
||||||
|
DragDrop,
|
||||||
|
DropTargetSwapDuplicateCombine,
|
||||||
|
ReorderProvider,
|
||||||
|
useDragDropContext,
|
||||||
|
} from '@kbn/dom-drag-drop';
|
||||||
import {
|
import {
|
||||||
DimensionButton,
|
DimensionButton,
|
||||||
DimensionTrigger,
|
DimensionTrigger,
|
||||||
EmptyDimensionButton,
|
EmptyDimensionButton,
|
||||||
} from '@kbn/visualization-ui-components/public';
|
} from '@kbn/visualization-ui-components/public';
|
||||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { euiThemeVars } from '@kbn/ui-theme';
|
import { euiThemeVars } from '@kbn/ui-theme';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
@ -34,8 +39,6 @@ export const AnnotationList = ({
|
||||||
setNewAnnotationId(uuidv4());
|
setNewAnnotationId(uuidv4());
|
||||||
}, [annotations.length]);
|
}, [annotations.length]);
|
||||||
|
|
||||||
const { dragging } = useContext(DragContext);
|
|
||||||
|
|
||||||
const addAnnotationText = i18n.translate('eventAnnotation.annotationList.add', {
|
const addAnnotationText = i18n.translate('eventAnnotation.annotationList.add', {
|
||||||
defaultMessage: 'Add annotation',
|
defaultMessage: 'Add annotation',
|
||||||
});
|
});
|
||||||
|
@ -59,45 +62,78 @@ export const AnnotationList = ({
|
||||||
[annotations, newAnnotationId, selectAnnotation, updateAnnotations]
|
[annotations, newAnnotationId, selectAnnotation, updateAnnotations]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const reorderAnnotations = useCallback(
|
||||||
|
(
|
||||||
|
sourceAnnotation: EventAnnotationConfig | undefined,
|
||||||
|
targetAnnotation: EventAnnotationConfig
|
||||||
|
) => {
|
||||||
|
if (!sourceAnnotation || sourceAnnotation.id === targetAnnotation.id) {
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
const newAnnotations = annotations.filter((c) => c.id !== sourceAnnotation.id);
|
||||||
|
const targetPosition = newAnnotations.findIndex((c) => c.id === targetAnnotation.id);
|
||||||
|
const targetIndex = annotations.indexOf(sourceAnnotation);
|
||||||
|
const sourceIndex = annotations.indexOf(targetAnnotation);
|
||||||
|
newAnnotations.splice(
|
||||||
|
targetIndex < sourceIndex ? targetPosition + 1 : targetPosition,
|
||||||
|
0,
|
||||||
|
sourceAnnotation
|
||||||
|
);
|
||||||
|
return updateAnnotations(newAnnotations);
|
||||||
|
},
|
||||||
|
[annotations, updateAnnotations]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [{ dragging }] = useDragDropContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{annotations.map((annotation, index) => (
|
<ReorderProvider>
|
||||||
<div
|
{annotations.map((annotation, index) => (
|
||||||
key={index}
|
<div
|
||||||
css={css`
|
key={index}
|
||||||
margin-top: ${euiThemeVars.euiSizeS};
|
css={css`
|
||||||
position: relative; // this is to properly contain the absolutely-positioned drop target in DragDrop
|
margin-top: ${euiThemeVars.euiSizeS};
|
||||||
`}
|
position: relative; // this is to properly contain the absolutely-positioned drop target in DragDrop
|
||||||
>
|
`}
|
||||||
<DragDrop
|
|
||||||
order={[index]}
|
|
||||||
key={annotation.id}
|
|
||||||
value={{
|
|
||||||
id: annotation.id,
|
|
||||||
humanData: {
|
|
||||||
label: annotation.label,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
dragType="copy"
|
|
||||||
dropTypes={[]}
|
|
||||||
draggable
|
|
||||||
>
|
>
|
||||||
<DimensionButton
|
<DragDrop
|
||||||
groupLabel={i18n.translate('eventAnnotation.groupEditor.addAnnotation', {
|
order={[index]}
|
||||||
defaultMessage: 'Annotations',
|
key={annotation.id}
|
||||||
})}
|
value={{
|
||||||
onClick={() => selectAnnotation(annotation)}
|
id: annotation.id,
|
||||||
onRemoveClick={() =>
|
humanData: {
|
||||||
updateAnnotations(annotations.filter(({ id }) => id !== annotation.id))
|
label: annotation.label,
|
||||||
}
|
},
|
||||||
accessorConfig={getAnnotationAccessor(annotation)}
|
}}
|
||||||
label={annotation.label}
|
dragType="move"
|
||||||
|
dropTypes={dragging && dragging.id !== annotation.id ? ['reorder'] : []}
|
||||||
|
draggable
|
||||||
|
reorderableGroup={annotations}
|
||||||
|
onDrop={(source) => {
|
||||||
|
const sourceAnnotation = source
|
||||||
|
? annotations.find(({ id }) => id === source.id)
|
||||||
|
: undefined;
|
||||||
|
reorderAnnotations(sourceAnnotation, annotation);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<DimensionTrigger label={annotation.label} />
|
<DimensionButton
|
||||||
</DimensionButton>
|
groupLabel={i18n.translate('eventAnnotation.groupEditor.addAnnotation', {
|
||||||
</DragDrop>
|
defaultMessage: 'Annotations',
|
||||||
</div>
|
})}
|
||||||
))}
|
onClick={() => selectAnnotation(annotation)}
|
||||||
|
onRemoveClick={() =>
|
||||||
|
updateAnnotations(annotations.filter(({ id }) => id !== annotation.id))
|
||||||
|
}
|
||||||
|
accessorConfig={getAnnotationAccessor(annotation)}
|
||||||
|
label={annotation.label}
|
||||||
|
>
|
||||||
|
<DimensionTrigger label={annotation.label} />
|
||||||
|
</DimensionButton>
|
||||||
|
</DragDrop>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ReorderProvider>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
css={css`
|
css={css`
|
||||||
|
@ -110,7 +146,7 @@ export const AnnotationList = ({
|
||||||
getAdditionalClassesOnDroppable={
|
getAdditionalClassesOnDroppable={
|
||||||
DropTargetSwapDuplicateCombine.getAdditionalClassesOnDroppable
|
DropTargetSwapDuplicateCombine.getAdditionalClassesOnDroppable
|
||||||
}
|
}
|
||||||
dropTypes={dragging ? ['field_add'] : []}
|
dropTypes={dragging ? ['duplicate_compatible'] : []}
|
||||||
value={{
|
value={{
|
||||||
id: 'addAnnotation',
|
id: 'addAnnotation',
|
||||||
humanData: {
|
humanData: {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { createMockedDragDropContext } from './mocks';
|
import { createMockedDragDropContext } from '../../mocks';
|
||||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||||
import {
|
import {
|
||||||
dataViewPluginMocks,
|
dataViewPluginMocks,
|
||||||
|
@ -336,7 +336,6 @@ describe('FormBased Data Panel', () => {
|
||||||
fieldFormats: fieldFormatsServiceMock.createStartContract(),
|
fieldFormats: fieldFormatsServiceMock.createStartContract(),
|
||||||
indexPatternFieldEditor: indexPatternFieldEditorPluginMock.createStartContract(),
|
indexPatternFieldEditor: indexPatternFieldEditorPluginMock.createStartContract(),
|
||||||
onIndexPatternRefresh: jest.fn(),
|
onIndexPatternRefresh: jest.fn(),
|
||||||
dragDropContext: createMockedDragDropContext(),
|
|
||||||
currentIndexPatternId: '1',
|
currentIndexPatternId: '1',
|
||||||
core,
|
core,
|
||||||
dateRange: {
|
dateRange: {
|
||||||
|
@ -387,10 +386,6 @@ describe('FormBased Data Panel', () => {
|
||||||
currentIndexPatternId: '',
|
currentIndexPatternId: '',
|
||||||
}}
|
}}
|
||||||
setState={jest.fn()}
|
setState={jest.fn()}
|
||||||
dragDropContext={{
|
|
||||||
...createMockedDragDropContext(),
|
|
||||||
dragging: { id: '1', humanData: { label: 'Label' } },
|
|
||||||
}}
|
|
||||||
frame={createMockFramePublicAPI()}
|
frame={createMockFramePublicAPI()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -413,10 +408,9 @@ describe('FormBased Data Panel', () => {
|
||||||
dataViews,
|
dataViews,
|
||||||
}),
|
}),
|
||||||
setState: jest.fn(),
|
setState: jest.fn(),
|
||||||
dragDropContext: {
|
dragDropContext: createMockedDragDropContext({
|
||||||
...createMockedDragDropContext(),
|
|
||||||
dragging: { id: '1', humanData: { label: 'Label' } },
|
dragging: { id: '1', humanData: { label: 'Label' } },
|
||||||
},
|
}),
|
||||||
dateRange: { fromDate: '2019-01-01', toDate: '2020-01-01' },
|
dateRange: { fromDate: '2019-01-01', toDate: '2020-01-01' },
|
||||||
frame: getFrameAPIMock({
|
frame: getFrameAPIMock({
|
||||||
indexPatterns: indexPatterns as unknown as DataViewsState['indexPatterns'],
|
indexPatterns: indexPatterns as unknown as DataViewsState['indexPatterns'],
|
||||||
|
|
|
@ -27,7 +27,6 @@ import {
|
||||||
useExistingFieldsFetcher,
|
useExistingFieldsFetcher,
|
||||||
useGroupedFields,
|
useGroupedFields,
|
||||||
} from '@kbn/unified-field-list';
|
} from '@kbn/unified-field-list';
|
||||||
import { ChildDragDropProvider, DragContextState } from '@kbn/dom-drag-drop';
|
|
||||||
import { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
import { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
||||||
import type {
|
import type {
|
||||||
DatasourceDataPanelProps,
|
DatasourceDataPanelProps,
|
||||||
|
@ -41,7 +40,7 @@ import { FieldItem } from '../common/field_item';
|
||||||
|
|
||||||
export type Props = Omit<
|
export type Props = Omit<
|
||||||
DatasourceDataPanelProps<FormBasedPrivateState>,
|
DatasourceDataPanelProps<FormBasedPrivateState>,
|
||||||
'core' | 'onChangeIndexPattern'
|
'core' | 'onChangeIndexPattern' | 'dragDropContext'
|
||||||
> & {
|
> & {
|
||||||
data: DataPublicPluginStart;
|
data: DataPublicPluginStart;
|
||||||
dataViews: DataViewsPublicPluginStart;
|
dataViews: DataViewsPublicPluginStart;
|
||||||
|
@ -77,7 +76,6 @@ function onSupportedFieldFilter(field: IndexPatternField): boolean {
|
||||||
|
|
||||||
export function FormBasedDataPanel({
|
export function FormBasedDataPanel({
|
||||||
state,
|
state,
|
||||||
dragDropContext,
|
|
||||||
core,
|
core,
|
||||||
data,
|
data,
|
||||||
dataViews,
|
dataViews,
|
||||||
|
@ -144,7 +142,6 @@ export function FormBasedDataPanel({
|
||||||
query={query}
|
query={query}
|
||||||
dateRange={dateRange}
|
dateRange={dateRange}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
dragDropContext={dragDropContext}
|
|
||||||
core={core}
|
core={core}
|
||||||
data={data}
|
data={data}
|
||||||
dataViews={dataViews}
|
dataViews={dataViews}
|
||||||
|
@ -171,7 +168,6 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
|
||||||
query,
|
query,
|
||||||
dateRange,
|
dateRange,
|
||||||
filters,
|
filters,
|
||||||
dragDropContext,
|
|
||||||
core,
|
core,
|
||||||
data,
|
data,
|
||||||
dataViews,
|
dataViews,
|
||||||
|
@ -187,14 +183,13 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
|
||||||
activeIndexPatterns,
|
activeIndexPatterns,
|
||||||
}: Omit<
|
}: Omit<
|
||||||
DatasourceDataPanelProps,
|
DatasourceDataPanelProps,
|
||||||
'state' | 'setState' | 'core' | 'onChangeIndexPattern' | 'usedIndexPatterns'
|
'state' | 'setState' | 'core' | 'onChangeIndexPattern' | 'usedIndexPatterns' | 'dragDropContext'
|
||||||
> & {
|
> & {
|
||||||
data: DataPublicPluginStart;
|
data: DataPublicPluginStart;
|
||||||
dataViews: DataViewsPublicPluginStart;
|
dataViews: DataViewsPublicPluginStart;
|
||||||
fieldFormats: FieldFormatsStart;
|
fieldFormats: FieldFormatsStart;
|
||||||
core: CoreStart;
|
core: CoreStart;
|
||||||
currentIndexPatternId: string;
|
currentIndexPatternId: string;
|
||||||
dragDropContext: DragContextState;
|
|
||||||
charts: ChartsPluginSetup;
|
charts: ChartsPluginSetup;
|
||||||
frame: FramePublicAPI;
|
frame: FramePublicAPI;
|
||||||
indexPatternFieldEditor: IndexPatternFieldEditorStart;
|
indexPatternFieldEditor: IndexPatternFieldEditorStart;
|
||||||
|
@ -398,20 +393,18 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChildDragDropProvider {...dragDropContext}>
|
<FieldList
|
||||||
<FieldList
|
className="lnsInnerIndexPatternDataPanel"
|
||||||
className="lnsInnerIndexPatternDataPanel"
|
isProcessing={isProcessing}
|
||||||
isProcessing={isProcessing}
|
prepend={<FieldListFilters {...fieldListFiltersProps} data-test-subj="lnsIndexPattern" />}
|
||||||
prepend={<FieldListFilters {...fieldListFiltersProps} data-test-subj="lnsIndexPattern" />}
|
>
|
||||||
>
|
<FieldListGrouped<IndexPatternField>
|
||||||
<FieldListGrouped<IndexPatternField>
|
{...fieldListGroupedProps}
|
||||||
{...fieldListGroupedProps}
|
renderFieldItem={renderFieldItem}
|
||||||
renderFieldItem={renderFieldItem}
|
data-test-subj="lnsIndexPattern"
|
||||||
data-test-subj="lnsIndexPattern"
|
localStorageKeyPrefix="lens"
|
||||||
localStorageKeyPrefix="lens"
|
/>
|
||||||
/>
|
</FieldList>
|
||||||
</FieldList>
|
|
||||||
</ChildDragDropProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,9 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||||
import { EuiButton } from '@elastic/eui';
|
import { EuiButton } from '@elastic/eui';
|
||||||
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
||||||
import type { DraggingIdentifier } from '@kbn/dom-drag-drop';
|
import { ChildDragDropProvider, type DraggingIdentifier } from '@kbn/dom-drag-drop';
|
||||||
import { DimensionTrigger } from '@kbn/visualization-ui-components/public';
|
import { DimensionTrigger } from '@kbn/visualization-ui-components/public';
|
||||||
|
import memoizeOne from 'memoize-one';
|
||||||
import type {
|
import type {
|
||||||
DatasourceDimensionEditorProps,
|
DatasourceDimensionEditorProps,
|
||||||
DatasourceDimensionTriggerProps,
|
DatasourceDimensionTriggerProps,
|
||||||
|
@ -72,7 +73,7 @@ import {
|
||||||
cloneLayer,
|
cloneLayer,
|
||||||
getNotifiableFeatures,
|
getNotifiableFeatures,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { getUniqueLabelGenerator, isDraggedDataViewField } from '../../utils';
|
import { getUniqueLabelGenerator, isDraggedDataViewField, nonNullable } from '../../utils';
|
||||||
import { hasField, normalizeOperationDataType } from './pure_utils';
|
import { hasField, normalizeOperationDataType } from './pure_utils';
|
||||||
import { LayerPanel } from './layerpanel';
|
import { LayerPanel } from './layerpanel';
|
||||||
import {
|
import {
|
||||||
|
@ -112,6 +113,20 @@ function wrapOnDot(str?: string) {
|
||||||
return str ? str.replace(/\./g, '.\u200B') : '';
|
return str ? str.replace(/\./g, '.\u200B') : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getSelectedFieldsFromColumns = memoizeOne(
|
||||||
|
(columns: GenericIndexPatternColumn[]) =>
|
||||||
|
columns
|
||||||
|
.flatMap((c) => {
|
||||||
|
if (operationDefinitionMap[c.operationType]?.getCurrentFields) {
|
||||||
|
return operationDefinitionMap[c.operationType]?.getCurrentFields?.(c) || [];
|
||||||
|
} else if ('sourceField' in c) {
|
||||||
|
return c.sourceField;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(nonNullable),
|
||||||
|
isEqual
|
||||||
|
);
|
||||||
|
|
||||||
function getSortingHint(column: GenericIndexPatternColumn, dataView?: IndexPattern | DataView) {
|
function getSortingHint(column: GenericIndexPatternColumn, dataView?: IndexPattern | DataView) {
|
||||||
if (column.dataType === 'string') {
|
if (column.dataType === 'string') {
|
||||||
const fieldTypes =
|
const fieldTypes =
|
||||||
|
@ -421,18 +436,9 @@ export function getFormBasedDatasource({
|
||||||
},
|
},
|
||||||
|
|
||||||
getSelectedFields(state) {
|
getSelectedFields(state) {
|
||||||
const fields: string[] = [];
|
return getSelectedFieldsFromColumns(
|
||||||
Object.values(state?.layers)?.forEach((l) => {
|
Object.values(state?.layers)?.flatMap((l) => Object.values(l.columns))
|
||||||
const { columns } = l;
|
);
|
||||||
Object.values(columns).forEach((c) => {
|
|
||||||
if (operationDefinitionMap[c.operationType]?.getCurrentFields) {
|
|
||||||
fields.push(...(operationDefinitionMap[c.operationType]?.getCurrentFields?.(c) || []));
|
|
||||||
} else if ('sourceField' in c) {
|
|
||||||
fields.push(c.sourceField);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return fields;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
toExpression: (state, layerId, indexPatterns, dateRange, nowInstant, searchSessionId) =>
|
toExpression: (state, layerId, indexPatterns, dateRange, nowInstant, searchSessionId) =>
|
||||||
|
@ -470,7 +476,7 @@ export function getFormBasedDatasource({
|
||||||
},
|
},
|
||||||
|
|
||||||
renderDataPanel(domElement: Element, props: DatasourceDataPanelProps<FormBasedPrivateState>) {
|
renderDataPanel(domElement: Element, props: DatasourceDataPanelProps<FormBasedPrivateState>) {
|
||||||
const { onChangeIndexPattern, ...otherProps } = props;
|
const { onChangeIndexPattern, dragDropContext, ...otherProps } = props;
|
||||||
const layerFields = formBasedDatasource?.getSelectedFields?.(props.state);
|
const layerFields = formBasedDatasource?.getSelectedFields?.(props.state);
|
||||||
|
|
||||||
render(
|
render(
|
||||||
|
@ -487,18 +493,20 @@ export function getFormBasedDatasource({
|
||||||
share,
|
share,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormBasedDataPanel
|
<ChildDragDropProvider value={dragDropContext}>
|
||||||
data={data}
|
<FormBasedDataPanel
|
||||||
dataViews={dataViews}
|
data={data}
|
||||||
fieldFormats={fieldFormats}
|
dataViews={dataViews}
|
||||||
charts={charts}
|
fieldFormats={fieldFormats}
|
||||||
indexPatternFieldEditor={dataViewFieldEditor}
|
charts={charts}
|
||||||
{...otherProps}
|
indexPatternFieldEditor={dataViewFieldEditor}
|
||||||
core={core}
|
{...otherProps}
|
||||||
uiActions={uiActions}
|
core={core}
|
||||||
onIndexPatternRefresh={onRefreshIndexPattern}
|
uiActions={uiActions}
|
||||||
layerFields={layerFields}
|
onIndexPatternRefresh={onRefreshIndexPattern}
|
||||||
/>
|
layerFields={layerFields}
|
||||||
|
/>
|
||||||
|
</ChildDragDropProvider>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
</KibanaThemeProvider>,
|
</KibanaThemeProvider>,
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DragContextState } from '@kbn/dom-drag-drop';
|
|
||||||
import { getFieldByNameFactory } from './pure_helpers';
|
import { getFieldByNameFactory } from './pure_helpers';
|
||||||
import type { IndexPattern, IndexPatternField } from '../../types';
|
import type { IndexPattern, IndexPatternField } from '../../types';
|
||||||
|
|
||||||
|
@ -217,18 +216,3 @@ export const createMockedIndexPatternWithoutType = (
|
||||||
getFieldByName: getFieldByNameFactory(filteredFields),
|
getFieldByName: getFieldByNameFactory(filteredFields),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createMockedDragDropContext(): jest.Mocked<DragContextState> {
|
|
||||||
return {
|
|
||||||
dataTestSubjPrefix: 'lnsDragDrop',
|
|
||||||
dragging: undefined,
|
|
||||||
setDragging: jest.fn(),
|
|
||||||
activeDropTarget: undefined,
|
|
||||||
setActiveDropTarget: jest.fn(),
|
|
||||||
keyboardMode: false,
|
|
||||||
setKeyboardMode: jest.fn(),
|
|
||||||
setA11yMessage: jest.fn(),
|
|
||||||
dropTargetsByOrder: undefined,
|
|
||||||
registerDropTarget: jest.fn(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -28,8 +28,7 @@ import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||||
|
|
||||||
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
|
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
|
||||||
import { createIndexPatternServiceMock } from '../../mocks/data_views_service_mock';
|
import { createIndexPatternServiceMock } from '../../mocks/data_views_service_mock';
|
||||||
import { createMockFramePublicAPI } from '../../mocks';
|
import { createMockFramePublicAPI, createMockedDragDropContext } from '../../mocks';
|
||||||
import { createMockedDragDropContext } from './mocks';
|
|
||||||
import { DataViewsState } from '../../state_management';
|
import { DataViewsState } from '../../state_management';
|
||||||
|
|
||||||
const fieldsFromQuery = [
|
const fieldsFromQuery = [
|
||||||
|
|
|
@ -24,7 +24,6 @@ import {
|
||||||
GetCustomFieldType,
|
GetCustomFieldType,
|
||||||
useGroupedFields,
|
useGroupedFields,
|
||||||
} from '@kbn/unified-field-list';
|
} from '@kbn/unified-field-list';
|
||||||
import { ChildDragDropProvider } from '@kbn/dom-drag-drop';
|
|
||||||
import type { DatasourceDataPanelProps } from '../../types';
|
import type { DatasourceDataPanelProps } from '../../types';
|
||||||
import type { TextBasedPrivateState } from './types';
|
import type { TextBasedPrivateState } from './types';
|
||||||
import { getStateFromAggregateQuery } from './utils';
|
import { getStateFromAggregateQuery } from './utils';
|
||||||
|
@ -42,7 +41,6 @@ export type TextBasedDataPanelProps = DatasourceDataPanelProps<TextBasedPrivateS
|
||||||
export function TextBasedDataPanel({
|
export function TextBasedDataPanel({
|
||||||
setState,
|
setState,
|
||||||
state,
|
state,
|
||||||
dragDropContext,
|
|
||||||
core,
|
core,
|
||||||
data,
|
data,
|
||||||
query,
|
query,
|
||||||
|
@ -54,7 +52,7 @@ export function TextBasedDataPanel({
|
||||||
layerFields,
|
layerFields,
|
||||||
hasSuggestionForField,
|
hasSuggestionForField,
|
||||||
dropOntoWorkspace,
|
dropOntoWorkspace,
|
||||||
}: TextBasedDataPanelProps) {
|
}: Omit<TextBasedDataPanelProps, 'dragDropContext'>) {
|
||||||
const prevQuery = usePrevious(query);
|
const prevQuery = usePrevious(query);
|
||||||
const [dataHasLoaded, setDataHasLoaded] = useState(false);
|
const [dataHasLoaded, setDataHasLoaded] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -138,22 +136,20 @@ export function TextBasedDataPanel({
|
||||||
...core,
|
...core,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ChildDragDropProvider {...dragDropContext}>
|
<FieldList
|
||||||
<FieldList
|
className="lnsInnerIndexPatternDataPanel"
|
||||||
className="lnsInnerIndexPatternDataPanel"
|
isProcessing={!dataHasLoaded}
|
||||||
isProcessing={!dataHasLoaded}
|
prepend={
|
||||||
prepend={
|
<FieldListFilters {...fieldListFiltersProps} data-test-subj="lnsTextBasedLanguages" />
|
||||||
<FieldListFilters {...fieldListFiltersProps} data-test-subj="lnsTextBasedLanguages" />
|
}
|
||||||
}
|
>
|
||||||
>
|
<FieldListGrouped<DatatableColumn>
|
||||||
<FieldListGrouped<DatatableColumn>
|
{...fieldListGroupedProps}
|
||||||
{...fieldListGroupedProps}
|
renderFieldItem={renderFieldItem}
|
||||||
renderFieldItem={renderFieldItem}
|
data-test-subj="lnsTextBasedLanguages"
|
||||||
data-test-subj="lnsTextBasedLanguages"
|
localStorageKeyPrefix="lens"
|
||||||
localStorageKeyPrefix="lens"
|
/>
|
||||||
/>
|
</FieldList>
|
||||||
</FieldList>
|
|
||||||
</ChildDragDropProvider>
|
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { DragContextState } from '@kbn/dom-drag-drop';
|
|
||||||
|
|
||||||
export function createMockedDragDropContext(): jest.Mocked<DragContextState> {
|
|
||||||
return {
|
|
||||||
dataTestSubjPrefix: 'lnsDragDrop',
|
|
||||||
dragging: undefined,
|
|
||||||
setDragging: jest.fn(),
|
|
||||||
activeDropTarget: undefined,
|
|
||||||
setActiveDropTarget: jest.fn(),
|
|
||||||
keyboardMode: false,
|
|
||||||
setKeyboardMode: jest.fn(),
|
|
||||||
setA11yMessage: jest.fn(),
|
|
||||||
dropTargetsByOrder: undefined,
|
|
||||||
registerDropTarget: jest.fn(),
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -20,6 +20,9 @@ import type { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugi
|
||||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||||
import { euiThemeVars } from '@kbn/ui-theme';
|
import { euiThemeVars } from '@kbn/ui-theme';
|
||||||
import { DimensionTrigger } from '@kbn/visualization-ui-components/public';
|
import { DimensionTrigger } from '@kbn/visualization-ui-components/public';
|
||||||
|
import memoizeOne from 'memoize-one';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
import { ChildDragDropProvider } from '@kbn/dom-drag-drop';
|
||||||
import {
|
import {
|
||||||
DatasourceDimensionEditorProps,
|
DatasourceDimensionEditorProps,
|
||||||
DatasourceDataPanelProps,
|
DatasourceDataPanelProps,
|
||||||
|
@ -43,12 +46,24 @@ import type {
|
||||||
import { FieldSelect } from './field_select';
|
import { FieldSelect } from './field_select';
|
||||||
import type { Datasource, IndexPatternMap } from '../../types';
|
import type { Datasource, IndexPatternMap } from '../../types';
|
||||||
import { LayerPanel } from './layerpanel';
|
import { LayerPanel } from './layerpanel';
|
||||||
import { getUniqueLabelGenerator } from '../../utils';
|
import { getUniqueLabelGenerator, nonNullable } from '../../utils';
|
||||||
|
|
||||||
function getLayerReferenceName(layerId: string) {
|
function getLayerReferenceName(layerId: string) {
|
||||||
return `textBasedLanguages-datasource-layer-${layerId}`;
|
return `textBasedLanguages-datasource-layer-${layerId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getSelectedFieldsFromColumns = memoizeOne(
|
||||||
|
(columns: TextBasedLayerColumn[]) =>
|
||||||
|
columns
|
||||||
|
.map((c) => {
|
||||||
|
if ('fieldName' in c) {
|
||||||
|
return c.fieldName;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(nonNullable),
|
||||||
|
isEqual
|
||||||
|
);
|
||||||
|
|
||||||
export function getTextBasedDatasource({
|
export function getTextBasedDatasource({
|
||||||
core,
|
core,
|
||||||
storage,
|
storage,
|
||||||
|
@ -344,30 +359,26 @@ export function getTextBasedDatasource({
|
||||||
return toExpression(state, layerId);
|
return toExpression(state, layerId);
|
||||||
},
|
},
|
||||||
getSelectedFields(state) {
|
getSelectedFields(state) {
|
||||||
const fields: string[] = [];
|
return getSelectedFieldsFromColumns(
|
||||||
Object.values(state?.layers)?.forEach((l) => {
|
Object.values(state?.layers)?.flatMap((l) => Object.values(l.columns))
|
||||||
const { columns } = l;
|
);
|
||||||
Object.values(columns).forEach((c) => {
|
|
||||||
if ('fieldName' in c) {
|
|
||||||
fields.push(c.fieldName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return fields;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
renderDataPanel(domElement: Element, props: DatasourceDataPanelProps<TextBasedPrivateState>) {
|
renderDataPanel(domElement: Element, props: DatasourceDataPanelProps<TextBasedPrivateState>) {
|
||||||
const layerFields = TextBasedDatasource?.getSelectedFields?.(props.state);
|
const layerFields = TextBasedDatasource?.getSelectedFields?.(props.state);
|
||||||
|
const { dragDropContext, ...otherProps } = props;
|
||||||
render(
|
render(
|
||||||
<KibanaThemeProvider theme$={core.theme.theme$}>
|
<KibanaThemeProvider theme$={core.theme.theme$}>
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<TextBasedDataPanel
|
<ChildDragDropProvider value={dragDropContext}>
|
||||||
data={data}
|
<TextBasedDataPanel
|
||||||
dataViews={dataViews}
|
data={data}
|
||||||
expressions={expressions}
|
dataViews={dataViews}
|
||||||
layerFields={layerFields}
|
expressions={expressions}
|
||||||
{...props}
|
layerFields={layerFields}
|
||||||
/>
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
</ChildDragDropProvider>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
</KibanaThemeProvider>,
|
</KibanaThemeProvider>,
|
||||||
domElement
|
domElement
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo, useCallback, useContext, ReactElement } from 'react';
|
import React, { useMemo, useCallback, ReactElement } from 'react';
|
||||||
import {
|
import {
|
||||||
DragDrop,
|
DragDrop,
|
||||||
DragDropIdentifier,
|
DragDropIdentifier,
|
||||||
DragContext,
|
useDragDropContext,
|
||||||
DropType,
|
DropType,
|
||||||
DropTargetSwapDuplicateCombine,
|
DropTargetSwapDuplicateCombine,
|
||||||
} from '@kbn/dom-drag-drop';
|
} from '@kbn/dom-drag-drop';
|
||||||
|
@ -61,7 +61,7 @@ export function DraggableDimensionButton({
|
||||||
registerNewButtonRef: (id: string, instance: HTMLDivElement | null) => void;
|
registerNewButtonRef: (id: string, instance: HTMLDivElement | null) => void;
|
||||||
indexPatterns: IndexPatternMap;
|
indexPatterns: IndexPatternMap;
|
||||||
}) {
|
}) {
|
||||||
const { dragging } = useContext(DragContext);
|
const [{ dragging }] = useDragDropContext();
|
||||||
|
|
||||||
let getDropProps;
|
let getDropProps;
|
||||||
|
|
||||||
|
@ -139,20 +139,20 @@ export function DraggableDimensionButton({
|
||||||
data-test-subj={group.dataTestSubj}
|
data-test-subj={group.dataTestSubj}
|
||||||
>
|
>
|
||||||
<DragDrop
|
<DragDrop
|
||||||
|
draggable
|
||||||
getCustomDropTarget={DropTargetSwapDuplicateCombine.getCustomDropTarget}
|
getCustomDropTarget={DropTargetSwapDuplicateCombine.getCustomDropTarget}
|
||||||
getAdditionalClassesOnEnter={DropTargetSwapDuplicateCombine.getAdditionalClassesOnEnter}
|
getAdditionalClassesOnEnter={DropTargetSwapDuplicateCombine.getAdditionalClassesOnEnter}
|
||||||
getAdditionalClassesOnDroppable={
|
getAdditionalClassesOnDroppable={
|
||||||
DropTargetSwapDuplicateCombine.getAdditionalClassesOnDroppable
|
DropTargetSwapDuplicateCombine.getAdditionalClassesOnDroppable
|
||||||
}
|
}
|
||||||
order={order}
|
order={order}
|
||||||
draggable
|
|
||||||
dragType={isOperation(dragging) ? 'move' : 'copy'}
|
dragType={isOperation(dragging) ? 'move' : 'copy'}
|
||||||
dropTypes={dropTypes}
|
dropTypes={dropTypes}
|
||||||
reorderableGroup={reorderableGroup.length > 1 ? reorderableGroup : undefined}
|
reorderableGroup={reorderableGroup.length > 1 ? reorderableGroup : undefined}
|
||||||
value={value}
|
value={value}
|
||||||
onDrop={handleOnDrop}
|
onDrop={handleOnDrop}
|
||||||
onDragStart={() => onDragStart()}
|
onDragStart={onDragStart}
|
||||||
onDragEnd={() => onDragEnd()}
|
onDragEnd={onDragEnd}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</DragDrop>
|
</DragDrop>
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo, useState, useEffect, useContext } from 'react';
|
import React, { useMemo, useState, useEffect } from 'react';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import {
|
import {
|
||||||
DragDrop,
|
DragDrop,
|
||||||
DragDropIdentifier,
|
DragDropIdentifier,
|
||||||
DragContext,
|
useDragDropContext,
|
||||||
DropType,
|
DropType,
|
||||||
DropTargetSwapDuplicateCombine,
|
DropTargetSwapDuplicateCombine,
|
||||||
} from '@kbn/dom-drag-drop';
|
} from '@kbn/dom-drag-drop';
|
||||||
|
@ -116,7 +116,7 @@ export function EmptyDimensionButton({
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}) {
|
}) {
|
||||||
const { dragging } = useContext(DragContext);
|
const [{ dragging }] = useDragDropContext();
|
||||||
|
|
||||||
let getDropProps;
|
let getDropProps;
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
createMockDatasource,
|
createMockDatasource,
|
||||||
DatasourceMock,
|
DatasourceMock,
|
||||||
mountWithProvider,
|
mountWithProvider,
|
||||||
|
createMockedDragDropContext,
|
||||||
} from '../../../mocks';
|
} from '../../../mocks';
|
||||||
import { createIndexPatternServiceMock } from '../../../mocks/data_views_service_mock';
|
import { createIndexPatternServiceMock } from '../../../mocks/data_views_service_mock';
|
||||||
import { DimensionButton } from '@kbn/visualization-ui-components/public';
|
import { DimensionButton } from '@kbn/visualization-ui-components/public';
|
||||||
|
@ -52,19 +53,6 @@ afterEach(() => {
|
||||||
container = undefined;
|
container = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultContext = {
|
|
||||||
dataTestSubjPrefix: 'lnsDragDrop',
|
|
||||||
dragging: undefined,
|
|
||||||
setDragging: jest.fn(),
|
|
||||||
setActiveDropTarget: () => {},
|
|
||||||
activeDropTarget: undefined,
|
|
||||||
dropTargetsByOrder: undefined,
|
|
||||||
keyboardMode: false,
|
|
||||||
setKeyboardMode: () => {},
|
|
||||||
setA11yMessage: jest.fn(),
|
|
||||||
registerDropTarget: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const draggingField = {
|
const draggingField = {
|
||||||
field: { name: 'dragged' },
|
field: { name: 'dragged' },
|
||||||
indexPatternId: 'a',
|
indexPatternId: 'a',
|
||||||
|
@ -773,7 +761,7 @@ describe('LayerPanel', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const { instance } = await mountWithProvider(
|
const { instance } = await mountWithProvider(
|
||||||
<ChildDragDropProvider {...defaultContext} dragging={draggingField}>
|
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingField })}>
|
||||||
<LayerPanel {...getDefaultProps()} />
|
<LayerPanel {...getDefaultProps()} />
|
||||||
</ChildDragDropProvider>
|
</ChildDragDropProvider>
|
||||||
);
|
);
|
||||||
|
@ -817,7 +805,7 @@ describe('LayerPanel', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const { instance } = await mountWithProvider(
|
const { instance } = await mountWithProvider(
|
||||||
<ChildDragDropProvider {...defaultContext} dragging={draggingField}>
|
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingField })}>
|
||||||
<LayerPanel {...getDefaultProps()} />
|
<LayerPanel {...getDefaultProps()} />
|
||||||
</ChildDragDropProvider>
|
</ChildDragDropProvider>
|
||||||
);
|
);
|
||||||
|
@ -886,7 +874,7 @@ describe('LayerPanel', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const { instance } = await mountWithProvider(
|
const { instance } = await mountWithProvider(
|
||||||
<ChildDragDropProvider {...defaultContext} dragging={draggingOperation}>
|
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||||
<LayerPanel {...getDefaultProps()} />
|
<LayerPanel {...getDefaultProps()} />
|
||||||
</ChildDragDropProvider>
|
</ChildDragDropProvider>
|
||||||
);
|
);
|
||||||
|
@ -956,7 +944,7 @@ describe('LayerPanel', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const { instance } = await mountWithProvider(
|
const { instance } = await mountWithProvider(
|
||||||
<ChildDragDropProvider {...defaultContext} dragging={draggingOperation}>
|
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||||
<LayerPanel {...getDefaultProps()} />
|
<LayerPanel {...getDefaultProps()} />
|
||||||
</ChildDragDropProvider>,
|
</ChildDragDropProvider>,
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -1009,7 +997,7 @@ describe('LayerPanel', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const { instance } = await mountWithProvider(
|
const { instance } = await mountWithProvider(
|
||||||
<ChildDragDropProvider {...defaultContext} dragging={draggingOperation}>
|
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||||
<LayerPanel {...getDefaultProps()} />
|
<LayerPanel {...getDefaultProps()} />
|
||||||
</ChildDragDropProvider>
|
</ChildDragDropProvider>
|
||||||
);
|
);
|
||||||
|
@ -1064,7 +1052,7 @@ describe('LayerPanel', () => {
|
||||||
const mockOnRemoveDimension = jest.fn();
|
const mockOnRemoveDimension = jest.fn();
|
||||||
|
|
||||||
const { instance } = await mountWithProvider(
|
const { instance } = await mountWithProvider(
|
||||||
<ChildDragDropProvider {...defaultContext} dragging={draggingOperation}>
|
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||||
<LayerPanel
|
<LayerPanel
|
||||||
{...getDefaultProps()}
|
{...getDefaultProps()}
|
||||||
onRemoveDimension={mockOnRemoveDimension}
|
onRemoveDimension={mockOnRemoveDimension}
|
||||||
|
@ -1139,7 +1127,7 @@ describe('LayerPanel', () => {
|
||||||
const mockOnRemoveDimension = jest.fn();
|
const mockOnRemoveDimension = jest.fn();
|
||||||
|
|
||||||
const { instance } = await mountWithProvider(
|
const { instance } = await mountWithProvider(
|
||||||
<ChildDragDropProvider {...defaultContext} dragging={draggingOperation}>
|
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||||
<LayerPanel
|
<LayerPanel
|
||||||
{...getDefaultProps()}
|
{...getDefaultProps()}
|
||||||
onRemoveDimension={mockOnRemoveDimension}
|
onRemoveDimension={mockOnRemoveDimension}
|
||||||
|
@ -1221,7 +1209,7 @@ describe('LayerPanel', () => {
|
||||||
const mockOnRemoveDimension = jest.fn();
|
const mockOnRemoveDimension = jest.fn();
|
||||||
|
|
||||||
const { instance } = await mountWithProvider(
|
const { instance } = await mountWithProvider(
|
||||||
<ChildDragDropProvider {...defaultContext} dragging={draggingOperation}>
|
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||||
<LayerPanel
|
<LayerPanel
|
||||||
{...getDefaultProps()}
|
{...getDefaultProps()}
|
||||||
onRemoveDimension={mockOnRemoveDimension}
|
onRemoveDimension={mockOnRemoveDimension}
|
||||||
|
@ -1284,7 +1272,7 @@ describe('LayerPanel', () => {
|
||||||
const mockOnRemoveDimension = jest.fn();
|
const mockOnRemoveDimension = jest.fn();
|
||||||
|
|
||||||
const { instance } = await mountWithProvider(
|
const { instance } = await mountWithProvider(
|
||||||
<ChildDragDropProvider {...defaultContext} dragging={draggingOperation}>
|
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||||
<LayerPanel
|
<LayerPanel
|
||||||
{...getDefaultProps()}
|
{...getDefaultProps()}
|
||||||
onRemoveDimension={mockOnRemoveDimension}
|
onRemoveDimension={mockOnRemoveDimension}
|
||||||
|
|
|
@ -547,11 +547,7 @@ export function LayerPanel(
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{group.accessors.length ? (
|
{group.accessors.length ? (
|
||||||
<ReorderProvider
|
<ReorderProvider className={'lnsLayerPanel__group'} dataTestSubj="lnsDragDrop">
|
||||||
id={group.groupId}
|
|
||||||
className={'lnsLayerPanel__group'}
|
|
||||||
dataTestSubj="lnsDragDrop"
|
|
||||||
>
|
|
||||||
{group.accessors.map((accessorConfig, accessorIndex) => {
|
{group.accessors.map((accessorConfig, accessorIndex) => {
|
||||||
const { columnId } = accessorConfig;
|
const { columnId } = accessorConfig;
|
||||||
|
|
||||||
|
@ -644,7 +640,6 @@ export function LayerPanel(
|
||||||
|
|
||||||
{group.fakeFinalAccessor && (
|
{group.fakeFinalAccessor && (
|
||||||
<div
|
<div
|
||||||
className="domDragDrop-isDraggable"
|
|
||||||
css={css`
|
css={css`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -7,12 +7,14 @@
|
||||||
|
|
||||||
import './data_panel_wrapper.scss';
|
import './data_panel_wrapper.scss';
|
||||||
|
|
||||||
import React, { useMemo, memo, useContext, useEffect, useCallback } from 'react';
|
import React, { useMemo, memo, useEffect, useCallback } from 'react';
|
||||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||||
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||||
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
|
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
|
||||||
import { DragContext, DragDropIdentifier } from '@kbn/dom-drag-drop';
|
import { useDragDropContext, DragDropIdentifier } from '@kbn/dom-drag-drop';
|
||||||
|
import memoizeOne from 'memoize-one';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
import { Easteregg } from './easteregg';
|
import { Easteregg } from './easteregg';
|
||||||
import { NativeRenderer } from '../../native_renderer';
|
import { NativeRenderer } from '../../native_renderer';
|
||||||
import {
|
import {
|
||||||
|
@ -54,6 +56,8 @@ interface DataPanelWrapperProps {
|
||||||
frame: FramePublicAPI;
|
frame: FramePublicAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const memoizeStrictlyEqual = memoizeOne((arg) => arg, isEqual);
|
||||||
|
|
||||||
export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => {
|
export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => {
|
||||||
const externalContext = useLensSelector(selectExecutionContext);
|
const externalContext = useLensSelector(selectExecutionContext);
|
||||||
const activeDatasourceId = useLensSelector(selectActiveDatasourceId);
|
const activeDatasourceId = useLensSelector(selectActiveDatasourceId);
|
||||||
|
@ -158,7 +162,7 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => {
|
||||||
|
|
||||||
const datasourceProps: DatasourceDataPanelProps = {
|
const datasourceProps: DatasourceDataPanelProps = {
|
||||||
...externalContext,
|
...externalContext,
|
||||||
dragDropContext: useContext(DragContext),
|
dragDropContext: useDragDropContext(),
|
||||||
state: activeDatasourceId ? datasourceStates[activeDatasourceId].state : null,
|
state: activeDatasourceId ? datasourceStates[activeDatasourceId].state : null,
|
||||||
setState: setDatasourceState,
|
setState: setDatasourceState,
|
||||||
core: props.core,
|
core: props.core,
|
||||||
|
@ -170,7 +174,7 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => {
|
||||||
indexPatternService: props.indexPatternService,
|
indexPatternService: props.indexPatternService,
|
||||||
frame: props.frame,
|
frame: props.frame,
|
||||||
// Visualization can handle dataViews, so need to pass to the data panel the full list of used dataViews
|
// Visualization can handle dataViews, so need to pass to the data panel the full list of used dataViews
|
||||||
usedIndexPatterns: [
|
usedIndexPatterns: memoizeStrictlyEqual([
|
||||||
...((activeDatasourceId &&
|
...((activeDatasourceId &&
|
||||||
props.datasourceMap[activeDatasourceId]?.getUsedDataViews(
|
props.datasourceMap[activeDatasourceId]?.getUsedDataViews(
|
||||||
datasourceStates[activeDatasourceId].state
|
datasourceStates[activeDatasourceId].state
|
||||||
|
@ -181,7 +185,7 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => {
|
||||||
visualizationState.state
|
visualizationState.state
|
||||||
)) ||
|
)) ||
|
||||||
[]),
|
[]),
|
||||||
],
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -820,11 +820,16 @@ describe('editor_frame', () => {
|
||||||
getDatasourceSuggestionsForField: () => [generateSuggestion()],
|
getDatasourceSuggestionsForField: () => [generateSuggestion()],
|
||||||
getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()],
|
getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()],
|
||||||
getDatasourceSuggestionsForVisualizeField: () => [generateSuggestion()],
|
getDatasourceSuggestionsForVisualizeField: () => [generateSuggestion()],
|
||||||
renderDataPanel: (_element, { dragDropContext: { setDragging, dragging } }) => {
|
renderDataPanel: (_element, { dragDropContext: [{ dragging }, dndDispatch] }) => {
|
||||||
if (!dragging || dragging.id !== 'draggedField') {
|
if (!dragging || dragging.id !== 'draggedField') {
|
||||||
setDragging({
|
dndDispatch({
|
||||||
id: 'draggedField',
|
type: 'startDragging',
|
||||||
humanData: { label: 'draggedField' },
|
payload: {
|
||||||
|
dragging: {
|
||||||
|
id: 'draggedField',
|
||||||
|
humanData: { label: 'draggedField' },
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -922,11 +927,16 @@ describe('editor_frame', () => {
|
||||||
getDatasourceSuggestionsForField: () => [generateSuggestion()],
|
getDatasourceSuggestionsForField: () => [generateSuggestion()],
|
||||||
getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()],
|
getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()],
|
||||||
getDatasourceSuggestionsForVisualizeField: () => [generateSuggestion()],
|
getDatasourceSuggestionsForVisualizeField: () => [generateSuggestion()],
|
||||||
renderDataPanel: (_element, { dragDropContext: { setDragging, dragging } }) => {
|
renderDataPanel: (_element, { dragDropContext: [{ dragging }, dndDispatch] }) => {
|
||||||
if (!dragging || dragging.id !== 'draggedField') {
|
if (!dragging || dragging.id !== 'draggedField') {
|
||||||
setDragging({
|
dndDispatch({
|
||||||
id: 'draggedField',
|
type: 'startDragging',
|
||||||
humanData: { label: '1' },
|
payload: {
|
||||||
|
dragging: {
|
||||||
|
id: 'draggedField',
|
||||||
|
humanData: { label: '1' },
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import React, { useCallback, useRef } from 'react';
|
import React, { useCallback, useRef } from 'react';
|
||||||
import { CoreStart } from '@kbn/core/public';
|
import { CoreStart } from '@kbn/core/public';
|
||||||
import { ReactExpressionRendererType } from '@kbn/expressions-plugin/public';
|
import { ReactExpressionRendererType } from '@kbn/expressions-plugin/public';
|
||||||
import { DragDropIdentifier, RootDragDropProvider } from '@kbn/dom-drag-drop';
|
import { type DragDropAction, DragDropIdentifier, RootDragDropProvider } from '@kbn/dom-drag-drop';
|
||||||
import { trackUiCounterEvents } from '../../lens_ui_telemetry';
|
import { trackUiCounterEvents } from '../../lens_ui_telemetry';
|
||||||
import {
|
import {
|
||||||
DatasourceMap,
|
DatasourceMap,
|
||||||
|
@ -108,8 +108,14 @@ export function EditorFrame(props: EditorFrameProps) {
|
||||||
|
|
||||||
const bannerMessages = props.getUserMessages('banner', { severity: 'warning' });
|
const bannerMessages = props.getUserMessages('banner', { severity: 'warning' });
|
||||||
|
|
||||||
|
const telemetryMiddleware = useCallback((action: DragDropAction) => {
|
||||||
|
if (action.type === 'dropToTarget') {
|
||||||
|
trackUiCounterEvents('drop_total');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootDragDropProvider dataTestSubj="lnsDragDrop" onTrackUICounterEvent={trackUiCounterEvents}>
|
<RootDragDropProvider dataTestSubj="lnsDragDrop" customMiddleware={telemetryMiddleware}>
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
bannerMessages={
|
bannerMessages={
|
||||||
bannerMessages.length ? (
|
bannerMessages.length ? (
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
createExpressionRendererMock,
|
createExpressionRendererMock,
|
||||||
DatasourceMock,
|
DatasourceMock,
|
||||||
createMockFramePublicAPI,
|
createMockFramePublicAPI,
|
||||||
|
createMockedDragDropContext,
|
||||||
} from '../../../mocks';
|
} from '../../../mocks';
|
||||||
import { mockDataPlugin, mountWithProvider } from '../../../mocks';
|
import { mockDataPlugin, mountWithProvider } from '../../../mocks';
|
||||||
jest.mock('../../../debounced_component', () => {
|
jest.mock('../../../debounced_component', () => {
|
||||||
|
@ -934,18 +935,7 @@ describe('workspace_panel', () => {
|
||||||
|
|
||||||
async function initComponent(draggingContext = draggedField) {
|
async function initComponent(draggingContext = draggedField) {
|
||||||
const mounted = await mountWithProvider(
|
const mounted = await mountWithProvider(
|
||||||
<ChildDragDropProvider
|
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingContext })}>
|
||||||
dataTestSubjPrefix="lnsDragDrop"
|
|
||||||
dragging={draggingContext}
|
|
||||||
setDragging={() => {}}
|
|
||||||
setActiveDropTarget={() => {}}
|
|
||||||
activeDropTarget={undefined}
|
|
||||||
keyboardMode={false}
|
|
||||||
setKeyboardMode={() => {}}
|
|
||||||
setA11yMessage={() => {}}
|
|
||||||
registerDropTarget={jest.fn()}
|
|
||||||
dropTargetsByOrder={undefined}
|
|
||||||
>
|
|
||||||
<WorkspacePanel
|
<WorkspacePanel
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
datasourceMap={{
|
datasourceMap={{
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, useMemo, useContext, useCallback, useRef } from 'react';
|
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||||
import useObservable from 'react-use/lib/useObservable';
|
import useObservable from 'react-use/lib/useObservable';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
@ -34,7 +34,7 @@ import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
|
||||||
import type { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common';
|
import type { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common';
|
||||||
import type { Datatable } from '@kbn/expressions-plugin/public';
|
import type { Datatable } from '@kbn/expressions-plugin/public';
|
||||||
import { DropIllustration } from '@kbn/chart-icons';
|
import { DropIllustration } from '@kbn/chart-icons';
|
||||||
import { DragDrop, DragContext, DragDropIdentifier } from '@kbn/dom-drag-drop';
|
import { DragDrop, useDragDropContext, DragDropIdentifier } from '@kbn/dom-drag-drop';
|
||||||
import { trackUiCounterEvents } from '../../../lens_ui_telemetry';
|
import { trackUiCounterEvents } from '../../../lens_ui_telemetry';
|
||||||
import { getSearchWarningMessages } from '../../../utils';
|
import { getSearchWarningMessages } from '../../../utils';
|
||||||
import {
|
import {
|
||||||
|
@ -126,11 +126,11 @@ const EXPRESSION_BUILD_ERROR_ID = 'expression_build_error';
|
||||||
export const WorkspacePanel = React.memo(function WorkspacePanel(props: WorkspacePanelProps) {
|
export const WorkspacePanel = React.memo(function WorkspacePanel(props: WorkspacePanelProps) {
|
||||||
const { getSuggestionForField, ...restProps } = props;
|
const { getSuggestionForField, ...restProps } = props;
|
||||||
|
|
||||||
const dragDropContext = useContext(DragContext);
|
const [{ dragging }] = useDragDropContext();
|
||||||
|
|
||||||
const suggestionForDraggedField = useMemo(
|
const suggestionForDraggedField = useMemo(
|
||||||
() => dragDropContext.dragging && getSuggestionForField(dragDropContext.dragging),
|
() => dragging && getSuggestionForField(dragging),
|
||||||
[dragDropContext.dragging, getSuggestionForField]
|
[dragging, getSuggestionForField]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -573,15 +573,15 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const dragDropContext = useContext(DragContext);
|
const [{ dragging }] = useDragDropContext();
|
||||||
const renderWorkspace = () => {
|
const renderWorkspace = () => {
|
||||||
const customWorkspaceRenderer =
|
const customWorkspaceRenderer =
|
||||||
activeDatasourceId &&
|
activeDatasourceId &&
|
||||||
datasourceMap[activeDatasourceId]?.getCustomWorkspaceRenderer &&
|
datasourceMap[activeDatasourceId]?.getCustomWorkspaceRenderer &&
|
||||||
dragDropContext.dragging
|
dragging
|
||||||
? datasourceMap[activeDatasourceId].getCustomWorkspaceRenderer!(
|
? datasourceMap[activeDatasourceId].getCustomWorkspaceRenderer!(
|
||||||
datasourceStates[activeDatasourceId].state,
|
datasourceStates[activeDatasourceId].state,
|
||||||
dragDropContext.dragging,
|
dragging,
|
||||||
dataViews.indexPatterns
|
dataViews.indexPatterns
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { DragContextState, DragContextValue } from '@kbn/dom-drag-drop';
|
||||||
import { createMockDataViewsState } from '../data_views_service/mocks';
|
import { createMockDataViewsState } from '../data_views_service/mocks';
|
||||||
import { FramePublicAPI, FrameDatasourceAPI } from '../types';
|
import { FramePublicAPI, FrameDatasourceAPI } from '../types';
|
||||||
export { mockDataPlugin } from './data_plugin_mock';
|
export { mockDataPlugin } from './data_plugin_mock';
|
||||||
|
@ -65,3 +66,20 @@ export const createMockFrameDatasourceAPI = ({
|
||||||
filters: filters ?? [],
|
filters: filters ?? [],
|
||||||
dataViews: createMockDataViewsState(dataViews),
|
dataViews: createMockDataViewsState(dataViews),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function createMockedDragDropContext(
|
||||||
|
partialState?: Partial<DragContextState>,
|
||||||
|
setState?: jest.Mocked<DragContextValue>[1]
|
||||||
|
): jest.Mocked<DragContextValue> {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
dataTestSubjPrefix: 'lnsDragDrop',
|
||||||
|
dragging: undefined,
|
||||||
|
keyboardMode: false,
|
||||||
|
activeDropTarget: undefined,
|
||||||
|
dropTargetsByOrder: undefined,
|
||||||
|
...partialState,
|
||||||
|
},
|
||||||
|
setState ? setState : jest.fn(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ import { EventAnnotationGroupConfig } from '@kbn/event-annotation-plugin/common'
|
||||||
import type {
|
import type {
|
||||||
DraggingIdentifier,
|
DraggingIdentifier,
|
||||||
DragDropIdentifier,
|
DragDropIdentifier,
|
||||||
DragContextState,
|
DragContextValue,
|
||||||
DropType,
|
DropType,
|
||||||
} from '@kbn/dom-drag-drop';
|
} from '@kbn/dom-drag-drop';
|
||||||
import type { AccessorConfig } from '@kbn/visualization-ui-components/public';
|
import type { AccessorConfig } from '@kbn/visualization-ui-components/public';
|
||||||
|
@ -588,7 +588,7 @@ export interface DatasourceLayerSettingsProps<T = unknown> {
|
||||||
|
|
||||||
export interface DatasourceDataPanelProps<T = unknown> {
|
export interface DatasourceDataPanelProps<T = unknown> {
|
||||||
state: T;
|
state: T;
|
||||||
dragDropContext: DragContextState;
|
dragDropContext: DragContextValue;
|
||||||
setState: StateSetter<T, { applyImmediately?: boolean }>;
|
setState: StateSetter<T, { applyImmediately?: boolean }>;
|
||||||
showNoDataPopover: () => void;
|
showNoDataPopover: () => void;
|
||||||
core: Pick<
|
core: Pick<
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue