From 2b3e2cc5ff2ca1ff0fa689d5a06ebf43b5bce528 Mon Sep 17 00:00:00 2001 From: Slizhevsky Vladislav Date: Tue, 14 Jul 2026 11:35:56 +0200 Subject: [PATCH 1/3] [UIK-5574][drag-and-drop] rewrite component types to NS --- semcore/drag-and-drop/src/DragAndDrop.tsx | 78 ++++---- semcore/drag-and-drop/src/DragAndDrop.type.ts | 179 +++++++++++------- semcore/drag-and-drop/src/index.ts | 4 +- .../drag-and-drop/drag-and-drop-api.md | 6 +- 4 files changed, 152 insertions(+), 115 deletions(-) diff --git a/semcore/drag-and-drop/src/DragAndDrop.tsx b/semcore/drag-and-drop/src/DragAndDrop.tsx index 20c0da4f52..c7992ed763 100644 --- a/semcore/drag-and-drop/src/DragAndDrop.tsx +++ b/semcore/drag-and-drop/src/DragAndDrop.tsx @@ -1,4 +1,5 @@ import { Box, ScreenReaderOnly } from '@semcore/base-components'; +import type { Intergalactic } from '@semcore/core'; import { createComponent, sstyled, Component, Root } from '@semcore/core'; import canUseDOM from '@semcore/core/lib/utils/canUseDOM'; import type { WithI18nEnhanceProps } from '@semcore/core/lib/utils/enhances/i18nEnhance'; @@ -7,56 +8,31 @@ import uniqueIDEnhance from '@semcore/core/lib/utils/uniqueID'; import useEnhancedEffect from '@semcore/core/lib/utils/use/useEnhancedEffect'; import React from 'react'; -import type { DragAndDropComponent, DragAndDropProps, DropZoneProps, DragAndDropDefaultProps } from './DragAndDrop.type'; +import type { NSDragAndDrop } from './DragAndDrop.type'; import style from './style/drag-and-drop.shadow.css'; import type { LocalizedMessages } from './translations/__intergalactic-dynamic-locales'; import { localizedMessages } from './translations/__intergalactic-dynamic-locales'; -type AttachDetails = { - index: number; - children: React.ReactNode; - node: HTMLElement; - id: string; - draggingAllowed: boolean; - isDropZone?: boolean; - zoneName?: string; -}; - const noop: (...args: any[]) => any = () => { /* do nothing */ }; const DragAndDropContext = React.createContext<{ - attach: (details: AttachDetails) => void; + attach: (details: NSDragAndDrop.AttachDetails) => void; detach: (index: number) => void; }>({ attach: noop, detach: noop, }); -type State = { - items: Array | undefined>; - dragging: null | { - index: number; - initialItemsRects: Array<{ x: number; y: number; width: number; height: number } | undefined>; - placeholder: HTMLElement | null; - }; - dragOver: number | null; - hideHoverEffect: boolean; - a11yHint: string | null; - keyboardDraggingIndex: number | null; - animatedScaling: number | null; - reversedScaling: boolean; -}; - type A11yHintKeys = keyof LocalizedMessages['en']; class DragAndDropRoot extends Component< - DragAndDropProps, + Intergalactic.InternalTypings.InferComponentProps, typeof DragAndDropRoot.enhance, {}, WithI18nEnhanceProps, - State, - DragAndDropDefaultProps + NSDragAndDrop.State, + NSDragAndDrop.DefaultProps > { static displayName = 'DragAndDrop'; static enhance = [i18nEnhance(localizedMessages), uniqueIDEnhance()] as const; @@ -67,7 +43,7 @@ class DragAndDropRoot extends Component< static style = style; - state: State = { + state: NSDragAndDrop.State = { items: [], dragging: null, dragOver: null, @@ -126,7 +102,7 @@ class DragAndDropRoot extends Component< const yOffset = this.asProps.scrollableContainerRef?.current?.scrollTop ?? 0; const xOffset = this.asProps.scrollableContainerRef?.current?.scrollLeft ?? 0; - this.setState((prevState: State) => ({ + this.setState((prevState: NSDragAndDrop.State) => ({ dragging: { index, initialItemsRects: prevState.items.map((item) => { @@ -259,7 +235,11 @@ class DragAndDropRoot extends Component< } const fromNode = items[dragging.index]; - if (fromNode) { + if ( + fromNode && + fromNode.id !== undefined && + currentItem.id !== undefined + ) { onDnD({ fromId: fromNode.id, fromIndex: dragging.index, @@ -501,8 +481,8 @@ class DragAndDropRoot extends Component< } }; - attach = ({ index, children, node, id, draggingAllowed, zoneName, isDropZone }: AttachDetails) => { - this.setState((prevState: State) => { + attach = ({ index, children, node, id, draggingAllowed, zoneName, isDropZone }: NSDragAndDrop.AttachDetails) => { + this.setState((prevState: NSDragAndDrop.State) => { if (prevState.items[index]?.children === children && prevState.items[index]?.node === node) return prevState; const { items } = prevState; items[index] = { children, node, id, draggingAllowed, zoneName, isDropZone }; @@ -511,7 +491,7 @@ class DragAndDropRoot extends Component< }; detach = (index: number) => { - this.setState((prevState: State) => { + this.setState((prevState: NSDragAndDrop.State) => { if (!prevState.items[index]) return prevState; const { items } = prevState; items[index] = undefined; @@ -554,7 +534,7 @@ class DragAndDropRoot extends Component< document.removeEventListener('keydown', this.handlePageKeyDown, { capture: true }); } - componentDidUpdate(prevProps: DragAndDropProps) { + componentDidUpdate(prevProps: typeof this.asProps) { if (prevProps.customFocus !== this.asProps.customFocus) { const itemIndex = this.getCustomFocusItemIndex(this.asProps.customFocus); if (this.state.items[itemIndex!]) this.handleItemFocus(); @@ -585,7 +565,18 @@ class DragAndDropRoot extends Component< } } -function Draggable(props: any) { +function Draggable( + props: Intergalactic.InternalTypings.InferChildComponentProps< + NSDragAndDrop.Draggable.Component, + typeof DragAndDropRoot, + 'Draggable' + > & { + // Passed from DropZone. + noDrag?: boolean; + // Passed from DropZone. + isDropZone?: boolean; + }, +) { const SDraggable = Root; const ref = React.useRef(); const { attach, detach } = React.useContext(DragAndDropContext); @@ -601,9 +592,10 @@ function Draggable(props: any) { isDropZone = false, uid, isCustomFocus = false, - keyboardFocused, } = props; + const resolvedChildren = React.useMemo( + // @ts-expect-error () => (typeof children === 'function' ? children(props) : children), [children, props], ); @@ -629,7 +621,7 @@ function Draggable(props: any) { placement={placement} role='group' aria-describedby={`describe-draggable-${uid}`} - use:keyboardFocused={isCustomFocus ? false : keyboardFocused} + use:keyboardFocused={isCustomFocus || false} tabIndex={0} > @@ -698,7 +690,9 @@ const findNextRectangleIndex = < return rectangles.indexOf(candidate!); }; -function DropZone(props: DropZoneProps) { +function DropZone( + props: Intergalactic.InternalTypings.InferComponentProps, +) { const SDropZone = Root; const { styles } = props; @@ -713,7 +707,7 @@ function DropZone(props: DropZoneProps) { * {@link https://developer.semrush.com/intergalactic/components/drag-and-drop/drag-and-drop-api/|API} | {@link https://developer.semrush.com/intergalactic/components/drag-and-drop/drag-and-drop-code/|Examples} */ const DragAndDrop = createComponent< - DragAndDropComponent, + NSDragAndDrop.Component, typeof DragAndDropRoot >(DragAndDropRoot, { Draggable, diff --git a/semcore/drag-and-drop/src/DragAndDrop.type.ts b/semcore/drag-and-drop/src/DragAndDrop.type.ts index 148c82d10b..7ea9a7fa7f 100644 --- a/semcore/drag-and-drop/src/DragAndDrop.type.ts +++ b/semcore/drag-and-drop/src/DragAndDrop.type.ts @@ -3,79 +3,122 @@ import type { PropGetterFn, Intergalactic } from '@semcore/core'; import type { LocalizedMessages } from './translations/__intergalactic-dynamic-locales'; -/** - * DragAndDrop and Draggable containers must have an accessible names (aria-group-name). - */ -type DNDAriaProps = Intergalactic.RequireAtLeastOne<{ - 'aria-label'?: string; - 'aria-labelledby'?: string; - 'title'?: string; -}>; - -export type DragAndDropProps = BoxProps & { - /** - * Controlled drag and drop handler - */ - onDnD: (dndData: { fromIndex: number; fromId: string; toIndex: number; toId: string }) => void; - /** - * Index of id that indicates item that is currently under the user focus in case of real browser focus cannot be used. - * When provided, drag and drop listens to whole page keyboard events. Doesn't provide `onCustomFocusChange` callback. - */ - customFocus?: number | string; - /** Specifies the locale for i18n support */ - locale?: string; +declare namespace NSDragAndDrop { + type Props = BoxProps & + NSDragAndDrop.AriaProps & { + /** + * Controlled drag and drop handler + */ + onDnD: (dndData: { fromIndex: number; fromId: string; toIndex: number; toId: string }) => void; + /** + * Index of id that indicates item that is currently under the user focus in case of real browser focus cannot be used. + * When provided, drag and drop listens to whole page keyboard events. Doesn't provide `onCustomFocusChange` callback. + */ + customFocus?: number | string; + /** Specifies the locale for i18n support */ + locale?: string; + /** + * Ref to a scrollable container, if exists + */ + scrollableContainerRef?: React.MutableRefObject; + }; + type DefaultProps = { + i18n: LocalizedMessages; + locale: 'en'; + }; /** - * Ref to a scrollable container, if exists + * DragAndDrop and Draggable containers must have an accessible names (aria-group-name). */ - scrollableContainerRef?: React.MutableRefObject; -}; + type AriaProps = Intergalactic.RequireAtLeastOne<{ + 'aria-label'?: string; + 'aria-labelledby'?: string; + 'title'?: string; + }>; + type State = { + items: Array | undefined>; + dragging: null | { + index: number; + initialItemsRects: Array<{ x: number; y: number; width: number; height: number } | undefined>; + placeholder: HTMLElement | null; + }; + dragOver: number | null; + hideHoverEffect: boolean; + a11yHint: string | null; + keyboardDraggingIndex: number | null; + animatedScaling: number | null; + reversedScaling: boolean; + }; + type Ctx = { + getDraggableProps: PropGetterFn; + getDroppableProps: PropGetterFn; + }; + type AttachDetails = { + index: number; + children: React.ReactNode; + node: HTMLElement; + id?: string; + draggingAllowed: boolean; + isDropZone?: boolean; + zoneName?: string; + }; -export type DragAndDropDefaultProps = { - i18n: LocalizedMessages; - locale: 'en'; -}; + namespace Draggable { + type Props = BoxProps & + NSDragAndDrop.AriaProps & { + /** Placement of visual drag-and-drop marker + * @default right + * */ + placement?: 'top' | 'right' | 'bottom' | 'left' | false; + /** Disabled DropZone abilities of component + * @default false + * */ + noDrop?: boolean; + /** + * Used as `fromId` or `toId` in `onDnD` handler. + */ + id?: string; + /** + * Used for add zoneName in a11y hints + */ + zoneName?: string; + /** + * Flag for disable keyboardFocused style form DnD.Draggable element + */ + isCustomFocus?: boolean; + }; -export type DraggableProps = BoxProps & { - /** Placement of visual drag-and-drop marker - * @default right - * */ - placement?: 'top' | 'right' | 'bottom' | 'left' | false; - /** Disabled DropZone abilities of component - * @default false - * */ - noDrop?: boolean; - /** - * Used as `fromId` or `toId` in `onDnD` handler. - */ - id?: string; - /** - * Used for add zoneName in a11y hints - */ - zoneName?: string; - /** - * Flag for disable keyboardFocused style form DnD.Draggable element - */ - isCustomFocus?: boolean; -}; + type Component = Intergalactic.Component<'div', Props>; + } -export type DragAndDropContext = { - getDraggableProps: PropGetterFn; - getDroppableProps: PropGetterFn; -}; + namespace DropZone { + type Props = BoxProps & + NSDragAndDrop.AriaProps & { + /** + * Used for add zoneName in a11y hints + */ + zoneName?: string; + }; -export type DropZoneProps = BoxProps & - DNDAriaProps & { - /** - * Used for add zoneName in a11y hints - */ - zoneName?: string; + type Component = Intergalactic.Component; + } + + type Component = Intergalactic.Component<'div', Props, Ctx> & { + Draggable: Draggable.Component; + DropZone: DropZone.Component; }; +} + +/** @deprecated It will be removed in v18. */ +export type DragAndDropProps = NSDragAndDrop.Props; +/** @deprecated It will be removed in v18. */ +export type DragAndDropDefaultProps = NSDragAndDrop.DefaultProps; +/** @deprecated It will be removed in v18. */ +export type DraggableProps = NSDragAndDrop.Draggable.Props; +/** @deprecated It will be removed in v18. */ +export type DragAndDropContext = NSDragAndDrop.Ctx; +/** @deprecated It will be removed in v18. */ +export type DropZoneProps = NSDragAndDrop.DropZone.Props; +/** @deprecated It will be removed in v18. */ +export type DragAndDropComponent = NSDragAndDrop.Component; -export type DragAndDropComponent = Intergalactic.Component< - 'div', - DragAndDropProps & DNDAriaProps, - DragAndDropContext -> & { - Draggable: Intergalactic.Component<'div', DraggableProps & DNDAriaProps>; - DropZone: Intergalactic.Component; -}; +export type { NSDragAndDrop }; diff --git a/semcore/drag-and-drop/src/index.ts b/semcore/drag-and-drop/src/index.ts index 4eead7089f..cac94abadd 100644 --- a/semcore/drag-and-drop/src/index.ts +++ b/semcore/drag-and-drop/src/index.ts @@ -1,5 +1,5 @@ -import type { DragAndDropProps, DropZoneProps } from './DragAndDrop.type'; +import type { DragAndDropProps, DropZoneProps, NSDragAndDrop } from './DragAndDrop.type'; export { default } from './DragAndDrop'; -export type { DragAndDropProps, DropZoneProps }; +export type { DragAndDropProps, DropZoneProps, NSDragAndDrop }; diff --git a/website/docs/components/drag-and-drop/drag-and-drop-api.md b/website/docs/components/drag-and-drop/drag-and-drop-api.md index bbcce3a382..a3e4069e8f 100644 --- a/website/docs/components/drag-and-drop/drag-and-drop-api.md +++ b/website/docs/components/drag-and-drop/drag-and-drop-api.md @@ -13,7 +13,7 @@ import DnD from '@semcore/ui/drag-and-drop'; ; ``` - + ## DnD.Draggable @@ -26,7 +26,7 @@ import DnD from '@semcore/ui/drag-and-drop'; ``` - + ## DnD.DropZone @@ -39,6 +39,6 @@ import DnD from '@semcore/ui/drag-and-drop'; ``` - + From 118f1b0dd3aeca3c4a827f5406dbe3f20e60d641 Mon Sep 17 00:00:00 2001 From: Slizhevsky Vladislav Date: Tue, 14 Jul 2026 17:19:08 +0200 Subject: [PATCH 2/3] [UIK-5574][drag-and-drop] rewrite component types to NS --- semcore/drag-and-drop/src/DragAndDrop.tsx | 6 +----- semcore/drag-and-drop/src/DragAndDrop.type.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/semcore/drag-and-drop/src/DragAndDrop.tsx b/semcore/drag-and-drop/src/DragAndDrop.tsx index c7992ed763..ad2b16ff90 100644 --- a/semcore/drag-and-drop/src/DragAndDrop.tsx +++ b/semcore/drag-and-drop/src/DragAndDrop.tsx @@ -235,11 +235,7 @@ class DragAndDropRoot extends Component< } const fromNode = items[dragging.index]; - if ( - fromNode && - fromNode.id !== undefined && - currentItem.id !== undefined - ) { + if (fromNode) { onDnD({ fromId: fromNode.id, fromIndex: dragging.index, diff --git a/semcore/drag-and-drop/src/DragAndDrop.type.ts b/semcore/drag-and-drop/src/DragAndDrop.type.ts index 7ea9a7fa7f..e2ecf78bd4 100644 --- a/semcore/drag-and-drop/src/DragAndDrop.type.ts +++ b/semcore/drag-and-drop/src/DragAndDrop.type.ts @@ -9,7 +9,7 @@ declare namespace NSDragAndDrop { /** * Controlled drag and drop handler */ - onDnD: (dndData: { fromIndex: number; fromId: string; toIndex: number; toId: string }) => void; + onDnD: (dndData: { fromIndex: number; fromId: NSDragAndDrop.AttachDetails['id']; toIndex: number; toId: NSDragAndDrop.AttachDetails['id'] }) => void; /** * Index of id that indicates item that is currently under the user focus in case of real browser focus cannot be used. * When provided, drag and drop listens to whole page keyboard events. Doesn't provide `onCustomFocusChange` callback. From 150f66bae9d21bbaae5681e073271a0f2c56c58d Mon Sep 17 00:00:00 2001 From: Slizhevsky Vladislav Date: Wed, 15 Jul 2026 11:52:54 +0200 Subject: [PATCH 3/3] [UIK-5574][drag-and-drop] rewrite component types to NS --- semcore/drag-and-drop/src/DragAndDrop.tsx | 4 +--- semcore/drag-and-drop/src/DragAndDrop.type.ts | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/semcore/drag-and-drop/src/DragAndDrop.tsx b/semcore/drag-and-drop/src/DragAndDrop.tsx index ad2b16ff90..6343617e61 100644 --- a/semcore/drag-and-drop/src/DragAndDrop.tsx +++ b/semcore/drag-and-drop/src/DragAndDrop.tsx @@ -567,8 +567,6 @@ function Draggable( typeof DragAndDropRoot, 'Draggable' > & { - // Passed from DropZone. - noDrag?: boolean; // Passed from DropZone. isDropZone?: boolean; }, @@ -617,7 +615,7 @@ function Draggable( placement={placement} role='group' aria-describedby={`describe-draggable-${uid}`} - use:keyboardFocused={isCustomFocus || false} + use:keyboardFocused={isCustomFocus || undefined} tabIndex={0} > diff --git a/semcore/drag-and-drop/src/DragAndDrop.type.ts b/semcore/drag-and-drop/src/DragAndDrop.type.ts index e2ecf78bd4..94f9762095 100644 --- a/semcore/drag-and-drop/src/DragAndDrop.type.ts +++ b/semcore/drag-and-drop/src/DragAndDrop.type.ts @@ -85,6 +85,10 @@ declare namespace NSDragAndDrop { * Flag for disable keyboardFocused style form DnD.Draggable element */ isCustomFocus?: boolean; + /** + * Disable drag ability + */ + noDrag?: boolean; }; type Component = Intergalactic.Component<'div', Props>;