You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by en...@apache.org on 2026/07/10 09:24:33 UTC

(superset) branch master updated: fix(CollectionControl): a11y drag handle and vertical axis lock (#39863)

This is an automated email from the ASF dual-hosted git repository.

EnxDev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 1631d52b650 fix(CollectionControl): a11y drag handle and vertical axis lock (#39863)
1631d52b650 is described below

commit 1631d52b65045fb7898e6468a6fe40af92d3837b
Author: Mehmet Salih Yavuz <sa...@proton.me>
AuthorDate: Fri Jul 10 12:24:25 2026 +0300

    fix(CollectionControl): a11y drag handle and vertical axis lock (#39863)
    
    Co-authored-by: Claude Opus 4.7 (1M context) <no...@anthropic.com>
    Co-authored-by: Evan Rusackas <ev...@preset.io>
    Co-authored-by: Joe Li <jo...@preset.io>
---
 superset-frontend/package-lock.json                | 15 ++++++++++
 superset-frontend/package.json                     |  1 +
 .../CollectionControl/CollectionControl.test.tsx   |  8 +++++
 .../controls/CollectionControl/index.tsx           | 35 +++++++++++++++++-----
 4 files changed, 52 insertions(+), 7 deletions(-)

diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json
index 8ffcb178c7c..91dc079a63b 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -24,6 +24,7 @@
         "@deck.gl/mesh-layers": "~9.2.5",
         "@deck.gl/react": "~9.2.5",
         "@dnd-kit/core": "^6.3.1",
+        "@dnd-kit/modifiers": "^9.0.0",
         "@dnd-kit/sortable": "^10.0.0",
         "@dnd-kit/utilities": "^3.2.2",
         "@emotion/cache": "^11.4.0",
@@ -3037,6 +3038,20 @@
         "react-dom": ">=16.8.0"
       }
     },
+    "node_modules/@dnd-kit/modifiers": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz",
+      "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==",
+      "license": "MIT",
+      "dependencies": {
+        "@dnd-kit/utilities": "^3.2.2",
+        "tslib": "^2.0.0"
+      },
+      "peerDependencies": {
+        "@dnd-kit/core": "^6.3.0",
+        "react": ">=16.8.0"
+      }
+    },
     "node_modules/@dnd-kit/sortable": {
       "version": "10.0.0",
       "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index b3bec530839..8167496c282 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -107,6 +107,7 @@
     "@deck.gl/mesh-layers": "~9.2.5",
     "@deck.gl/react": "~9.2.5",
     "@dnd-kit/core": "^6.3.1",
+    "@dnd-kit/modifiers": "^9.0.0",
     "@dnd-kit/sortable": "^10.0.0",
     "@dnd-kit/utilities": "^3.2.2",
     "@emotion/cache": "^11.4.0",
diff --git a/superset-frontend/src/explore/components/controls/CollectionControl/CollectionControl.test.tsx b/superset-frontend/src/explore/components/controls/CollectionControl/CollectionControl.test.tsx
index 1bd518e9511..09cca7d7f83 100644
--- a/superset-frontend/src/explore/components/controls/CollectionControl/CollectionControl.test.tsx
+++ b/superset-frontend/src/explore/components/controls/CollectionControl/CollectionControl.test.tsx
@@ -134,6 +134,14 @@ test('Should have SortableDragger icon', async () => {
   expect(await screen.findByLabelText('Drag to reorder')).toBeVisible();
 });
 
+test('Drag activator exposes aria-label="Drag to reorder"', async () => {
+  const props = createProps();
+  render(<CollectionControl {...props} />);
+  const dragActivator = await screen.findByLabelText('Drag to reorder');
+  expect(dragActivator).toHaveAttribute('aria-label', 'Drag to reorder');
+  expect(dragActivator.tagName).toBe('BUTTON');
+});
+
 test('Should call Control component', async () => {
   const props = createProps();
   render(<CollectionControl {...props} />);
diff --git a/superset-frontend/src/explore/components/controls/CollectionControl/index.tsx b/superset-frontend/src/explore/components/controls/CollectionControl/index.tsx
index 1a0393577ad..97ad6976d46 100644
--- a/superset-frontend/src/explore/components/controls/CollectionControl/index.tsx
+++ b/superset-frontend/src/explore/components/controls/CollectionControl/index.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 import React, { useCallback, useMemo, useRef } from 'react';
-import { IconTooltip, List } from '@superset-ui/core/components';
+import { Button, IconTooltip, List } from '@superset-ui/core/components';
 import { nanoid } from 'nanoid';
 import { t } from '@apache-superset/core/translation';
 import { useTheme, type SupersetTheme } from '@apache-superset/core/theme';
@@ -27,14 +27,17 @@ import {
   useSensor,
   useSensors,
   PointerSensor,
+  KeyboardSensor,
   type DragEndEvent,
 } from '@dnd-kit/core';
 import {
   SortableContext,
   verticalListSortingStrategy,
   useSortable,
+  sortableKeyboardCoordinates,
   arrayMove,
 } from '@dnd-kit/sortable';
+import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
 import { CSS } from '@dnd-kit/utilities';
 import { Icons } from '@superset-ui/core/components/Icons';
 import {
@@ -68,8 +71,7 @@ export interface CollectionControlProps {
 function DragHandle() {
   return (
     <Icons.MenuOutlined
-      role="img"
-      aria-label={t('Drag to reorder')}
+      aria-hidden
       className="text-primary"
       style={{ cursor: 'ns-resize' }}
     />
@@ -93,8 +95,14 @@ function SortableItem({
   onChangeItem,
   onRemoveItem,
 }: SortableItemProps) {
-  const { attributes, listeners, setNodeRef, transform, transition } =
-    useSortable({ id });
+  const {
+    attributes,
+    listeners,
+    setNodeRef,
+    setActivatorNodeRef,
+    transform,
+    transition,
+  } = useSortable({ id });
   const style = {
     transform: CSS.Transform.toString(transform),
     transition: transition ?? undefined,
@@ -116,8 +124,17 @@ function SortableItem({
         paddingInline: theme.sizeUnit * 6,
       })}
     >
-      <span {...attributes} {...listeners}>
-        <DragHandle />
+      <span ref={setActivatorNodeRef} css={{ display: 'inline-flex' }}>
+        <Button
+          type="text"
+          aria-label={t('Drag to reorder')}
+          icon={<DragHandle />}
+          css={{
+            cursor: 'ns-resize',
+          }}
+          {...attributes}
+          {...listeners}
+        />
       </span>
       <div
         css={(theme: SupersetTheme) => ({
@@ -183,6 +200,9 @@ function CollectionControl({
     useSensor(PointerSensor, {
       activationConstraint: { distance: 5 },
     }),
+    useSensor(KeyboardSensor, {
+      coordinateGetter: sortableKeyboardCoordinates,
+    }),
   );
 
   // Two items can collide when keyAccessor returns falsy and the index
@@ -284,6 +304,7 @@ function CollectionControl({
       <DndContext
         sensors={sensors}
         collisionDetection={closestCenter}
+        modifiers={[restrictToVerticalAxis]}
         onDragEnd={handleDragEnd}
       >
         <SortableContext items={itemIds} strategy={verticalListSortingStrategy}>