Refactor dashboard drop target attributes to use constants for improved maintainability

- Introduced constants for data attributes used to identify auto grid items and dashboard layout elements as drop targets.
- Updated references throughout the AutoGridLayout, AutoGridItemRenderer, DashboardLayoutOrchestrator, and other related components to utilize the new constants.
- This change enhances code readability and reduces the risk of errors related to hardcoded strings.
This commit is contained in:
oscarkilhed
2026-01-13 09:07:55 +01:00
parent 5256a5e83e
commit 9992da3cc8
8 changed files with 34 additions and 17 deletions
@@ -21,7 +21,12 @@ import { RowItem } from './layout-rows/RowItem';
import { RowsLayoutManager } from './layout-rows/RowsLayoutManager';
import { TabItem } from './layout-tabs/TabItem';
import { TabsLayoutManager } from './layout-tabs/TabsLayoutManager';
import { DashboardDropTarget, isDashboardDropTarget } from './types/DashboardDropTarget';
import {
AUTO_GRID_ITEM_DROP_TARGET_ATTR,
DASHBOARD_DROP_TARGET_KEY_ATTR,
DashboardDropTarget,
isDashboardDropTarget,
} from './types/DashboardDropTarget';
const TAB_ACTIVATION_DELAY_MS = 600;
@@ -389,8 +394,8 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase<DashboardLayout
}
private _captureRowDimensions(row: RowItem): void {
// Try to find the DOM element for the row using data-dashboard-drop-target-key
const element = document.querySelector(`[data-dashboard-drop-target-key="${row.state.key}"]`);
// Try to find the DOM element for the row using DASHBOARD_DROP_TARGET_KEY_ATTR
const element = document.querySelector(`[${DASHBOARD_DROP_TARGET_KEY_ATTR}="${row.state.key}"]`);
if (element) {
const rect = element.getBoundingClientRect();
this._previewWidth = rect.width;
@@ -405,7 +410,7 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase<DashboardLayout
private _captureRowDragOffset(cursorX: number, cursorY: number, row: RowItem): void {
// Try to find the DOM element for the row
const element = document.querySelector(`[data-dashboard-drop-target-key="${row.state.key}"]`);
const element = document.querySelector(`[${DASHBOARD_DROP_TARGET_KEY_ATTR}="${row.state.key}"]`);
if (element) {
const rect = element.getBoundingClientRect();
this._dragOffsetX = cursorX - rect.left;
@@ -594,8 +599,8 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase<DashboardLayout
// Find which AutoGridItem we're hovering over
const elementsUnderPoint = document.elementsFromPoint(clientX, clientY);
const targetElement = elementsUnderPoint?.find((el) => el.getAttribute('data-auto-grid-item-drop-target'));
const targetKey = targetElement?.getAttribute('data-auto-grid-item-drop-target');
const targetElement = elementsUnderPoint?.find((el) => el.getAttribute(AUTO_GRID_ITEM_DROP_TARGET_ATTR));
const targetKey = targetElement?.getAttribute(AUTO_GRID_ITEM_DROP_TARGET_ATTR);
const children = dropTarget.state.layout.state.children;
@@ -658,7 +663,7 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase<DashboardLayout
private _getDropTargetUnderMouse(evt: MouseEvent): DashboardDropTarget | null {
const elementsUnderPoint = document.elementsFromPoint(evt.clientX, evt.clientY);
const cursorIsInSourceTarget = elementsUnderPoint.some(
(el) => el.getAttribute('data-dashboard-drop-target-key') === this._sourceDropTarget?.state.key
(el) => el.getAttribute(DASHBOARD_DROP_TARGET_KEY_ATTR) === this._sourceDropTarget?.state.key
);
if (cursorIsInSourceTarget) {
@@ -666,8 +671,8 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase<DashboardLayout
}
const key = elementsUnderPoint
?.find((element) => element.getAttribute('data-dashboard-drop-target-key'))
?.getAttribute('data-dashboard-drop-target-key');
?.find((element) => element.getAttribute(DASHBOARD_DROP_TARGET_KEY_ATTR))
?.getAttribute(DASHBOARD_DROP_TARGET_KEY_ATTR);
if (!key) {
return null;
@@ -9,8 +9,9 @@ import { ConditionalRenderingGroup } from '../../conditional-rendering/group/Con
import { useIsConditionallyHidden } from '../../conditional-rendering/hooks/useIsConditionallyHidden';
import { useDashboardState } from '../../utils/utils';
import { SoloPanelContextValueWithSearchStringFilter } from '../PanelSearchLayout';
import { renderMatchingSoloPanels, useSoloPanelContext } from '../SoloPanelContext';
import { useSoloPanelContext, renderMatchingSoloPanels } from '../SoloPanelContext';
import { getIsLazy } from '../layouts-shared/utils';
import { AUTO_GRID_ITEM_DROP_TARGET_ATTR } from '../types/DashboardDropTarget';
import { AutoGridItem } from './AutoGridItem';
import { AutoGridLayoutManager } from './AutoGridLayoutManager';
@@ -53,7 +54,7 @@ export function AutoGridItemRenderer({ model }: SceneComponentProps<AutoGridItem
return isConditionallyHidden && !isEditing && !renderHidden ? null : (
<div
{...(addDndContainer
? { ref: model.containerRef, ['data-auto-grid-item-drop-target']: showDropTarget ? key : undefined }
? { ref: model.containerRef, [AUTO_GRID_ITEM_DROP_TARGET_ATTR]: showDropTarget ? key : undefined }
: {})}
className={cx(isConditionallyHidden && !isEditing && styles.hidden)}
>
@@ -4,6 +4,7 @@ import { SceneLayout, SceneObjectBase, SceneObjectState, VizPanel, SceneGridItem
import { isRepeatCloneOrChildOf } from '../../utils/clone';
import { getLayoutOrchestratorFor } from '../../utils/utils';
import { AUTO_GRID_ITEM_DROP_TARGET_ATTR } from '../types/DashboardDropTarget';
import { AutoGridItem } from './AutoGridItem';
import { AutoGridLayoutRenderer } from './AutoGridLayoutRenderer';
@@ -229,11 +230,11 @@ export class AutoGridLayout extends SceneObjectBase<AutoGridLayoutState> impleme
const dropTargetGridItemKey = document
.elementsFromPoint(evt.clientX, evt.clientY)
?.find((element) => {
const key = element.getAttribute('data-auto-grid-item-drop-target');
const key = element.getAttribute(AUTO_GRID_ITEM_DROP_TARGET_ATTR);
return !!key && key !== this._draggedGridItem!.state.key;
})
?.getAttribute('data-auto-grid-item-drop-target');
?.getAttribute(AUTO_GRID_ITEM_DROP_TARGET_ATTR);
if (dropTargetGridItemKey && dropTargetGridItemKey !== this._lastDropTargetGridItemKey) {
this._onDragOverItem(dropTargetGridItemKey);
@@ -9,6 +9,7 @@ import { useDashboardState } from '../../utils/utils';
import { useSoloPanelContext } from '../SoloPanelContext';
import { CanvasGridAddActions } from '../layouts-shared/CanvasGridAddActions';
import { dashboardCanvasAddButtonHoverStyles } from '../layouts-shared/styles';
import { DASHBOARD_DROP_TARGET_KEY_ATTR } from '../types/DashboardDropTarget';
import { AutoGridLayout, AutoGridLayoutState } from './AutoGridLayout';
import { AutoGridLayoutManager } from './AutoGridLayoutManager';
@@ -57,7 +58,7 @@ export function AutoGridLayoutRenderer({ model }: SceneComponentProps<AutoGridLa
<div
className={cx(styles.container, fillScreen && styles.containerFillScreen, isEditing && styles.containerEditing)}
ref={model.containerRef}
data-dashboard-drop-target-key={layoutManager.state.key}
{...{ [DASHBOARD_DROP_TARGET_KEY_ATTR]: layoutManager.state.key }}
>
{renderChildren()}
{showCanvasActions && <CanvasGridAddActions layoutManager={layoutManager} />}
@@ -13,6 +13,7 @@ import { isRepeatCloneOrChildOf } from '../../utils/clone';
import { useDashboardState, useInterpolatedTitle } from '../../utils/utils';
import { DashboardScene } from '../DashboardScene';
import { useSoloPanelContext } from '../SoloPanelContext';
import { DASHBOARD_DROP_TARGET_KEY_ATTR } from '../types/DashboardDropTarget';
import { isDashboardLayoutGrid } from '../types/DashboardLayoutGrid';
import { RowItem } from './RowItem';
@@ -85,7 +86,7 @@ export function RowItemRenderer({ model }: SceneComponentProps<RowItem>) {
dragProvided.innerRef(ref);
model.containerRef.current = ref;
}}
data-dashboard-drop-target-key={isDashboardLayoutGrid(layout) ? model.state.key : undefined}
{...{ [DASHBOARD_DROP_TARGET_KEY_ATTR]: isDashboardLayoutGrid(layout) ? model.state.key : undefined }}
className={cx(
styles.wrapper,
!isCollapsed && styles.wrapperNotCollapsed,
@@ -12,6 +12,7 @@ import { isRepeatCloneOrChildOf } from '../../utils/clone';
import { useDashboardState, getLayoutOrchestratorFor } from '../../utils/utils';
import { useSoloPanelContext } from '../SoloPanelContext';
import { useClipboardState } from '../layouts-shared/useClipboardState';
import { DASHBOARD_DROP_TARGET_KEY_ATTR } from '../types/DashboardDropTarget';
import { RowItem } from './RowItem';
import { RowItemRepeater } from './RowItemRepeater';
@@ -74,7 +75,7 @@ export function RowLayoutManagerRenderer({ model }: SceneComponentProps<RowsLayo
className={styles.wrapper}
ref={dropProvided.innerRef}
{...dropProvided.droppableProps}
{...(showAsDropTarget ? { 'data-dashboard-drop-target-key': key } : {})}
{...(showAsDropTarget ? { [DASHBOARD_DROP_TARGET_KEY_ATTR]: key } : {})}
>
{rows.map((row) => (
<RowWrapper row={row} manager={model} key={row.state.key!} />
@@ -11,6 +11,7 @@ import { useIsConditionallyHidden } from '../../conditional-rendering/hooks/useI
import { isRepeatCloneOrChildOf } from '../../utils/clone';
import { useDashboardState } from '../../utils/utils';
import { useSoloPanelContext } from '../SoloPanelContext';
import { DASHBOARD_DROP_TARGET_KEY_ATTR } from '../types/DashboardDropTarget';
import { TabItem } from './TabItem';
@@ -125,7 +126,7 @@ export function TabItemLayoutRenderer({ tab, isEditing }: TabItemLayoutRendererP
return (
<TabContent
className={cx(styles.tabContentContainer, isEditing && conditionalRenderingClass)}
data-dashboard-drop-target-key={key}
{...{ [DASHBOARD_DROP_TARGET_KEY_ATTR]: key }}
>
<layout.Component model={layout} />
{isEditing && conditionalRenderingOverlay}
@@ -1,5 +1,11 @@
import { SceneObject, SceneGridItemLike } from '@grafana/scenes';
/** Data attribute used to identify auto grid items as drop targets */
export const AUTO_GRID_ITEM_DROP_TARGET_ATTR = 'data-auto-grid-item-drop-target';
/** Data attribute used to identify dashboard layout elements as drop targets */
export const DASHBOARD_DROP_TARGET_KEY_ATTR = 'data-dashboard-drop-target-key';
export interface DashboardDropTarget extends SceneObject {
isDashboardDropTarget: Readonly<true>;
setIsDropTarget?(isDropTarget: boolean): void;