From d31279456419e90a5acd020d7ed3cee25e8aa660 Mon Sep 17 00:00:00 2001 From: ykornilov Date: Thu, 20 Nov 2025 10:52:30 +0300 Subject: [PATCH 1/7] fix!: move GridLayout from JS to TS (#277) * fix!: move GridLayout from JS to TS * chore: update ci workflow --------- Co-authored-by: Yury Kornilov --- .github/workflows/ci.yaml | 3 +- README-ru.md | 2 +- README.md | 2 +- src/components/DashKit/DashKit.tsx | 2 +- .../{GridLayout.js => GridLayout.tsx} | 392 ++++++++++++------ src/components/GridLayout/types.ts | 77 ++++ src/components/MobileLayout/MobileLayout.tsx | 10 +- src/context/DashKitContext.ts | 20 +- src/typings/config.ts | 4 +- src/typings/plugin.ts | 2 + 10 files changed, 375 insertions(+), 139 deletions(-) rename src/components/GridLayout/{GridLayout.js => GridLayout.tsx} (64%) create mode 100644 src/components/GridLayout/types.ts diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 792d6cf..9a2fd76 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,9 +2,8 @@ name: CI on: push: - branches: [main, v8] + branches: [main] pull_request: - branches: [main, v8] jobs: verify_files: diff --git a/README-ru.md b/README-ru.md index aa71844..e85a303 100644 --- a/README-ru.md +++ b/README-ru.md @@ -25,7 +25,7 @@ npm i @gravity-ui/dashkit @gravity-ui/uikit ```ts type ItemManipulationCallback = (eventData: { - layout: Layouts; + layout: Layout[]; oldItem: Layout; newItem: Layout; placeholder: Layout; diff --git a/README.md b/README.md index 6949c33..c1b844a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Plugins are required to create custom widgets. ```ts type ItemManipulationCallback = (eventData: { - layout: Layouts; + layout: Layout[]; oldItem: Layout; newItem: Layout; placeholder: Layout; diff --git a/src/components/DashKit/DashKit.tsx b/src/components/DashKit/DashKit.tsx index 69ce91c..32ab3ad 100644 --- a/src/components/DashKit/DashKit.tsx +++ b/src/components/DashKit/DashKit.tsx @@ -226,7 +226,7 @@ export class DashKit extends React.PureComponent { } getItemsMeta() { - return this.metaRef.current?.getItemsMeta(); + return this.metaRef.current?.getItemsMeta() ?? []; } reloadItems(options?: {targetIds?: string[]; force?: boolean}) { diff --git a/src/components/GridLayout/GridLayout.js b/src/components/GridLayout/GridLayout.tsx similarity index 64% rename from src/components/GridLayout/GridLayout.js rename to src/components/GridLayout/GridLayout.tsx index f36c85d..55230fe 100644 --- a/src/components/GridLayout/GridLayout.js +++ b/src/components/GridLayout/GridLayout.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import type {PluginRef, ReactGridLayoutProps} from 'src/typings'; + import { COMPACT_TYPE_HORIZONTAL_NOWRAP, DEFAULT_GROUP, @@ -7,13 +9,50 @@ import { TEMPORARY_ITEM_ID, } from '../../constants'; import {DashKitContext} from '../../context'; +import type {DashKitCtxShape} from '../../context'; +import type {ConfigItem, ConfigLayout} from '../../shared'; import {resolveLayoutGroup} from '../../utils'; import GridItem from '../GridItem/GridItem'; import {Layout} from './ReactGridLayout'; +import type { + AdjustWidgetLayoutParams, + CurrentDraggingElement, + GridLayoutProps, + GridLayoutState, + GroupCallbacks, + LayoutAndPropsByGroup, + ManipulationCallbackArgs, + MemoGroupLayout, + ReloadItemsOptions, +} from './types'; + +const hasPluginId = (value: PluginRef): value is {props: {id: string}} => { + return ( + 'props' in value && + typeof value.props === 'object' && + value.props !== null && + 'id' in value.props && + typeof value.props.id === 'string' + ); +}; + +export default class GridLayout extends React.PureComponent { + static contextType = DashKitContext; + context!: DashKitCtxShape; + + pluginsRefs: Array; + + private _memoForwardedPluginRef: Array<(pluginRef: PluginRef) => void> = []; + private _memoGroupsProps: Record> = {}; + private _memoGroupsLayouts: Record = {}; + private _memoCallbacksForGroups: Record = {}; + + private _timeout?: NodeJS.Timeout; + private _lastReloadAt?: number; + private _parendDragNode: HTMLElement | null = null; -export default class GridLayout extends React.PureComponent { - constructor(props, context) { + constructor(props: GridLayoutProps, context: DashKitCtxShape) { super(props, context); this.pluginsRefs = []; this.state = { @@ -45,23 +84,17 @@ export default class GridLayout extends React.PureComponent { this._memoForwardedPluginRef = []; } - static contextType = DashKitContext; - - _memoForwardedPluginRef = []; - _memoGroupsProps = {}; - _memoGroupsLayouts = {}; - _memoCallbacksForGroups = {}; - - _timeout; - _lastReloadAt; - onVisibilityChange = () => { this.setState({ isPageHidden: document.hidden, }); }; - adjustWidgetLayout = ({widgetId, needSetDefault, adjustedWidgetLayout}) => { + adjustWidgetLayout = ({ + widgetId, + needSetDefault, + adjustedWidgetLayout, + }: AdjustWidgetLayoutParams) => { const {layout, memorizeOriginalLayout, revertToOriginalLayout} = this.context; if (needSetDefault) { @@ -89,13 +122,13 @@ export default class GridLayout extends React.PureComponent { return getItemsMeta(this.pluginsRefs); }; - getActiveLayout() { + getActiveLayout(): ConfigLayout[] { const {layout, temporaryLayout} = this.context; return temporaryLayout?.data || layout; } - getMemoGroupLayout(group, layout) { + getMemoGroupLayout(group: string, layout: ConfigLayout[]): MemoGroupLayout { // fastest possible way to match json const key = JSON.stringify(layout); @@ -116,7 +149,7 @@ export default class GridLayout extends React.PureComponent { return this._memoGroupsLayouts[group]; } - getMemoGroupCallbacks(group) { + getMemoGroupCallbacks(group: string): GroupCallbacks { if (!this._memoCallbacksForGroups[group]) { const onDragStart = this._onDragStart.bind(this, group); const onDrag = this._onDrag.bind(this, group); @@ -128,7 +161,7 @@ export default class GridLayout extends React.PureComponent { const onDrop = this._onDrop.bind(this, group); const onDropDragOver = this._onDropDragOver.bind(this, group); - const onDragTargetRestore = this._onTargetRestore.bind(this, group); + const onDragTargetRestore = this._onTargetRestore.bind(this); this._memoCallbacksForGroups[group] = { onDragStart, @@ -146,7 +179,11 @@ export default class GridLayout extends React.PureComponent { return this._memoCallbacksForGroups[group]; } - getMemoGroupProps = (group, renderLayout, properties) => { + getMemoGroupProps = ( + group: string, + renderLayout: ConfigLayout[], + properties: Partial, + ) => { // Needed for _onDropDragOver this._memoGroupsProps[group] = properties; @@ -156,16 +193,16 @@ export default class GridLayout extends React.PureComponent { }; }; - getLayoutAndPropsByGroup = (group) => { + getLayoutAndPropsByGroup = (group: string): LayoutAndPropsByGroup => { return { properties: this._memoGroupsProps[group], layout: this._memoGroupsLayouts[group].layout, }; }; - getMemoForwardRefCallback = (refIndex) => { + getMemoForwardRefCallback = (refIndex: number) => { if (!this._memoForwardedPluginRef[refIndex]) { - this._memoForwardedPluginRef[refIndex] = (pluginRef) => { + this._memoForwardedPluginRef[refIndex] = (pluginRef: PluginRef) => { this.pluginsRefs[refIndex] = pluginRef; }; } @@ -173,9 +210,13 @@ export default class GridLayout extends React.PureComponent { return this._memoForwardedPluginRef[refIndex]; }; - mergeGroupsLayout(group, newLayout, temporaryItem) { + mergeGroupsLayout( + group: string, + newLayout: ConfigLayout[], + temporaryItem?: ConfigLayout, + ): ConfigLayout[] { const renderLayout = this.getActiveLayout(); - const itemsByGroup = renderLayout.reduce( + const itemsByGroup = renderLayout.reduce>( (memo, item) => { memo[item.i] = item; return memo; @@ -183,8 +224,8 @@ export default class GridLayout extends React.PureComponent { temporaryItem ? {[temporaryItem.i]: temporaryItem} : {}, ); - const newItemsLayoutById = newLayout.reduce((memo, item) => { - const parent = itemsByGroup[item.i].parent; + const newItemsLayoutById = newLayout.reduce>((memo, item) => { + const parent = itemsByGroup[item.i]?.parent; memo[item.i] = {...item}; if (parent) { @@ -204,7 +245,7 @@ export default class GridLayout extends React.PureComponent { }); } - reloadItems(options) { + reloadItems(options?: ReloadItemsOptions) { const {targetIds, force} = options || {}; const { @@ -218,7 +259,9 @@ export default class GridLayout extends React.PureComponent { const autoupdateIntervalMs = Number(autoupdateInterval) * 1000; const targetPlugins = targetIds - ? this.pluginsRefs.filter((plugin) => targetIds.includes(plugin.props.id)) + ? this.pluginsRefs.filter( + (plugin) => hasPluginId(plugin) && targetIds.includes(plugin.props.id), + ) : this.pluginsRefs; if (autoupdateIntervalMs) { @@ -227,7 +270,7 @@ export default class GridLayout extends React.PureComponent { if (force || (!isPageHidden && !editMode && reloadIntervalRemains <= 0)) { this._lastReloadAt = new Date().getTime(); - reloadItems(targetPlugins, {silentLoading, noVeil: true}); + reloadItems(targetPlugins, {silentLoading: Boolean(silentLoading), noVeil: true}); } this._timeout = setTimeout( @@ -235,11 +278,19 @@ export default class GridLayout extends React.PureComponent { reloadIntervalRemains <= 0 ? autoupdateIntervalMs : reloadIntervalRemains, ); } else if (force) { - reloadItems(targetPlugins, {silentLoading, noVeil: true}); + reloadItems(targetPlugins, {silentLoading: Boolean(silentLoading), noVeil: true}); } } - prepareDefaultArguments(group, layout, oldItem, newItem, placeholder, e, element) { + prepareDefaultArguments( + group: string, + layout: ConfigLayout[], + oldItem: ConfigLayout, + newItem: ConfigLayout, + placeholder: ConfigLayout, + e: MouseEvent, + element: HTMLElement, + ): ManipulationCallbackArgs { return { group, layout, @@ -251,8 +302,9 @@ export default class GridLayout extends React.PureComponent { }; } - updateDraggingElementState(group, layoutItem, e) { - let currentDraggingElement = this.state.currentDraggingElement; + updateDraggingElementState(group: string, layoutItem: ConfigLayout, e: MouseEvent) { + let currentDraggingElement: CurrentDraggingElement | null = + this.state.currentDraggingElement; if (!currentDraggingElement) { const {temporaryLayout} = this.context; @@ -262,12 +314,18 @@ export default class GridLayout extends React.PureComponent { ? temporaryLayout.dragProps : this.context.configItems.find(({id}) => id === layoutId); - let {offsetX, offsetY} = e.nativeEvent || {}; + if (!item) { + return; + } + + let {offsetX, offsetY} = + (e as MouseEvent & {nativeEvent: MouseEvent}).nativeEvent || {}; if (offsetX === undefined || offsetY === undefined) { - const gridRect = e.currentTarget.getBoundingClientRect(); + const target = e.currentTarget as HTMLElement; + const gridRect = target?.getBoundingClientRect(); - offsetX = e.clientX - gridRect.left; - offsetY = e.clientY - gridRect.top; + offsetX = e.clientX - (gridRect?.left || 0); + offsetY = e.clientY - (gridRect?.top || 0); } currentDraggingElement = { @@ -281,7 +339,7 @@ export default class GridLayout extends React.PureComponent { this.setState({currentDraggingElement, draggedOverGroup: group}); } - _initDragCoordinatesWatcher(element) { + _initDragCoordinatesWatcher(element: HTMLElement) { if (!this._parendDragNode) { this._parendDragNode = element.parentElement; this.setState({isDraggedOut: false}); @@ -290,7 +348,11 @@ export default class GridLayout extends React.PureComponent { // When element is going back and prointer-event: none is removed mouse enter event is not fired // So to trigger it we are forcing this event by adding transparent block under the mouse - _forceCursorCapture(parentElement, position, parentRect) { + _forceCursorCapture( + parentElement: HTMLElement, + position: {top: number; left: number}, + parentRect: DOMRect, + ) { const block = document.createElement('div'); block.classList.add('react-grid-focus-capture'); @@ -313,8 +375,13 @@ export default class GridLayout extends React.PureComponent { }, 100); } - _updateDragCoordinates(e) { + _updateDragCoordinates(e: MouseEvent) { const parent = this._parendDragNode; + + if (!parent) { + return; + } + const parentRect = parent.getBoundingClientRect(); const {clientX, clientY} = e; @@ -351,7 +418,15 @@ export default class GridLayout extends React.PureComponent { this.setState({isDraggedOut: false}); } - _onDragStart(group, _newLayout, layoutItem, _newItem, _placeholder, e, element) { + _onDragStart( + group: string, + _newLayout: ConfigLayout[], + layoutItem: ConfigLayout, + _newItem: ConfigLayout, + _placeholder: ConfigLayout, + e: MouseEvent, + element: HTMLElement, + ) { this.context.onDragStart?.call( this, this.prepareDefaultArguments( @@ -374,7 +449,15 @@ export default class GridLayout extends React.PureComponent { } } - _onDrag(group, layout, oldItem, newItem, placeholder, e, element) { + _onDrag( + group: string, + layout: ConfigLayout[], + oldItem: ConfigLayout, + newItem: ConfigLayout, + placeholder: ConfigLayout, + e: MouseEvent, + element: HTMLElement, + ) { if (!this.context.dragOverPlugin) { this._updateDragCoordinates(e); } @@ -385,7 +468,15 @@ export default class GridLayout extends React.PureComponent { ); } - _onDragStop(group, layout, oldItem, newItem, placeholder, e, element) { + _onDragStop( + group: string, + layout: ConfigLayout[], + oldItem: ConfigLayout, + newItem: ConfigLayout, + placeholder: ConfigLayout, + e: MouseEvent, + element: HTMLElement, + ) { this._resetDragWatcher(); this._onStop(group, layout); @@ -396,7 +487,15 @@ export default class GridLayout extends React.PureComponent { ); } - _onResizeStart(group, layout, oldItem, newItem, placeholder, e, element) { + _onResizeStart( + group: string, + layout: ConfigLayout[], + oldItem: ConfigLayout, + newItem: ConfigLayout, + placeholder: ConfigLayout, + e: MouseEvent, + element: HTMLElement, + ) { this.setState({ isDragging: true, }); @@ -407,14 +506,30 @@ export default class GridLayout extends React.PureComponent { ); } - _onResize(group, layout, oldItem, newItem, placeholder, e, element) { + _onResize( + group: string, + layout: ConfigLayout[], + oldItem: ConfigLayout, + newItem: ConfigLayout, + placeholder: ConfigLayout, + e: MouseEvent, + element: HTMLElement, + ) { this.context.onResize?.call( this, this.prepareDefaultArguments(group, layout, oldItem, newItem, placeholder, e, element), ); } - _onResizeStop(group, layout, oldItem, newItem, placeholder, e, element) { + _onResizeStop( + group: string, + layout: ConfigLayout[], + oldItem: ConfigLayout, + newItem: ConfigLayout, + placeholder: ConfigLayout, + e: MouseEvent, + element: HTMLElement, + ) { this._onStop(group, layout); this.context.onResizeStop?.call( @@ -437,7 +552,7 @@ export default class GridLayout extends React.PureComponent { } } - _onStop = (group, newLayout) => { + _onStop = (group: string, newLayout: ConfigLayout[]) => { const {layoutChange, onDrop, temporaryLayout} = this.context; const {draggedOverGroup, currentDraggingElement} = this.state; @@ -460,16 +575,16 @@ export default class GridLayout extends React.PureComponent { }); if (temporaryLayout) { - onDrop?.( - groupedLayout, - groupedLayout.find(({i}) => i === TEMPORARY_ITEM_ID), - ); + const temporaryItem = groupedLayout.find(({i}) => i === TEMPORARY_ITEM_ID); + if (onDrop && temporaryItem) { + onDrop(groupedLayout, temporaryItem); + } } else { layoutChange(groupedLayout); } }; - _onSharedDrop = (targetGroup, newLayout, tempItem) => { + _onSharedDrop = (targetGroup: string, newLayout: ConfigLayout[], tempItem: ConfigLayout) => { const {currentDraggingElement} = this.state; const {layoutChange} = this.context; @@ -507,7 +622,12 @@ export default class GridLayout extends React.PureComponent { layoutChange(groupedLayout); }; - _onExternalDrop = (group, newLayout, item, e) => { + _onExternalDrop = ( + group: string, + newLayout: ConfigLayout[], + item: ConfigLayout, + e: MouseEvent, + ) => { const {onDrop} = this.context; if (group !== DEFAULT_GROUP) { @@ -520,7 +640,12 @@ export default class GridLayout extends React.PureComponent { onDrop?.(groupedLayout, item, e); }; - _onDrop = (group, newLayout, item, e) => { + _onDrop = ( + group: string, + newLayout: ConfigLayout[], + item: ConfigLayout | undefined, + e: MouseEvent, + ): void | false => { if (!item || !this.context.editMode) { return false; } @@ -533,7 +658,7 @@ export default class GridLayout extends React.PureComponent { } }; - _onDropDragOver = (group, e) => { + _onDropDragOver = (group: string, e: DragEvent | MouseEvent): void | boolean => { const {editMode, dragOverPlugin, onDropDragOver, temporaryLayout} = this.context; const {currentDraggingElement} = this.state; @@ -569,7 +694,7 @@ export default class GridLayout extends React.PureComponent { return false; }; - renderTemporaryPlaceholder(gridLayout) { + renderTemporaryPlaceholder(gridLayout: Partial) { const {temporaryLayout, noOverlay, draggableHandleClassName} = this.context; if (!temporaryLayout || !temporaryLayout.dragProps) { @@ -582,6 +707,8 @@ export default class GridLayout extends React.PureComponent { return ( ReactGridLayoutProps, + ) { const { registerManager, editMode, @@ -632,36 +765,43 @@ export default class GridLayout extends React.PureComponent { return ( {renderItems.map((item, i) => { const keyId = item.id; const isDragging = this.state.isDragging; - const isCurrentDraggedItem = currentDraggingElement?.item.id === keyId; + const isCurrentDraggedItem = + currentDraggingElement && + 'id' in currentDraggingElement.item && + currentDraggingElement.item.id === keyId; const isDraggedOut = isCurrentDraggedItem && this.state.isDraggedOut; const itemNoOverlay = hasOwnGroupProperties && 'noOverlay' in properties @@ -670,22 +810,24 @@ export default class GridLayout extends React.PureComponent { return ( ); })} @@ -699,38 +841,44 @@ export default class GridLayout extends React.PureComponent { this.pluginsRefs.length = this.context.configItems.length; - const defaultRenderLayout = []; - const defaultRenderItems = []; - const layoutMap = {}; + const defaultRenderLayout: ConfigLayout[] = []; + const defaultRenderItems: ConfigItem[] = []; + const layoutMap: Record = {}; - const groupedLayout = this.getActiveLayout().reduce((memo, item) => { - if (item.parent) { - if (!memo[item.parent]) { - memo[item.parent] = []; - } + const groupedLayout = this.getActiveLayout().reduce>( + (memo, item) => { + if (item.parent) { + if (!memo[item.parent]) { + memo[item.parent] = []; + } - memo[item.parent].push(item); - layoutMap[item.i] = item.parent; - } else { - defaultRenderLayout.push(item); - } + memo[item.parent].push(item); + layoutMap[item.i] = item.parent; + } else { + defaultRenderLayout.push(item); + } - return memo; - }, []); + return memo; + }, + {}, + ); - const itemsByGroup = this.context.configItems.reduce((memo, item) => { - const group = layoutMap[item.id]; - if (group) { - if (!memo[group]) { - memo[group] = []; + const itemsByGroup = this.context.configItems.reduce>( + (memo, item) => { + const group = layoutMap[item.id]; + if (group) { + if (!memo[group]) { + memo[group] = []; + } + memo[group].push(item); + } else { + defaultRenderItems.push(item); } - memo[group].push(item); - } else { - defaultRenderItems.push(item); - } - return memo; - }, {}); + return memo; + }, + {}, + ); let offset = 0; diff --git a/src/components/GridLayout/types.ts b/src/components/GridLayout/types.ts new file mode 100644 index 0000000..724ebe8 --- /dev/null +++ b/src/components/GridLayout/types.ts @@ -0,0 +1,77 @@ +import type {ReactGridLayoutProps} from 'src/typings'; + +import type {ConfigItem, ConfigLayout, ItemDragProps} from '../../shared'; + +export type GridLayoutProps = { + ref?: React.ForwardedRef; +}; + +export type GridLayoutState = { + isDragging: boolean; + isDraggedOut: boolean; + isPageHidden: boolean; + currentDraggingElement: CurrentDraggingElement | null; + draggedOverGroup: string | null; +}; + +export type CurrentDraggingElement = { + group: string; + layoutItem: ConfigLayout; + item: ConfigItem | ItemDragProps; + cursorPosition: { + offsetX: number; + offsetY: number; + }; +}; + +export type ReloadItemsOptions = { + targetIds?: string[]; + force?: boolean; +}; + +export type ManipulationCallbackArgs = { + group: string; + layout: ConfigLayout[]; + oldItem: ConfigLayout; + newItem: ConfigLayout; + placeholder: ConfigLayout; + e: MouseEvent; + element: HTMLElement; +}; + +export type MemoGroupLayout = { + key: string; + layout: ConfigLayout[]; +}; + +type GroupCallback = ( + layout: ConfigLayout[], + layoutItem: ConfigLayout, + newItem: ConfigLayout, + placeholder: ConfigLayout, + event: MouseEvent, + element: HTMLElement, +) => void; + +export type GroupCallbacks = { + onDragStart: GroupCallback; + onDrag: GroupCallback; + onDragStop: GroupCallback; + onResizeStart: GroupCallback; + onResize: GroupCallback; + onResizeStop: GroupCallback; + onDrop: (layout: ConfigLayout[], item: ConfigLayout | undefined, e: MouseEvent) => void | false; + onDropDragOver: (e: DragEvent | MouseEvent) => void | boolean; + onDragTargetRestore: (group?: string) => void; +}; + +export type AdjustWidgetLayoutParams = { + widgetId: string; + needSetDefault?: boolean; + adjustedWidgetLayout?: ConfigLayout; +}; + +export type LayoutAndPropsByGroup = { + properties: Partial; + layout: ConfigLayout[]; +}; diff --git a/src/components/MobileLayout/MobileLayout.tsx b/src/components/MobileLayout/MobileLayout.tsx index 6714617..377b796 100644 --- a/src/components/MobileLayout/MobileLayout.tsx +++ b/src/components/MobileLayout/MobileLayout.tsx @@ -2,6 +2,8 @@ import React from 'react'; import groupBy from 'lodash/groupBy'; +import type {PluginRef} from 'src/typings'; + import {DEFAULT_GROUP} from '../../constants'; import {DashKitContext} from '../../context'; import {cn} from '../../utils/cn'; @@ -19,8 +21,6 @@ type MobileLayoutState = { itemsWithActiveAutoheight: Record; }; -type PlugibRefObject = React.RefObject; - export default class MobileLayout extends React.PureComponent< MobileLayoutProps, MobileLayoutState @@ -28,11 +28,11 @@ export default class MobileLayout extends React.PureComponent< static contextType = DashKitContext; context!: React.ContextType; - pluginsRefs: PlugibRefObject[] = []; + pluginsRefs: PluginRef[] = []; sortedLayoutItems: Record> | null = null; _memoLayout = this.context.layout; - _memoForwardedPluginRef: Array<(refObject: PlugibRefObject) => void> = []; + _memoForwardedPluginRef: Array<(refObject: PluginRef) => void> = []; _memoAdjustWidgetLayout: Record void> = {}; state: MobileLayoutState = { @@ -123,7 +123,7 @@ export default class MobileLayout extends React.PureComponent< getMemoForwardRefCallback(refIndex: number) { if (!this._memoForwardedPluginRef[refIndex]) { - this._memoForwardedPluginRef[refIndex] = (pluginRef: PlugibRefObject) => { + this._memoForwardedPluginRef[refIndex] = (pluginRef: PluginRef) => { this.pluginsRefs[refIndex] = pluginRef; }; } diff --git a/src/context/DashKitContext.ts b/src/context/DashKitContext.ts index 53d35be..8d2d40f 100644 --- a/src/context/DashKitContext.ts +++ b/src/context/DashKitContext.ts @@ -5,11 +5,13 @@ import type {DashKitProps} from '../components/DashKit'; import type { ConfigItem, ConfigLayout, + ItemDragProps, ItemParams, ItemState, ItemStateAndParams, ItemStateAndParamsChangeOptions, } from '../shared'; +import type {PluginRef, ReactGridLayoutProps} from '../typings'; type DashkitPropsPassedToCtx = Pick< DashKitProps, @@ -35,13 +37,19 @@ type DashkitPropsPassedToCtx = Pick< type PluginType = string; +type TemporaryLayout = { + data: ConfigLayout[]; + dragProps: ItemDragProps; +}; + export type DashKitCtxShape = DashkitPropsPassedToCtx & { registerManager: RegisterManager; forwardedMetaRef: React.ForwardedRef; configItems: ConfigItem[]; layout: ConfigLayout[]; - temporaryLayout: ConfigLayout[] | null; + layoutChange: (layout: ConfigLayout[]) => void; + temporaryLayout: TemporaryLayout | null; memorizeOriginalLayout: ( widgetId: string, preAutoHeightLayout: ConfigLayout, @@ -57,20 +65,22 @@ export type DashKitCtxShape = DashkitPropsPassedToCtx & { options: ItemStateAndParamsChangeOptions, ) => void; - getItemsMeta: (pluginsRefs: Array>) => Array>; + getItemsMeta: (pluginsRefs: Array) => Array>; reloadItems: ( - pluginsRefs: Array>, + pluginsRefs: Array, data: {silentLoading: boolean; noVeil: boolean}, ) => void; - onDrop: (newLayout: ConfigLayout, item: ConfigItem) => void; + onDrop: (newLayout: ConfigLayout[], item: ConfigLayout, e?: MouseEvent) => void; onDropDragOver: ( e: DragEvent | MouseEvent, group: string | void, - gridProps: Partial, + gridProps: ReactGridLayoutProps, groupLayout: ConfigLayout[], sharedItem: (Partial & {type: PluginType}) | void, ) => void | boolean; + onItemBlur: (item: ConfigItem) => void; + onItemFocus: (item: ConfigItem) => void; outerDnDEnable: boolean; dragOverPlugin: null | PluginType; }; diff --git a/src/typings/config.ts b/src/typings/config.ts index 4cb61cf..49dadab 100644 --- a/src/typings/config.ts +++ b/src/typings/config.ts @@ -1,4 +1,4 @@ -import type {Layout, Layouts} from 'react-grid-layout'; +import type {Layout} from 'react-grid-layout'; import type {Config, ConfigItem, ConfigLayout} from '../shared'; @@ -74,7 +74,7 @@ export interface DashKitGroup { } export type ItemManipulationCallback = (eventData: { - layout: Layouts; + layout: Layout[]; oldItem: Layout; newItem: Layout; placeholder: Layout; diff --git a/src/typings/plugin.ts b/src/typings/plugin.ts index 80c3412..3d8e327 100644 --- a/src/typings/plugin.ts +++ b/src/typings/plugin.ts @@ -41,6 +41,8 @@ export interface PluginWidgetProps { export type PluginDefaultLayout = Partial>; +export type PluginRef = React.RefObject | Record; + export interface Plugin

= any, T = StringParams> extends PluginBase { defaultLayout?: PluginDefaultLayout; renderer: (props: P, forwardedRef: React.RefObject) => React.ReactNode; From 9ba2f414a531863c17cd5fa5c350fd698883a7a1 Mon Sep 17 00:00:00 2001 From: ykornilov Date: Thu, 20 Nov 2025 14:43:42 +0300 Subject: [PATCH 2/7] chore: move ReactGridLayout from JS to TS (#278) * chore: move ReactGridLayout from JS to TS * chore: add comments --------- Co-authored-by: Yury Kornilov --- src/components/GridLayout/GridLayout.tsx | 72 ++++----- ...ReactGridLayout.js => ReactGridLayout.tsx} | 141 ++++++++++++++---- src/components/GridLayout/types.ts | 4 +- src/context/DashKitContext.ts | 2 +- 4 files changed, 155 insertions(+), 64 deletions(-) rename src/components/GridLayout/{ReactGridLayout.js => ReactGridLayout.tsx} (61%) diff --git a/src/components/GridLayout/GridLayout.tsx b/src/components/GridLayout/GridLayout.tsx index 55230fe..c19a7ce 100644 --- a/src/components/GridLayout/GridLayout.tsx +++ b/src/components/GridLayout/GridLayout.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import type {DragOverEvent} from 'react-grid-layout'; + import type {PluginRef, ReactGridLayoutProps} from 'src/typings'; import { @@ -319,7 +321,7 @@ export default class GridLayout extends React.PureComponent { + _onDropDragOver = ( + group: string, + e: DragOverEvent, + ): {w?: number; h?: number} | false | undefined => { const {editMode, dragOverPlugin, onDropDragOver, temporaryLayout} = this.context; const {currentDraggingElement} = this.state; @@ -757,43 +762,42 @@ export default class GridLayout extends React.PureComponent {renderItems.map((item, i) => { const keyId = item.id; diff --git a/src/components/GridLayout/ReactGridLayout.js b/src/components/GridLayout/ReactGridLayout.tsx similarity index 61% rename from src/components/GridLayout/ReactGridLayout.js rename to src/components/GridLayout/ReactGridLayout.tsx index ca0e829..f34d44e 100644 --- a/src/components/GridLayout/ReactGridLayout.js +++ b/src/components/GridLayout/ReactGridLayout.tsx @@ -1,23 +1,74 @@ import React from 'react'; +import type {Layout as RGLLayout} from 'react-grid-layout'; +// @ts-expect-error - utils is not exported in type definitions import ReactGridLayout, {WidthProvider, utils} from 'react-grid-layout'; import {DROPPING_ELEMENT_CLASS_NAME, OVERLAY_CLASS_NAME} from '../../constants'; +const isRefObject = ( + value: React.Ref, +): value is React.RefObject => { + return ( + typeof value === 'object' && + value !== null && + 'current' in value && + typeof value.current === 'object' + ); +}; + +type SharedDragPosition = { + offsetX: number; + offsetY: number; +}; + +type DragOverLayoutProps = ReactGridLayout.ReactGridLayoutProps & { + innerRef?: React.Ref; + isDragCaptured?: boolean; + hasSharedDragItem?: boolean; + sharedDragPosition?: SharedDragPosition; + onDragTargetRestore?: () => void; +}; + +type DragOverLayoutState = { + layout: RGLLayout[]; + activeDrag: RGLLayout | null; +}; + +type RGLLayoutWithPlaceholder = RGLLayout & {placeholder?: boolean}; + +type OnDragMethod = ( + i: string, + x: number, + y: number, + sintEv: {e: Event; node: HTMLElement}, +) => void; + class DragOverLayout extends ReactGridLayout { - constructor(...args) { - super(...args); + // @ts-expect-error - TypeScript doesn't allow direct property redeclaration in extending classes. We need to narrow the props type from ReactGridLayoutProps to DragOverLayoutProps for type safety in our custom methods + props: DragOverLayoutProps; + // @ts-expect-error - TypeScript doesn't allow direct property redeclaration in extending classes. State is initialized by parent constructor + state: DragOverLayoutState; + + parentOnDrag: OnDragMethod; + parentOnDragStop: OnDragMethod; + _savedDraggedOutLayout: RGLLayout[] | null = null; + + constructor(props: DragOverLayoutProps, context?: unknown) { + super(props, context); + // @ts-expect-error - onDrag is a protected method in parent class this.parentOnDrag = this.onDrag; + // @ts-expect-error - assigning custom method to parent's onDrag this.onDrag = this.extendedOnDrag; + // @ts-expect-error - onDragStop is a protected method in parent class this.parentOnDragStop = this.onDragStop; + // @ts-expect-error - assigning custom method to parent's onDragStop this.onDragStop = this.extendedOnDragStop; } - _savedDraggedOutLayout = null; - - componentDidMount() { + componentDidMount(): void { super.componentDidMount?.(); // If cursor is moved out of the window there is a bug @@ -33,7 +84,7 @@ class DragOverLayout extends ReactGridLayout { } } - componentWillUnmount() { + componentWillUnmount(): void { window.removeEventListener('dragend', this.resetExternalPlaceholder); const innerElement = this.getInnerElement(); @@ -48,12 +99,13 @@ class DragOverLayout extends ReactGridLayout { // react-grid-layout doens't calculate it's height when last element is removed // and just keeps the previous value // so for autosize to work in that case we are resetting it's height value - containerHeight() { + containerHeight(): string | undefined { if (this.props.autoSize && this.state.layout.length === 0) { return; } // eslint-disable-next-line consistent-return + // @ts-expect-error - containerHeight is a protected method in parent class return super.containerHeight(); } @@ -62,25 +114,30 @@ class DragOverLayout extends ReactGridLayout { // * rewrite whole ReactGridLayout.render method // so in that case don't try to use this class on it's own // or pass innerRef: React.MutableRef as it's not optional prop - getInnerElement() { - return this.props.innerRef?.current || null; + getInnerElement(): HTMLDivElement | null { + const {innerRef} = this.props; + + return innerRef && isRefObject(innerRef) && innerRef.current ? innerRef.current : null; } // Reset placeholder when item dragged from outside - resetExternalPlaceholder = () => { + resetExternalPlaceholder = (): void => { + // @ts-expect-error - dragEnterCounter is an internal property of parent class if (this.dragEnterCounter) { + // @ts-expect-error - dragEnterCounter is an internal property of parent class this.dragEnterCounter = 0; + // @ts-expect-error - removeDroppingPlaceholder is a protected method in parent class this.removeDroppingPlaceholder(); } }; // Hide placeholder when element is dragged out - hideLocalPlaceholder = (i) => { + hideLocalPlaceholder = (i: string): RGLLayout[] => { const {layout} = this.state; const {cols} = this.props; const savedLayout = layout.map((item) => ({...item})); - let hiddenElement; + let hiddenElement: RGLLayout | undefined; const newLayout = utils.compact( layout.filter((item) => { if (item.i === i) { @@ -106,7 +163,12 @@ class DragOverLayout extends ReactGridLayout { return savedLayout; }; - extendedOnDrag = (i, x, y, sintEv) => { + extendedOnDrag = ( + i: string, + x: number, + y: number, + sintEv: {e: Event; node: HTMLElement}, + ): void => { if (this.props.isDragCaptured) { if (!this._savedDraggedOutLayout) { this._savedDraggedOutLayout = this.hideLocalPlaceholder(i); @@ -120,14 +182,19 @@ class DragOverLayout extends ReactGridLayout { this.parentOnDrag(i, x, y, sintEv); }; - extendedOnDragStop = (i, x, y, sintEv) => { + extendedOnDragStop = ( + i: string, + x: number, + y: number, + sintEv: {e: Event; node: HTMLElement}, + ): void => { // Restoring layout if item was dropped outside of the grid if (this._savedDraggedOutLayout) { const savedLayout = this._savedDraggedOutLayout; const l = utils.getLayoutItem(savedLayout, i); // Create placeholder (display only) - const placeholder = { + const placeholder: RGLLayoutWithPlaceholder = { w: l.w, h: l.h, x: l.x, @@ -153,58 +220,72 @@ class DragOverLayout extends ReactGridLayout { }; // Proxy mouse events -> drag methods for dnd between groups - mouseEnterHandler = (e) => { + mouseEnterHandler = (e: MouseEvent): void => { if (this.props.hasSharedDragItem) { + // @ts-expect-error - onDragEnter is a protected method in parent class this.onDragEnter(e); } else if (this.props.isDragCaptured) { this.props.onDragTargetRestore?.(); } }; - mouseLeaveHandler = (e) => { + mouseLeaveHandler = (e: MouseEvent): void => { if (this.props.hasSharedDragItem) { + // @ts-expect-error - onDragLeave is a protected method in parent class this.onDragLeave(e); this.props.onDragTargetRestore?.(); } }; - mouseMoveHandler = (e) => { + mouseMoveHandler = (e: MouseEvent): void => { if (this.props.hasSharedDragItem) { - if (!e.nativeEvent) { + if (!(e as MouseEvent & {nativeEvent?: MouseEvent}).nativeEvent) { // Emulate nativeEvent for firefox - const target = this.getInnerElement() || e.target; + const target = this.getInnerElement() || (e.target as HTMLElement); - e.nativeEvent = { + (e as MouseEvent & {nativeEvent: Partial}).nativeEvent = { clientX: e.clientX, clientY: e.clientY, target, }; } + // @ts-expect-error - onDragOver is a protected method in parent class this.onDragOver(e); } }; - mouseUpHandler = (e) => { + mouseUpHandler = (e: MouseEvent): void => { if (this.props.hasSharedDragItem) { e.preventDefault(); const {droppingItem} = this.props; const {layout} = this.state; - const item = layout.find((l) => l.i === droppingItem.i); + const item = layout.find((l) => l.i === droppingItem?.i); // reset dragEnter counter on drop this.resetExternalPlaceholder(); - this.props.onDrop?.(layout, item, e); + if (item) { + this.props.onDrop?.(layout, item, e); + } } }; - calculateDroppingPosition(itemProps) { + calculateDroppingPosition(itemProps: { + containerWidth: number; + cols: number; + w: number; + h: number; + rowHeight: number; + margin: [number, number]; + transformScale: number; + droppingPosition: {left: number; top: number}; + }): {left: number; top: number} { const {containerWidth, cols, w, h, rowHeight, margin, transformScale, droppingPosition} = itemProps; const {sharedDragPosition} = this.props; - let offsetX, offsetY; + let offsetX: number, offsetY: number; if (sharedDragPosition) { offsetX = sharedDragPosition.offsetX; @@ -225,7 +306,11 @@ class DragOverLayout extends ReactGridLayout { // centering cursor on newly creted grid item // And cause grid-layout using it's own GridItem to make it look // like overlay adding className - processGridItem(child, isDroppingItem) { + processGridItem( + child: React.ReactElement, + isDroppingItem?: boolean, + ): React.ReactElement | undefined { + // @ts-expect-error - processGridItem is a protected method in parent class const gridItem = super.processGridItem?.(child, isDroppingItem); if (!gridItem) { @@ -249,4 +334,4 @@ class DragOverLayout extends ReactGridLayout { } // eslint-disable-next-line new-cap -export const Layout = WidthProvider(DragOverLayout); +export const Layout = WidthProvider(DragOverLayout); diff --git a/src/components/GridLayout/types.ts b/src/components/GridLayout/types.ts index 724ebe8..14ec5ff 100644 --- a/src/components/GridLayout/types.ts +++ b/src/components/GridLayout/types.ts @@ -1,3 +1,5 @@ +import type {DragOverEvent} from 'react-grid-layout'; + import type {ReactGridLayoutProps} from 'src/typings'; import type {ConfigItem, ConfigLayout, ItemDragProps} from '../../shared'; @@ -61,7 +63,7 @@ export type GroupCallbacks = { onResize: GroupCallback; onResizeStop: GroupCallback; onDrop: (layout: ConfigLayout[], item: ConfigLayout | undefined, e: MouseEvent) => void | false; - onDropDragOver: (e: DragEvent | MouseEvent) => void | boolean; + onDropDragOver: (event: DragOverEvent) => {w?: number; h?: number} | false | undefined; onDragTargetRestore: (group?: string) => void; }; diff --git a/src/context/DashKitContext.ts b/src/context/DashKitContext.ts index 8d2d40f..cdb4581 100644 --- a/src/context/DashKitContext.ts +++ b/src/context/DashKitContext.ts @@ -78,7 +78,7 @@ export type DashKitCtxShape = DashkitPropsPassedToCtx & { gridProps: ReactGridLayoutProps, groupLayout: ConfigLayout[], sharedItem: (Partial & {type: PluginType}) | void, - ) => void | boolean; + ) => {w?: number; h?: number} | false | undefined; onItemBlur: (item: ConfigItem) => void; onItemFocus: (item: ConfigItem) => void; outerDnDEnable: boolean; From ccdb2df269838c5192936ade4f127b43b565e77d Mon Sep 17 00:00:00 2001 From: ykornilov Date: Fri, 21 Nov 2025 19:36:17 +0300 Subject: [PATCH 3/7] fix!: move GridItem from JS to TS (#279) * fix!: move GridItem from JS to TS --------- Co-authored-by: Yury Kornilov --- .../GridItem/{GridItem.js => GridItem.tsx} | 118 ++++++++++-------- src/components/GridLayout/GridLayout.tsx | 36 +++--- .../OverlayControls/OverlayControls.tsx | 2 +- 3 files changed, 81 insertions(+), 75 deletions(-) rename src/components/GridItem/{GridItem.js => GridItem.tsx} (73%) diff --git a/src/components/GridItem/GridItem.js b/src/components/GridItem/GridItem.tsx similarity index 73% rename from src/components/GridItem/GridItem.js rename to src/components/GridItem/GridItem.tsx index 672e9da..59e4f50 100644 --- a/src/components/GridItem/GridItem.js +++ b/src/components/GridItem/GridItem.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import PropTypes from 'prop-types'; - import {FOCUSED_CLASS_NAME} from '../../constants'; import {DashKitContext} from '../../context'; +import type {ConfigItem, ConfigLayout} from '../../shared'; +import type {PluginRef, ReactGridLayoutProps} from '../../typings'; import {cn} from '../../utils/cn'; import Item from '../Item/Item'; import OverlayControls from '../OverlayControls/OverlayControls'; @@ -13,28 +13,28 @@ import './GridItem.scss'; const b = cn('dashkit-grid-item'); class WindowFocusObserver { - constructor() { - this.subscribers = 0; - this.isFocused = !document.hidden; + subscribers = 0; + isFocused = !document.hidden; + constructor() { window.addEventListener('blur', this.blurHandler, true); window.addEventListener('focus', this.focusHandler, true); } - blurHandler = (e) => { + blurHandler = (e: FocusEvent) => { if (e.target === window) { this.isFocused = false; } }; - focusHandler = (e) => { + focusHandler = (e: FocusEvent) => { if (e.target === window) { this.isFocused = true; } }; // Method to get state after all blur\focus events in document are triggered - async getFocusedState() { + async getFocusedState(): Promise { return new Promise((resolve) => { requestAnimationFrame(() => { resolve(this.isFocused); @@ -45,43 +45,53 @@ class WindowFocusObserver { const windowFocusObserver = new WindowFocusObserver(); -class GridItem extends React.PureComponent { - static propTypes = { - adjustWidgetLayout: PropTypes.func.isRequired, - gridLayout: PropTypes.object, - id: PropTypes.string, - item: PropTypes.object, - isDragging: PropTypes.bool, - isDraggedOut: PropTypes.bool, - layout: PropTypes.array, - - forwardedRef: PropTypes.any, - forwardedPluginRef: PropTypes.any, - isPlaceholder: PropTypes.bool, - - onItemMountChange: PropTypes.func, - onItemRender: PropTypes.func, - - // from react-grid-layout: - children: PropTypes.node, - className: PropTypes.string, - style: PropTypes.object, - noOverlay: PropTypes.bool, - focusable: PropTypes.bool, - withCustomHandle: PropTypes.bool, - onMouseDown: PropTypes.func, - onMouseUp: PropTypes.func, - onTouchEnd: PropTypes.func, - onTouchStart: PropTypes.func, - onItemFocus: PropTypes.func, - onItemBlur: PropTypes.func, - }; - +type GridItemProps = { + adjustWidgetLayout: (data: { + widgetId: string; + needSetDefault?: boolean; + adjustedWidgetLayout?: ConfigLayout; + }) => void; + gridLayout?: ReactGridLayoutProps; + id?: string; + item: ConfigItem; + isDragging?: boolean; + isDraggedOut?: boolean; + layout?: ConfigLayout[]; + + forwardedRef?: React.Ref; + forwardedPluginRef?: (pluginRef: PluginRef) => void; + isPlaceholder?: boolean; + + onItemMountChange?: (item: ConfigItem, meta: {isAsync: boolean; isMounted: boolean}) => void; + onItemRender?: (item: ConfigItem) => void; + + // from react-grid-layout: + children?: React.ReactNode; + className?: string; + style?: React.CSSProperties; + noOverlay?: boolean; + focusable?: boolean; + withCustomHandle?: boolean; + onMouseDown?: (e: React.MouseEvent) => void; + onMouseUp?: (e: React.MouseEvent) => void; + onTouchEnd?: (e: React.TouchEvent) => void; + onTouchStart?: (e: React.TouchEvent) => void; + onItemFocus?: (item: ConfigItem) => void; + onItemBlur?: (item: ConfigItem) => void; +}; + +type GridItemState = { + isFocused: boolean; +}; + +class GridItem extends React.PureComponent { static contextType = DashKitContext; + context!: React.ContextType; _isAsyncItem = false; + controller: AbortController | null = null; - state = { + state: GridItemState = { isFocused: false, }; @@ -105,7 +115,7 @@ class GridItem extends React.PureComponent {

); @@ -114,17 +124,13 @@ class GridItem extends React.PureComponent { onOverlayItemClick = () => { // Creating button element to trigger focus out const focusDummy = document.createElement('button'); - const styles = { + Object.assign(focusDummy.style, { width: '0', height: '0', opacity: '0', position: 'fixed', top: '0', left: '0', - }; - - Object.entries(styles).forEach(([key, value]) => { - focusDummy.style[key] = value; }); // requestAnimationFrame to make call after alert() or confirm() @@ -187,14 +193,16 @@ class GridItem extends React.PureComponent { const {editMode} = this.context; const {isFocused} = this.state; - const width = Number.parseInt(style.width, 10); - const height = Number.parseInt(style.height, 10); - const transform = style.transform; + const width = + style?.width === undefined ? undefined : Number.parseInt(String(style.width), 10); + const height = + style?.height === undefined ? undefined : Number.parseInt(String(style.height), 10); + const transform = style?.transform; const preparedClassName = (editMode ? className : className - .replace('react-resizable', '') + ?.replace('react-resizable', '') .replace('react-draggable', '') .replace(FOCUSED_CLASS_NAME, '')) + (isFocused ? ` ${FOCUSED_CLASS_NAME}` : ''); @@ -254,9 +262,11 @@ class GridItem extends React.PureComponent { } } -const GridItemForwarderRef = React.forwardRef((props, ref) => { - return ; -}); +const GridItemForwarderRef = React.forwardRef>( + (props, ref) => { + return ; + }, +); GridItemForwarderRef.displayName = 'forwardRef(GridItem)'; diff --git a/src/components/GridLayout/GridLayout.tsx b/src/components/GridLayout/GridLayout.tsx index c19a7ce..30b16cb 100644 --- a/src/components/GridLayout/GridLayout.tsx +++ b/src/components/GridLayout/GridLayout.tsx @@ -712,10 +712,8 @@ export default class GridLayout extends React.PureComponent ); })} diff --git a/src/components/OverlayControls/OverlayControls.tsx b/src/components/OverlayControls/OverlayControls.tsx index 29f4a72..22dccaa 100644 --- a/src/components/OverlayControls/OverlayControls.tsx +++ b/src/components/OverlayControls/OverlayControls.tsx @@ -77,7 +77,7 @@ interface OverlayControlsDefaultProps { interface OverlayControlsProps extends OverlayControlsDefaultProps { configItem: ConfigItem; - onItemClick?: () => void | null; + onItemClick?: () => void; } type PreparedCopyItemOptionsArg = Pick & { From 6600267a95c19c594a229cb7df92dd565d75b6c5 Mon Sep 17 00:00:00 2001 From: ykornilov Date: Wed, 26 Nov 2025 16:56:31 +0300 Subject: [PATCH 4/7] chore: translate Item to TS (#280) Co-authored-by: Yury Kornilov --- src/components/GridLayout/GridLayout.tsx | 1 + src/components/Item/{Item.js => Item.tsx} | 31 +++++++++++++---------- src/typings/plugin.ts | 12 ++++++--- 3 files changed, 27 insertions(+), 17 deletions(-) rename src/components/Item/{Item.js => Item.tsx} (79%) diff --git a/src/components/GridLayout/GridLayout.tsx b/src/components/GridLayout/GridLayout.tsx index 30b16cb..515ee73 100644 --- a/src/components/GridLayout/GridLayout.tsx +++ b/src/components/GridLayout/GridLayout.tsx @@ -31,6 +31,7 @@ import type { const hasPluginId = (value: PluginRef): value is {props: {id: string}} => { return ( + value !== null && 'props' in value && typeof value.props === 'object' && value.props !== null && diff --git a/src/components/Item/Item.js b/src/components/Item/Item.tsx similarity index 79% rename from src/components/Item/Item.js rename to src/components/Item/Item.tsx index 4fa6c7f..9112087 100644 --- a/src/components/Item/Item.js +++ b/src/components/Item/Item.tsx @@ -1,16 +1,28 @@ import React from 'react'; -import PropTypes from 'prop-types'; - import {prepareItem} from '../../hocs/prepareItem'; +import type {ConfigItem} from '../../shared/types'; +import type {PluginRef, PluginWidgetProps} from '../../typings'; import {cn} from '../../utils/cn'; +import type {RegisterManager} from '../../utils/register-manager'; import './Item.scss'; const b = cn('dashkit-item'); +type ItemProps = { + registerManager: RegisterManager; + rendererProps: Omit; + type: string; + isPlaceholder?: boolean; + forwardedPluginRef?: (pluginRef: PluginRef) => void; + onItemRender?: (item: ConfigItem) => void; + onItemMountChange?: (item: ConfigItem, meta: {isAsync: boolean; isMounted: boolean}) => void; + item: ConfigItem; +}; + // TODO: getDerivedStateFromError и заглушка с ошибкой -const Item = ({ +const Item: React.FC = ({ registerManager, rendererProps, type, @@ -50,6 +62,8 @@ const Item = ({ }); }; } + + return undefined; }, []); const onLoad = React.useCallback(() => { @@ -88,15 +102,4 @@ const Item = ({ ); }; -Item.propTypes = { - forwardedPluginRef: PropTypes.any, - rendererProps: PropTypes.object, - registerManager: PropTypes.object, - type: PropTypes.string, - isPlaceholder: PropTypes.bool, - onItemRender: PropTypes.func, - onItemMountChange: PropTypes.func, - item: PropTypes.object, -}; - export default prepareItem(Item); diff --git a/src/typings/plugin.ts b/src/typings/plugin.ts index 3d8e327..84d06e7 100644 --- a/src/typings/plugin.ts +++ b/src/typings/plugin.ts @@ -41,10 +41,16 @@ export interface PluginWidgetProps { export type PluginDefaultLayout = Partial>; -export type PluginRef = React.RefObject | Record; +export type PluginRef = object | null; export interface Plugin

= any, T = StringParams> extends PluginBase { defaultLayout?: PluginDefaultLayout; - renderer: (props: P, forwardedRef: React.RefObject) => React.ReactNode; - placeholderRenderer?: (props: P, forwardedRef: React.RefObject) => React.ReactNode; + renderer: ( + props: P, + forwardedRef: ((instance: PluginRef) => void) | undefined, + ) => React.ReactNode; + placeholderRenderer?: ( + props: P, + forwardedRef: ((instance: PluginRef) => void) | undefined, + ) => React.ReactNode; } From 1f4a779b8f6e903672701d18160ae4048ef8d9e4 Mon Sep 17 00:00:00 2001 From: ykornilov Date: Tue, 2 Dec 2025 18:19:03 +0300 Subject: [PATCH 5/7] fix!: move prepareItem from JS to TS (#284) Co-authored-by: Yury Kornilov --- src/components/GridItem/GridItem.tsx | 4 +- src/components/Item/Item.tsx | 16 +--- src/components/Item/types.ts | 22 +++++ src/components/MobileLayout/MobileLayout.tsx | 4 +- src/context/DashKitContext.ts | 11 ++- src/hocs/{prepareItem.js => prepareItem.tsx} | 93 +++++++++++--------- src/typings/common.ts | 4 +- src/typings/plugin.ts | 5 +- src/utils/register-manager.ts | 6 +- 9 files changed, 96 insertions(+), 69 deletions(-) create mode 100644 src/components/Item/types.ts rename src/hocs/{prepareItem.js => prepareItem.tsx} (55%) diff --git a/src/components/GridItem/GridItem.tsx b/src/components/GridItem/GridItem.tsx index 59e4f50..30f4eff 100644 --- a/src/components/GridItem/GridItem.tsx +++ b/src/components/GridItem/GridItem.tsx @@ -52,11 +52,11 @@ type GridItemProps = { adjustedWidgetLayout?: ConfigLayout; }) => void; gridLayout?: ReactGridLayoutProps; - id?: string; + id: string; item: ConfigItem; isDragging?: boolean; isDraggedOut?: boolean; - layout?: ConfigLayout[]; + layout: ConfigLayout[]; forwardedRef?: React.Ref; forwardedPluginRef?: (pluginRef: PluginRef) => void; diff --git a/src/components/Item/Item.tsx b/src/components/Item/Item.tsx index 9112087..26474e7 100644 --- a/src/components/Item/Item.tsx +++ b/src/components/Item/Item.tsx @@ -1,26 +1,14 @@ import React from 'react'; import {prepareItem} from '../../hocs/prepareItem'; -import type {ConfigItem} from '../../shared/types'; -import type {PluginRef, PluginWidgetProps} from '../../typings'; import {cn} from '../../utils/cn'; -import type {RegisterManager} from '../../utils/register-manager'; + +import type {ItemProps} from './types'; import './Item.scss'; const b = cn('dashkit-item'); -type ItemProps = { - registerManager: RegisterManager; - rendererProps: Omit; - type: string; - isPlaceholder?: boolean; - forwardedPluginRef?: (pluginRef: PluginRef) => void; - onItemRender?: (item: ConfigItem) => void; - onItemMountChange?: (item: ConfigItem, meta: {isAsync: boolean; isMounted: boolean}) => void; - item: ConfigItem; -}; - // TODO: getDerivedStateFromError и заглушка с ошибкой const Item: React.FC = ({ registerManager, diff --git a/src/components/Item/types.ts b/src/components/Item/types.ts new file mode 100644 index 0000000..dd733f8 --- /dev/null +++ b/src/components/Item/types.ts @@ -0,0 +1,22 @@ +import type {ConfigItem, ItemParams} from '../../shared/types'; +import type {PluginRef, PluginWidgetProps} from '../../typings'; +import type {RegisterManager} from '../../utils/register-manager'; + +export type RendererProps = Omit< + PluginWidgetProps, + 'onBeforeLoad' | 'width' | 'height' +> & { + width?: number; + height?: number; +}; + +export type ItemProps = { + forwardedPluginRef?: (ref: PluginRef) => void; + isPlaceholder?: boolean; + item: ConfigItem; + registerManager: RegisterManager; + rendererProps: RendererProps; + type: string; + onItemRender?: (item: ConfigItem) => void; + onItemMountChange?: (item: ConfigItem, meta: {isAsync: boolean; isMounted: boolean}) => void; +}; diff --git a/src/components/MobileLayout/MobileLayout.tsx b/src/components/MobileLayout/MobileLayout.tsx index 377b796..c9c18a4 100644 --- a/src/components/MobileLayout/MobileLayout.tsx +++ b/src/components/MobileLayout/MobileLayout.tsx @@ -33,7 +33,7 @@ export default class MobileLayout extends React.PureComponent< _memoLayout = this.context.layout; _memoForwardedPluginRef: Array<(refObject: PluginRef) => void> = []; - _memoAdjustWidgetLayout: Record void> = {}; + _memoAdjustWidgetLayout: Record void> = {}; state: MobileLayoutState = { itemsWithActiveAutoheight: {}, @@ -131,7 +131,7 @@ export default class MobileLayout extends React.PureComponent< return this._memoForwardedPluginRef[refIndex]; } - adjustWidgetLayout(id: string, {needSetDefault}: {needSetDefault: boolean}) { + adjustWidgetLayout(id: string, {needSetDefault}: {needSetDefault?: boolean}) { if (needSetDefault) { const indexesOfItemsWithActiveAutoheight = { ...this.state.itemsWithActiveAutoheight, diff --git a/src/context/DashKitContext.ts b/src/context/DashKitContext.ts index cdb4581..cea6881 100644 --- a/src/context/DashKitContext.ts +++ b/src/context/DashKitContext.ts @@ -11,7 +11,7 @@ import type { ItemStateAndParams, ItemStateAndParamsChangeOptions, } from '../shared'; -import type {PluginRef, ReactGridLayoutProps} from '../typings'; +import type {ContextProps, PluginRef, ReactGridLayoutProps, SettingsProps} from '../typings'; type DashkitPropsPassedToCtx = Pick< DashKitProps, @@ -42,7 +42,10 @@ type TemporaryLayout = { dragProps: ItemDragProps; }; -export type DashKitCtxShape = DashkitPropsPassedToCtx & { +export type DashKitCtxShape = Omit & { + context: ContextProps; + settings: SettingsProps; + registerManager: RegisterManager; forwardedMetaRef: React.ForwardedRef; @@ -57,12 +60,12 @@ export type DashKitCtxShape = DashkitPropsPassedToCtx & { ) => void; revertToOriginalLayout: (widgetId: string) => void; - itemsState?: Record; + itemsState: Record; itemsParams: Record; onItemStateAndParamsChange: ( id: string, stateAndParams: ItemStateAndParams, - options: ItemStateAndParamsChangeOptions, + options?: ItemStateAndParamsChangeOptions, ) => void; getItemsMeta: (pluginsRefs: Array) => Array>; diff --git a/src/hocs/prepareItem.js b/src/hocs/prepareItem.tsx similarity index 55% rename from src/hocs/prepareItem.js rename to src/hocs/prepareItem.tsx index bc7183a..3846cde 100644 --- a/src/hocs/prepareItem.js +++ b/src/hocs/prepareItem.tsx @@ -1,59 +1,69 @@ import React from 'react'; import isEqual from 'lodash/isEqual'; -import PropTypes from 'prop-types'; +import type {ItemProps, RendererProps} from '../components/Item/types'; import {DashKitContext} from '../context'; +import type {ConfigItem, ConfigLayout} from '../shared'; +import type { + ItemStateAndParams, + ItemStateAndParamsChangeOptions, +} from '../shared/types/state-and-params'; +import type {PluginRef, PluginWidgetProps, ReactGridLayoutProps} from '../typings'; + +type PrepareItemProps = { + gridLayout?: ReactGridLayoutProps; + adjustWidgetLayout: PluginWidgetProps['adjustWidgetLayout']; + layout: ConfigLayout[]; + id: string; + item: ConfigItem; + shouldItemUpdate?: boolean; + width?: number; + height?: number; + transform?: string; + isPlaceholder?: boolean; + + onItemRender?: (item: ConfigItem) => void; + onItemMountChange?: (item: ConfigItem, meta: {isAsync: boolean; isMounted: boolean}) => void; + + forwardedPluginRef?: (ref: PluginRef) => void; +}; + +export function prepareItem( + WrappedComponent: React.ComponentType, +): React.ComponentClass { + return class PrepareItem extends React.Component { + static contextType = DashKitContext; + context!: React.ContextType; -export function prepareItem(Component) { - return class PrepareItem extends React.Component { - static propTypes = { - gridLayout: PropTypes.object, - adjustWidgetLayout: PropTypes.func.isRequired, - layout: PropTypes.array, - id: PropTypes.string, - item: PropTypes.object, - shouldItemUpdate: PropTypes.bool, - width: PropTypes.number, - height: PropTypes.number, - transform: PropTypes.string, - isPlaceholder: PropTypes.bool, - - onItemRender: PropTypes.func, - onItemMountChange: PropTypes.func, - - forwardedPluginRef: PropTypes.any, - }; + _currentRenderProps: RendererProps = {} as RendererProps; - shouldComponentUpdate(nextProps) { + shouldComponentUpdate(nextProps: PrepareItemProps) { const {width, height, transform} = this.props; const {width: widthNext, height: heightNext, transform: transformNext} = nextProps; - if ( - !nextProps.shouldItemUpdate && - width === widthNext && - height === heightNext && - transform === transformNext - ) { - return false; - } - return true; - } - static contextType = DashKitContext; + return ( + nextProps.shouldItemUpdate || + width !== widthNext || + height !== heightNext || + transform !== transformNext + ); + } - _onStateAndParamsChange = (stateAndParams, options) => { + _onStateAndParamsChange = ( + stateAndParams: ItemStateAndParams, + options?: ItemStateAndParamsChangeOptions, + ) => { this.context.onItemStateAndParamsChange(this.props.id, stateAndParams, options); }; - _currentRenderProps = {}; - getRenderProps = () => { - const {id, width, height, item, adjustWidgetLayout, layout, isPlaceholder, gridLayout} = - this.props; + getRenderProps = (): RendererProps => { + const {id, width, height, item, adjustWidgetLayout, layout, gridLayout} = this.props; const {itemsState, itemsParams, registerManager, settings, context, editMode} = this.context; const {data, defaults, namespace} = item; - const rendererProps = { + const rendererProps: RendererProps = { data, editMode, params: itemsParams[id], @@ -69,16 +79,15 @@ export function prepareItem(Component) { layout, gridLayout: gridLayout || registerManager.gridLayout, adjustWidgetLayout, - isPlaceholder, }; const changedProp = Object.entries(rendererProps).find(([key, value]) => { // Checking gridLayoout deep as groups gridProperties method has tendancy to creat new objects if (key === 'gridLayout') { - return !isEqual(this._currentRenderProps[key], value); + return !isEqual(this._currentRenderProps[key as keyof RendererProps], value); } - return this._currentRenderProps[key] !== value; + return this._currentRenderProps[key as keyof RendererProps] !== value; }); if (changedProp) { @@ -95,7 +104,7 @@ export function prepareItem(Component) { const {type} = item; return ( - { id: string; @@ -31,7 +32,9 @@ export interface PluginWidgetProps { settings: SettingsProps; context: ContextProps; layout: WidgetLayout[]; - gridLayout: ReactGridLayout.ReactGridLayoutProps; + gridLayout: Omit & { + compactType?: CompactType; + }; adjustWidgetLayout: (data: { widgetId: string; needSetDefault?: boolean; diff --git a/src/utils/register-manager.ts b/src/utils/register-manager.ts index dfc129d..6b762fc 100644 --- a/src/utils/register-manager.ts +++ b/src/utils/register-manager.ts @@ -1,6 +1,6 @@ import ReactGridLayout from 'react-grid-layout'; -import type {Plugin, PluginDefaultLayout, Settings} from '../typings'; +import type {CompactType, Plugin, PluginDefaultLayout, Settings} from '../typings'; interface RegisterManagerDefaultLayout { x: number; @@ -29,7 +29,9 @@ export class RegisterManager { minW: 4, minH: 2, }; - private _gridLayout: ReactGridLayout.ReactGridLayoutProps = { + private _gridLayout: Omit & { + compactType?: CompactType; + } = { rowHeight: 18, cols: 36, margin: [2, 2], From 907babb39c877c2142a96ae5257f11848354af0c Mon Sep 17 00:00:00 2001 From: ykornilov Date: Mon, 29 Dec 2025 13:24:09 +0300 Subject: [PATCH 6/7] fix: move withContext from JS to TS (#285) Co-authored-by: Yury Kornilov --- src/components/DashKitView/DashKitView.tsx | 7 +- src/components/GridLayout/GridLayout.tsx | 4 +- src/context/DashKitContext.ts | 21 ++- src/hocs/{withContext.js => withContext.tsx} | 189 +++++++++++++++---- src/utils/update-manager.ts | 3 +- 5 files changed, 165 insertions(+), 59 deletions(-) rename src/hocs/{withContext.js => withContext.tsx} (74%) diff --git a/src/components/DashKitView/DashKitView.tsx b/src/components/DashKitView/DashKitView.tsx index f915055..815951e 100644 --- a/src/components/DashKitView/DashKitView.tsx +++ b/src/components/DashKitView/DashKitView.tsx @@ -2,10 +2,9 @@ import React from 'react'; import {DashKitContext} from '../../context'; import {withContext} from '../../hocs/withContext'; +import type {DashKitWithContextProps} from '../../hocs/withContext'; import {useCalcPropsLayout} from '../../hooks/useCalcLayout'; -import type {RegisterManager} from '../../utils'; import {cn} from '../../utils/cn'; -import type {DashKitProps} from '../DashKit'; import GridLayout from '../GridLayout/GridLayout'; import MobileLayout from '../MobileLayout/MobileLayout'; @@ -13,9 +12,7 @@ import './DashKitView.scss'; const b = cn('dashkit'); -type DashKitViewProps = DashKitProps & { - registerManager: RegisterManager; -}; +type DashKitViewProps = Omit; function DashKitView() { const context = React.useContext(DashKitContext); diff --git a/src/components/GridLayout/GridLayout.tsx b/src/components/GridLayout/GridLayout.tsx index 515ee73..0bd7df2 100644 --- a/src/components/GridLayout/GridLayout.tsx +++ b/src/components/GridLayout/GridLayout.tsx @@ -12,7 +12,7 @@ import { } from '../../constants'; import {DashKitContext} from '../../context'; import type {DashKitCtxShape} from '../../context'; -import type {ConfigItem, ConfigLayout} from '../../shared'; +import type {ConfigItem, ConfigLayout, DraggedOverItem} from '../../shared'; import {resolveLayoutGroup} from '../../utils'; import GridItem from '../GridItem/GridItem'; @@ -682,7 +682,7 @@ export default class GridLayout extends React.PureComponent; -type PluginType = string; - -type TemporaryLayout = { +export type TemporaryLayout = { data: ConfigLayout[]; dragProps: ItemDragProps; }; @@ -76,16 +77,16 @@ export type DashKitCtxShape = Omit void; onDropDragOver: ( - e: DragEvent | MouseEvent, - group: string | void, + e: DragOverEvent, + group: string | undefined, gridProps: ReactGridLayoutProps, groupLayout: ConfigLayout[], - sharedItem: (Partial & {type: PluginType}) | void, + sharedItem?: DraggedOverItem, ) => {w?: number; h?: number} | false | undefined; - onItemBlur: (item: ConfigItem) => void; - onItemFocus: (item: ConfigItem) => void; + onItemBlur?: (item: ConfigItem) => void; + onItemFocus?: (item: ConfigItem) => void; outerDnDEnable: boolean; - dragOverPlugin: null | PluginType; + dragOverPlugin: null | RegisterManagerPlugin; }; const DashKitContext = React.createContext({} as DashKitCtxShape); diff --git a/src/hocs/withContext.js b/src/hocs/withContext.tsx similarity index 74% rename from src/hocs/withContext.js rename to src/hocs/withContext.tsx index 6f0e6e7..eb08b8a 100644 --- a/src/hocs/withContext.js +++ b/src/hocs/withContext.tsx @@ -3,6 +3,10 @@ import React from 'react'; import isEqual from 'lodash/isEqual'; import pick from 'lodash/pick'; +import type { + OverlayControlItem, + PreparedCopyItemOptions, +} from '../components/OverlayControls/OverlayControls'; import { COMPACT_TYPE_HORIZONTAL_NOWRAP, DEFAULT_GROUP, @@ -11,24 +15,111 @@ import { TEMPORARY_ITEM_ID, } from '../constants/common'; import {DashKitContext, DashKitDnDContext, DashkitOvelayControlsContext} from '../context'; +import type {DashKitCtxShape, OverlayControlsCtxShape, TemporaryLayout} from '../context'; import {useDeepEqualMemo} from '../hooks/useDeepEqualMemo'; +import type { + Config, + ConfigItem, + ConfigLayout, + GlobalParams, + ItemDropProps, + ItemsStateAndParams, +} from '../shared'; import {getAllConfigItems, getItemsParams, getItemsState} from '../shared'; +import type { + ContextProps, + DashKitGroup, + ItemManipulationCallback, + MenuItem, + PluginRef, + SettingsProps, +} from '../typings'; +import type {RegisterManager, RegisterManagerPlugin} from '../utils'; import {UpdateManager, resolveLayoutGroup} from '../utils'; -const ITEM_PROPS = ['i', 'h', 'w', 'x', 'y', 'parent']; +const ITEM_PROPS = ['i', 'h', 'w', 'x', 'y', 'parent'] as const; + +export type DashKitWithContextProps = { + config: Config; + itemsStateAndParams: ItemsStateAndParams; + groups?: DashKitGroup[]; + onChange: (data: { + config: Config; + itemsStateAndParams: ItemsStateAndParams; + groups?: DashKitGroup[]; + }) => void; + layout: ConfigLayout[]; + registerManager: RegisterManager; + defaultGlobalParams: GlobalParams; + globalParams: GlobalParams; + onItemEdit: (item: ConfigItem) => void; + context: ContextProps; + noOverlay: boolean; + focusable?: boolean; + settings: SettingsProps; + onItemMountChange?: (item: ConfigItem, state: {isAsync: boolean; isMounted: boolean}) => void; + onItemRender?: (item: ConfigItem) => void; + forwardedMetaRef: React.ForwardedRef; + draggableHandleClassName?: string; + onDrop?: (dropProps: ItemDropProps) => void; + overlayControls?: Record | null; + overlayMenuItems?: MenuItem[] | null; + getPreparedCopyItemOptions?: (options: PreparedCopyItemOptions) => PreparedCopyItemOptions; + onCopyFulfill?: (error: null | Error, data?: PreparedCopyItemOptions) => void; + editMode: boolean; + onItemFocus?: (item: ConfigItem) => void; + onItemBlur?: (item: ConfigItem) => void; + onDragStart?: ItemManipulationCallback; + onDrag?: ItemManipulationCallback; + onDragStop?: ItemManipulationCallback; + onResizeStart?: ItemManipulationCallback; + onResize?: ItemManipulationCallback; + onResizeStop?: ItemManipulationCallback; +}; + +type OriginalLayouts = Record; + +type AdjustedLayouts = Record; + +type NowrapAdjustedLayouts = Record; + +type UseMemoStateContextResult = { + dashkitContextValue: DashKitCtxShape; + controlsContextValue: OverlayControlsCtxShape; +}; + +const hasGetMeta = (value: PluginRef): value is {getMeta: () => Promise} => { + return ( + typeof value === 'object' && + value !== null && + 'getMeta' in value && + typeof value.getMeta === 'function' + ); +}; + +const hasReload = ( + value: PluginRef, +): value is {reload: (data: {silentLoading: boolean; noVeil: boolean}) => void} => { + return ( + typeof value === 'object' && + value !== null && + 'reload' in value && + typeof value.reload === 'function' + ); +}; -function useMemoStateContext(props) { +function useMemoStateContext(props: DashKitWithContextProps): UseMemoStateContextResult { // так как мы не хотим хранить параметры виджета с активированной автовысотой в сторе и на сервере, актуальный // (видимый юзером в конкретный момент времени) лэйаут (массив объектов с данными о ширине, высоте, // расположении конкретного виджета на сетке) будет храниться в стейте, но, для того, чтобы в стор попадал // лэйаут без учета вижетов с активированной автовысотой, в момент "подстройки" высоты виджета значение h // (высота) из конфига будет запоминаться в originalLayouts, новое значение высоты в adjustedLayouts - const originalLayouts = React.useRef({}); - const adjustedLayouts = React.useRef({}); - const nowrapAdjustedLayouts = React.useRef({}); + const originalLayouts = React.useRef({}); + const adjustedLayouts = React.useRef({}); + const nowrapAdjustedLayouts = React.useRef({}); - const [temporaryLayout, setTemporaryLayout] = React.useState(null); + const [temporaryLayout, setTemporaryLayout] = React.useState(null); const resetTemporaryLayout = React.useCallback( () => setTemporaryLayout(null), [setTemporaryLayout], @@ -64,22 +155,22 @@ function useMemoStateContext(props) { // "подстроенный"; чтобы, для сохранения в сторе "ушли" значения без учёта подстройки (как если бы у этих // виджетов автовысота была деактивирована) корректируем их используя this.originalLayouts const onLayoutChange = React.useCallback( - (layout) => { + (layout: ConfigLayout[]) => { const currentInnerLayout = layout.map((item) => { if (item.i in originalLayouts.current) { // eslint-disable-next-line no-unused-vars - const {parent, ...originalCopy} = originalLayouts.current[item.i]; + const {parent: _parent, ...originalCopy} = originalLayouts.current[item.i]; // Updating original if parent has changed and saving copy as original // or leaving default if (item.parent) { - originalCopy.parent = item.parent; + (originalCopy as ConfigLayout).parent = item.parent; } originalCopy.w = item.w; originalCopy.x = item.x; originalCopy.y = item.y; - return originalCopy; + return originalCopy satisfies ConfigLayout; } else { return {...item}; } @@ -98,7 +189,7 @@ function useMemoStateContext(props) { ); const getLayoutItem = React.useCallback( - (id) => { + (id: string) => { return props.config.layout.find(({i}) => i === id); }, [props.config.layout], @@ -111,7 +202,7 @@ function useMemoStateContext(props) { ); const onItemRemove = React.useCallback( - (id) => { + (id: string) => { delete nowrapAdjustedLayouts.current[id]; delete adjustedLayouts.current[id]; delete originalLayouts.current[id]; @@ -145,7 +236,9 @@ function useMemoStateContext(props) { ], ); - const onItemStateAndParamsChange = React.useCallback( + const onItemStateAndParamsChange = React.useCallback< + DashKitCtxShape['onItemStateAndParamsChange'] + >( (id, stateAndParams, options) => { onChange({ itemsStateAndParams: UpdateManager.changeStateAndParams({ @@ -160,7 +253,7 @@ function useMemoStateContext(props) { [props.config, props.itemsStateAndParams, onChange], ); - const memorizeOriginalLayout = React.useCallback( + const memorizeOriginalLayout = React.useCallback( (widgetId, preAutoHeightLayout, postAutoHeightLayout) => { let needUpdateLayout = false; if (!(widgetId in originalLayouts.current)) { @@ -179,7 +272,7 @@ function useMemoStateContext(props) { [], ); - const revertToOriginalLayout = React.useCallback((widgetId) => { + const revertToOriginalLayout = React.useCallback((widgetId: string) => { const needUpdateLayout = widgetId in adjustedLayouts.current || widgetId in originalLayouts.current; delete adjustedLayouts.current[widgetId]; @@ -193,10 +286,13 @@ function useMemoStateContext(props) { const groups = props.groups; const layout = props.layout; const defaultProps = props.registerManager.gridLayout || {}; - const nowrapGroups = {}; + const nowrapGroups: Record = {}; let hasNowrapGroups = false; - if (defaultProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) { + if ( + defaultProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP && + defaultProps.cols !== undefined + ) { nowrapGroups[DEFAULT_GROUP] = { items: [], leftSpace: defaultProps.cols, @@ -208,7 +304,11 @@ function useMemoStateContext(props) { groups.forEach((group) => { const resultProps = group.gridProperties?.(defaultProps) || {}; - if (resultProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) { + if ( + resultProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP && + resultProps.cols !== undefined && + group.id + ) { nowrapGroups[group.id] = { items: [], leftSpace: resultProps.cols, @@ -269,15 +369,15 @@ function useMemoStateContext(props) { [props.config, props.itemsStateAndParams], ); - const getItemsMeta = React.useCallback((pluginsRefs) => { + const getItemsMeta = React.useCallback((pluginsRefs) => { return pluginsRefs .map((ref) => { - if (!(ref && typeof ref.getMeta === 'function')) { + if (!(ref && hasGetMeta(ref))) { return undefined; } return ref.getMeta(); }) - .filter(Boolean); + .filter((item): item is Promise => item !== undefined); }, []); const resultLayout = React.useMemo(() => { @@ -291,21 +391,21 @@ function useMemoStateContext(props) { if (widgetId in adjusted || widgetId in nowrapAdjust) { original[widgetId] = item; // eslint-disable-next-line no-unused-vars - const {parent, ...adjustedItem} = adjusted[widgetId] || item; + const {parent: _parent2, ...adjustedItem} = adjusted[widgetId] || item; adjustedItem.w = item.w; adjustedItem.x = item.x; adjustedItem.y = item.y; if (item.parent) { - adjustedItem.parent = item.parent; + (adjustedItem as ConfigLayout).parent = item.parent; } if (widgetId in nowrapAdjust) { - adjustedItem.maxW = nowrapAdjust[widgetId]; + (adjustedItem as ConfigLayout & {maxW?: number}).maxW = nowrapAdjust[widgetId]; } - return adjustedItem; + return adjustedItem satisfies ConfigLayout; } else { if (widgetId in original) { delete original[widgetId]; @@ -315,8 +415,8 @@ function useMemoStateContext(props) { }); }, [props.layout, layoutUpdateCounter]); - const reloadItems = React.useCallback((pluginsRefs, data) => { - pluginsRefs.forEach((ref) => ref && ref.reload && ref.reload(data)); + const reloadItems = React.useCallback((pluginsRefs, data) => { + pluginsRefs.forEach((ref) => ref && hasReload(ref) && ref.reload(data)); }, []); const dragPropsContext = dndContext?.dragProps; @@ -338,36 +438,45 @@ function useMemoStateContext(props) { } }, [dragPropsContext, props.registerManager]); - const onDropDragOver = React.useCallback( + const onDropDragOver = React.useCallback( (_e, group, gridProps, groupLayout, sharedItem) => { if (temporaryLayout) { resetTemporaryLayout(); return false; } - let defaultLayout; + let dragItemType: string; + let defaultLayout: RegisterManagerPlugin['defaultLayout'] | {h: number; w: number}; if (sharedItem) { const {type, h, w} = sharedItem; + dragItemType = type; const _defaults = props.registerManager.getItem(type); defaultLayout = _defaults ? {..._defaults.defaultLayout, h, w} : {h, w}; } else if (dragOverPlugin) { + dragItemType = dragOverPlugin.type; defaultLayout = dragOverPlugin.defaultLayout; } else { return false; } let maxW = gridProps.cols; - const maxH = Math.min(gridProps.maxRows || Infinity, defaultLayout.maxH || Infinity); + const maxH = Math.min( + gridProps.maxRows || Infinity, + 'maxH' in defaultLayout && defaultLayout.maxH ? defaultLayout.maxH : Infinity, + ); - if (gridProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP) { + if (gridProps.compactType === COMPACT_TYPE_HORIZONTAL_NOWRAP && gridProps.cols) { maxW = groupLayout.reduce((memo, item) => memo - item.w, gridProps.cols); } if ( maxW === 0 || maxH === 0 || - maxW < defaultLayout.minW || - maxH < defaultLayout.minH + ('minW' in defaultLayout && + defaultLayout.minW && + maxW && + maxW < defaultLayout.minW) || + ('minH' in defaultLayout && defaultLayout.minH && maxH < defaultLayout.minH) ) { return false; } @@ -388,7 +497,7 @@ function useMemoStateContext(props) { ...sharedItem, ...itemLayout, parent: group, - type: sharedItem?.type || dragOverPlugin?.type, + type: dragItemType, }, sharedItem ?? null, ) === false @@ -409,7 +518,7 @@ function useMemoStateContext(props) { ); const onDropProp = props.onDrop; - const onDrop = React.useCallback( + const onDrop = React.useCallback( (newLayout, item) => { if (!dragPropsContext) { return; @@ -420,8 +529,8 @@ function useMemoStateContext(props) { dragProps: dragPropsContext, }); - onDropProp({ - newLayout: newLayout.reduce((memo, l) => { + onDropProp?.({ + newLayout: newLayout.reduce((memo, l) => { if (l.i !== item.i) { memo.push(pick(l, ITEM_PROPS)); } @@ -563,8 +672,8 @@ function useMemoStateContext(props) { return {controlsContextValue, dashkitContextValue}; } -export function withContext(Component) { - const WithContext = (props) => { +export function withContext(Component: React.ComponentType) { + const WithContext = (props: DashKitWithContextProps) => { const {dashkitContextValue, controlsContextValue} = useMemoStateContext(props); return ( @@ -578,7 +687,7 @@ export function withContext(Component) { WithContext.displayName = `withContext(${ Component.displayName || Component.name || 'Component' - }`; + })`; return WithContext; } diff --git a/src/utils/update-manager.ts b/src/utils/update-manager.ts index d7bb657..e52f566 100644 --- a/src/utils/update-manager.ts +++ b/src/utils/update-manager.ts @@ -37,7 +37,6 @@ import type { AddNewItemOptions, ReflowLayoutOptions, SetItemOptions, - WidgetLayout, } from '../typings'; import {getNewId} from './get-new-id'; @@ -667,7 +666,7 @@ export class UpdateManager { }; } - static updateLayout({layout, config}: {layout: WidgetLayout[]; config: Config}) { + static updateLayout({layout, config}: {layout: ConfigLayout[]; config: Config}) { return update(config, { layout: { $set: layout.map((item) => pick(item, ['i', 'h', 'w', 'x', 'y', 'parent'])), From eb9199accbf846a4f5de494c17222b1b0be8d0b3 Mon Sep 17 00:00:00 2001 From: ykornilov Date: Mon, 29 Dec 2025 13:39:29 +0300 Subject: [PATCH 7/7] chore: typings refactoring (#293) * refactor: use PluginWidgetProps type for adjustWidgetLayout * refactor: deduplicate types using DashKitProps references * refactor: extract type from item prop instead of passing separately * refactor: make width and height optional in PluginWidgetProps * refactor: consolidate grid layout types in common --------- Co-authored-by: Yury Kornilov --- src/components/DashKit/DashKit.tsx | 2 +- src/components/GridItem/GridItem.tsx | 17 ++--- src/components/GridLayout/GridLayout.tsx | 7 +- src/components/GridLayout/types.ts | 6 -- src/components/Item/Item.tsx | 2 +- src/components/Item/types.ts | 14 ++-- src/context/DashKitContext.ts | 13 ++-- src/hocs/prepareItem.tsx | 17 ++--- src/hocs/withContext.tsx | 89 +++++++++--------------- src/typings/common.ts | 23 +++++- src/typings/config.ts | 24 +------ src/typings/plugin.ts | 13 ++-- src/utils/register-manager.ts | 8 +-- 13 files changed, 87 insertions(+), 148 deletions(-) diff --git a/src/components/DashKit/DashKit.tsx b/src/components/DashKit/DashKit.tsx index 32ab3ad..bf80b6d 100644 --- a/src/components/DashKit/DashKit.tsx +++ b/src/components/DashKit/DashKit.tsx @@ -58,7 +58,7 @@ interface DashKitDefaultProps { groups?: DashKitGroup[]; }) => void; - onDrop?: (dropProps: ItemDropProps) => void; + onDrop: (dropProps: ItemDropProps) => void; onItemMountChange?: (item: ConfigItem, state: {isAsync: boolean; isMounted: boolean}) => void; onItemRender?: (item: ConfigItem) => void; diff --git a/src/components/GridItem/GridItem.tsx b/src/components/GridItem/GridItem.tsx index 30f4eff..1261603 100644 --- a/src/components/GridItem/GridItem.tsx +++ b/src/components/GridItem/GridItem.tsx @@ -3,8 +3,9 @@ import React from 'react'; import {FOCUSED_CLASS_NAME} from '../../constants'; import {DashKitContext} from '../../context'; import type {ConfigItem, ConfigLayout} from '../../shared'; -import type {PluginRef, ReactGridLayoutProps} from '../../typings'; +import type {PluginRef, PluginWidgetProps, ReactGridLayoutProps} from '../../typings'; import {cn} from '../../utils/cn'; +import type {DashKitProps} from '../DashKit'; import Item from '../Item/Item'; import OverlayControls from '../OverlayControls/OverlayControls'; @@ -46,11 +47,7 @@ class WindowFocusObserver { const windowFocusObserver = new WindowFocusObserver(); type GridItemProps = { - adjustWidgetLayout: (data: { - widgetId: string; - needSetDefault?: boolean; - adjustedWidgetLayout?: ConfigLayout; - }) => void; + adjustWidgetLayout: PluginWidgetProps['adjustWidgetLayout']; gridLayout?: ReactGridLayoutProps; id: string; item: ConfigItem; @@ -62,8 +59,8 @@ type GridItemProps = { forwardedPluginRef?: (pluginRef: PluginRef) => void; isPlaceholder?: boolean; - onItemMountChange?: (item: ConfigItem, meta: {isAsync: boolean; isMounted: boolean}) => void; - onItemRender?: (item: ConfigItem) => void; + onItemMountChange?: DashKitProps['onItemMountChange']; + onItemRender?: DashKitProps['onItemRender']; // from react-grid-layout: children?: React.ReactNode; @@ -76,8 +73,8 @@ type GridItemProps = { onMouseUp?: (e: React.MouseEvent) => void; onTouchEnd?: (e: React.TouchEvent) => void; onTouchStart?: (e: React.TouchEvent) => void; - onItemFocus?: (item: ConfigItem) => void; - onItemBlur?: (item: ConfigItem) => void; + onItemFocus?: DashKitProps['onItemFocus']; + onItemBlur?: DashKitProps['onItemBlur']; }; type GridItemState = { diff --git a/src/components/GridLayout/GridLayout.tsx b/src/components/GridLayout/GridLayout.tsx index 0bd7df2..5ae31ee 100644 --- a/src/components/GridLayout/GridLayout.tsx +++ b/src/components/GridLayout/GridLayout.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type {DragOverEvent} from 'react-grid-layout'; -import type {PluginRef, ReactGridLayoutProps} from 'src/typings'; +import type {PluginRef, PluginWidgetProps, ReactGridLayoutProps} from 'src/typings'; import { COMPACT_TYPE_HORIZONTAL_NOWRAP, @@ -18,7 +18,6 @@ import GridItem from '../GridItem/GridItem'; import {Layout} from './ReactGridLayout'; import type { - AdjustWidgetLayoutParams, CurrentDraggingElement, GridLayoutProps, GridLayoutState, @@ -93,11 +92,11 @@ export default class GridLayout extends React.PureComponent { + }) => { const {layout, memorizeOriginalLayout, revertToOriginalLayout} = this.context; if (needSetDefault) { diff --git a/src/components/GridLayout/types.ts b/src/components/GridLayout/types.ts index 14ec5ff..7e2fe4a 100644 --- a/src/components/GridLayout/types.ts +++ b/src/components/GridLayout/types.ts @@ -67,12 +67,6 @@ export type GroupCallbacks = { onDragTargetRestore: (group?: string) => void; }; -export type AdjustWidgetLayoutParams = { - widgetId: string; - needSetDefault?: boolean; - adjustedWidgetLayout?: ConfigLayout; -}; - export type LayoutAndPropsByGroup = { properties: Partial; layout: ConfigLayout[]; diff --git a/src/components/Item/Item.tsx b/src/components/Item/Item.tsx index 26474e7..352b6a2 100644 --- a/src/components/Item/Item.tsx +++ b/src/components/Item/Item.tsx @@ -13,7 +13,6 @@ const b = cn('dashkit-item'); const Item: React.FC = ({ registerManager, rendererProps, - type, isPlaceholder, forwardedPluginRef, onItemRender, @@ -27,6 +26,7 @@ const Item: React.FC = ({ const onItemMountChangeRef = React.useRef(onItemMountChange); itemRef.current = item; + const {type} = item; onItemRenderRef.current = onItemRender; onItemMountChangeRef.current = onItemMountChange; diff --git a/src/components/Item/types.ts b/src/components/Item/types.ts index dd733f8..42c5ed5 100644 --- a/src/components/Item/types.ts +++ b/src/components/Item/types.ts @@ -1,14 +1,9 @@ import type {ConfigItem, ItemParams} from '../../shared/types'; import type {PluginRef, PluginWidgetProps} from '../../typings'; import type {RegisterManager} from '../../utils/register-manager'; +import type {DashKitProps} from '../DashKit'; -export type RendererProps = Omit< - PluginWidgetProps, - 'onBeforeLoad' | 'width' | 'height' -> & { - width?: number; - height?: number; -}; +export type RendererProps = Omit, 'onBeforeLoad'>; export type ItemProps = { forwardedPluginRef?: (ref: PluginRef) => void; @@ -16,7 +11,6 @@ export type ItemProps = { item: ConfigItem; registerManager: RegisterManager; rendererProps: RendererProps; - type: string; - onItemRender?: (item: ConfigItem) => void; - onItemMountChange?: (item: ConfigItem, meta: {isAsync: boolean; isMounted: boolean}) => void; + onItemRender?: DashKitProps['onItemRender']; + onItemMountChange?: DashKitProps['onItemMountChange']; }; diff --git a/src/context/DashKitContext.ts b/src/context/DashKitContext.ts index 5a7c3a5..32b4842 100644 --- a/src/context/DashKitContext.ts +++ b/src/context/DashKitContext.ts @@ -8,6 +8,7 @@ import type { ConfigItem, ConfigLayout, DraggedOverItem, + GlobalParams, ItemDragProps, ItemParams, ItemState, @@ -16,18 +17,17 @@ import type { } from '../shared'; import type {ContextProps, PluginRef, ReactGridLayoutProps, SettingsProps} from '../typings'; -type DashkitPropsPassedToCtx = Pick< +export type DashkitPropsPassedToCtx = Pick< DashKitProps, | 'config' | 'groups' - | 'context' | 'noOverlay' | 'focusable' - | 'globalParams' | 'editMode' - | 'settings' | 'onItemMountChange' | 'onItemRender' + | 'onItemFocus' + | 'onItemBlur' | 'draggableHandleClassName' // default handlers bypass | 'onDragStart' @@ -43,9 +43,10 @@ export type TemporaryLayout = { dragProps: ItemDragProps; }; -export type DashKitCtxShape = Omit & { +export type DashKitCtxShape = DashkitPropsPassedToCtx & { context: ContextProps; settings: SettingsProps; + globalParams: GlobalParams; registerManager: RegisterManager; forwardedMetaRef: React.ForwardedRef; @@ -83,8 +84,6 @@ export type DashKitCtxShape = Omit {w?: number; h?: number} | false | undefined; - onItemBlur?: (item: ConfigItem) => void; - onItemFocus?: (item: ConfigItem) => void; outerDnDEnable: boolean; dragOverPlugin: null | RegisterManagerPlugin; }; diff --git a/src/hocs/prepareItem.tsx b/src/hocs/prepareItem.tsx index 3846cde..65dd690 100644 --- a/src/hocs/prepareItem.tsx +++ b/src/hocs/prepareItem.tsx @@ -2,13 +2,10 @@ import React from 'react'; import isEqual from 'lodash/isEqual'; +import type {DashKitProps} from '../components/DashKit'; import type {ItemProps, RendererProps} from '../components/Item/types'; import {DashKitContext} from '../context'; import type {ConfigItem, ConfigLayout} from '../shared'; -import type { - ItemStateAndParams, - ItemStateAndParamsChangeOptions, -} from '../shared/types/state-and-params'; import type {PluginRef, PluginWidgetProps, ReactGridLayoutProps} from '../typings'; type PrepareItemProps = { @@ -23,8 +20,8 @@ type PrepareItemProps = { transform?: string; isPlaceholder?: boolean; - onItemRender?: (item: ConfigItem) => void; - onItemMountChange?: (item: ConfigItem, meta: {isAsync: boolean; isMounted: boolean}) => void; + onItemRender?: DashKitProps['onItemRender']; + onItemMountChange?: DashKitProps['onItemMountChange']; forwardedPluginRef?: (ref: PluginRef) => void; }; @@ -50,9 +47,9 @@ export function prepareItem( ); } - _onStateAndParamsChange = ( - stateAndParams: ItemStateAndParams, - options?: ItemStateAndParamsChangeOptions, + _onStateAndParamsChange: PluginWidgetProps['onStateAndParamsChange'] = ( + stateAndParams, + options, ) => { this.context.onItemStateAndParamsChange(this.props.id, stateAndParams, options); }; @@ -101,14 +98,12 @@ export function prepareItem( const {item, isPlaceholder, forwardedPluginRef, onItemMountChange, onItemRender} = this.props; const {registerManager} = this.context; - const {type} = item; return ( void; - layout: ConfigLayout[]; - registerManager: RegisterManager; - defaultGlobalParams: GlobalParams; - globalParams: GlobalParams; - onItemEdit: (item: ConfigItem) => void; - context: ContextProps; - noOverlay: boolean; - focusable?: boolean; - settings: SettingsProps; - onItemMountChange?: (item: ConfigItem, state: {isAsync: boolean; isMounted: boolean}) => void; - onItemRender?: (item: ConfigItem) => void; - forwardedMetaRef: React.ForwardedRef; - draggableHandleClassName?: string; - onDrop?: (dropProps: ItemDropProps) => void; - overlayControls?: Record | null; - overlayMenuItems?: MenuItem[] | null; - getPreparedCopyItemOptions?: (options: PreparedCopyItemOptions) => PreparedCopyItemOptions; - onCopyFulfill?: (error: null | Error, data?: PreparedCopyItemOptions) => void; - editMode: boolean; - onItemFocus?: (item: ConfigItem) => void; - onItemBlur?: (item: ConfigItem) => void; - onDragStart?: ItemManipulationCallback; - onDrag?: ItemManipulationCallback; - onDragStop?: ItemManipulationCallback; - onResizeStart?: ItemManipulationCallback; - onResize?: ItemManipulationCallback; - onResizeStop?: ItemManipulationCallback; -}; +export type DashKitWithContextProps = DashkitPropsPassedToCtx & + Pick< + DashKitProps, + 'overlayControls' | 'overlayMenuItems' | 'getPreparedCopyItemOptions' | 'onCopyFulfill' + > & + Required< + Pick< + DashKitProps, + | 'itemsStateAndParams' + | 'defaultGlobalParams' + | 'globalParams' + | 'context' + | 'settings' + | 'onItemEdit' + | 'onChange' + | 'onDrop' + > + > & { + layout: ConfigLayout[]; + registerManager: RegisterManager; + forwardedMetaRef: React.ForwardedRef; + }; type OriginalLayouts = Record; diff --git a/src/typings/common.ts b/src/typings/common.ts index e49d2bb..be76010 100644 --- a/src/typings/common.ts +++ b/src/typings/common.ts @@ -4,12 +4,31 @@ import type {OverlayCustomControlItem} from '../components/OverlayControls/Overl import {MenuItems} from '../constants'; import {AdditionalWidgetLayout} from '../shared'; -export type GridLayoutSettings = ReactGridLayout.ReactGridLayoutProps & { +export type CompactType = ReactGridLayout.ReactGridLayoutProps['compactType'] | 'horizontal-nowrap'; + +export type ReactGridLayoutProps = Omit< + ReactGridLayout.ReactGridLayoutProps, + | 'children' + | 'compactType' + | 'innerRef' + | 'key' + | 'layout' + | 'onDragStart' + | 'onDragStop' + | 'onResizeStart' + | 'onResizeStop' + | 'draggableHandle' + | 'isDroppable' + | 'onDropDragOver' + | 'onDrop' + | 'draggableCancel' +> & { + compactType?: CompactType; noOverlay?: boolean; }; export interface Settings { - gridLayout?: GridLayoutSettings; + gridLayout?: ReactGridLayoutProps; theme?: string; isMobile?: boolean; // @deprecated as it's possibly mutable property use Dashkit overlayMenuItems property instead diff --git a/src/typings/config.ts b/src/typings/config.ts index 49dadab..9f606f4 100644 --- a/src/typings/config.ts +++ b/src/typings/config.ts @@ -2,7 +2,7 @@ import type {Layout} from 'react-grid-layout'; import type {Config, ConfigItem, ConfigLayout} from '../shared'; -import {GridLayoutSettings} from './common'; +import type {CompactType, ReactGridLayoutProps} from './common'; export interface AddConfigItem extends Omit { id?: null; @@ -22,8 +22,6 @@ export type GridReflowOptions = { compactType?: CompactType; }; -export type CompactType = ReactGridLayout.ReactGridLayoutProps['compactType'] | 'horizontal-nowrap'; - export type ReflowLayoutOptions = { defaultProps: GridReflowOptions; groups?: Record; @@ -43,26 +41,6 @@ export interface DashkitGroupRenderProps { context: any; } -export type ReactGridLayoutProps = Omit< - GridLayoutSettings, - | 'children' - | 'compactType' - | 'innerRef' - | 'key' - | 'layout' - | 'onDragStart' - | 'onDragStop' - | 'onResizeStart' - | 'onResizeStop' - | 'draggableHandle' - | 'isDroppable' - | 'onDropDragOver' - | 'onDrop' - | 'draggableCancel' -> & { - compactType?: CompactType; -}; - export interface DashKitGroup { id?: string; render?: ( diff --git a/src/typings/plugin.ts b/src/typings/plugin.ts index 9543519..1623dfd 100644 --- a/src/typings/plugin.ts +++ b/src/typings/plugin.ts @@ -1,7 +1,5 @@ import React from 'react'; -import ReactGridLayout from 'react-grid-layout'; - import type { ConfigItem, ItemState, @@ -11,8 +9,7 @@ import type { StringParams, } from '../shared'; -import type {ContextProps, SettingsProps, WidgetLayout} from './common'; -import type {CompactType} from './config'; +import type {ContextProps, ReactGridLayoutProps, SettingsProps, WidgetLayout} from './common'; export interface PluginWidgetProps { id: string; @@ -24,17 +21,15 @@ export interface PluginWidgetProps { options?: ItemStateAndParamsChangeOptions, ) => void; onBeforeLoad: () => () => void; - width: number; - height: number; + width?: number; + height?: number; data: ConfigItem['data']; defaults: ConfigItem['defaults']; namespace: ConfigItem['namespace']; settings: SettingsProps; context: ContextProps; layout: WidgetLayout[]; - gridLayout: Omit & { - compactType?: CompactType; - }; + gridLayout: ReactGridLayoutProps; adjustWidgetLayout: (data: { widgetId: string; needSetDefault?: boolean; diff --git a/src/utils/register-manager.ts b/src/utils/register-manager.ts index 6b762fc..9c23000 100644 --- a/src/utils/register-manager.ts +++ b/src/utils/register-manager.ts @@ -1,6 +1,4 @@ -import ReactGridLayout from 'react-grid-layout'; - -import type {CompactType, Plugin, PluginDefaultLayout, Settings} from '../typings'; +import type {Plugin, PluginDefaultLayout, ReactGridLayoutProps, Settings} from '../typings'; interface RegisterManagerDefaultLayout { x: number; @@ -29,9 +27,7 @@ export class RegisterManager { minW: 4, minH: 2, }; - private _gridLayout: Omit & { - compactType?: CompactType; - } = { + private _gridLayout: ReactGridLayoutProps = { rowHeight: 18, cols: 36, margin: [2, 2],