or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

array-utilities.mdindex.mdsortable-container.mdsortable-element.mdsortable-handle.md

sortable-handle.mddocs/

0

# Sortable Handle

1

2

Higher-order component that creates designated drag areas within sortable elements. Enables fine-grained control over drag initiation by restricting sorting to specific UI elements.

3

4

## Capabilities

5

6

### SortableHandle HOC

7

8

Creates a drag handle component that can initiate sorting when used with `useDragHandle={true}` on the SortableContainer.

9

10

```typescript { .api }

11

/**

12

* Higher-order component that creates a drag handle for sortable elements

13

* @param wrappedComponent - The React component to enhance as a drag handle

14

* @param config - Optional configuration object

15

* @returns Enhanced React component that serves as a drag handle

16

*/

17

function SortableHandle<P>(

18

wrappedComponent: WrappedComponent<P>,

19

config?: Config

20

): React.ComponentClass<P>;

21

22

interface Config {

23

withRef: boolean;

24

}

25

```

26

27

**Usage Examples:**

28

29

```typescript

30

import React from 'react';

31

import { SortableHandle, SortableElement, SortableContainer } from 'react-sortable-hoc';

32

33

// Simple drag handle

34

const DragHandle = SortableHandle(() => (

35

<span className="drag-handle">≡</span>

36

));

37

38

// Icon-based drag handle

39

const IconDragHandle = SortableHandle(() => (

40

<div className="drag-handle-icon">

41

<svg width="12" height="12" viewBox="0 0 12 12">

42

<circle cx="2" cy="2" r="1" />

43

<circle cx="6" cy="2" r="1" />

44

<circle cx="10" cy="2" r="1" />

45

<circle cx="2" cy="6" r="1" />

46

<circle cx="6" cy="6" r="1" />

47

<circle cx="10" cy="6" r="1" />

48

<circle cx="2" cy="10" r="1" />

49

<circle cx="6" cy="10" r="1" />

50

<circle cx="10" cy="10" r="1" />

51

</svg>

52

</div>

53

));

54

55

// Usage in sortable element

56

const SortableItem = SortableElement(({ value }) => (

57

<li className="sortable-item">

58

<DragHandle />

59

<span>{value}</span>

60

</li>

61

));

62

```

63

64

### Handle Integration

65

66

#### Container Configuration

67

68

To use drag handles, set `useDragHandle={true}` on the SortableContainer:

69

70

```typescript

71

const SortableList = SortableContainer(({ items }) => (

72

<ul>

73

{items.map((value, index) => (

74

<SortableItem key={`item-${value}`} index={index} value={value} />

75

))}

76

</ul>

77

));

78

79

// Enable drag handles

80

<SortableList

81

items={items}

82

onSortEnd={handleSortEnd}

83

useDragHandle={true} // Required for handles to work

84

/>

85

```

86

87

#### Element Structure

88

89

Place the drag handle component anywhere within your sortable element:

90

91

```typescript

92

const SortableCard = SortableElement(({ title, content, onEdit }) => (

93

<div className="card">

94

<div className="card-header">

95

<DragHandle /> {/* Handle at the top */}

96

<h3>{title}</h3>

97

</div>

98

<div className="card-body">

99

<p>{content}</p>

100

</div>

101

<div className="card-footer">

102

<button onClick={onEdit}>Edit</button>

103

</div>

104

</div>

105

));

106

```

107

108

### Handle Styling

109

110

#### CSS Styling

111

112

Style drag handles to provide clear visual feedback:

113

114

```css

115

.drag-handle {

116

display: inline-block;

117

width: 20px;

118

height: 20px;

119

cursor: grab;

120

color: #999;

121

font-size: 16px;

122

line-height: 20px;

123

text-align: center;

124

user-select: none;

125

}

126

127

.drag-handle:hover {

128

color: #666;

129

}

130

131

.drag-handle:active {

132

cursor: grabbing;

133

}

134

135

.card-header .drag-handle {

136

float: right;

137

margin-left: 10px;

138

}

139

```

140

141

#### Interactive Handle States

142

143

```typescript

144

const InteractiveDragHandle = SortableHandle(({ isActive }) => (

145

<div className={`drag-handle ${isActive ? 'active' : ''}`}>

146

<span>⋮⋮</span>

147

</div>

148

));

149

150

// Usage with state

151

const [activeHandle, setActiveHandle] = useState(null);

152

153

const SortableItem = SortableElement(({ value, index }) => (

154

<li

155

onMouseEnter={() => setActiveHandle(index)}

156

onMouseLeave={() => setActiveHandle(null)}

157

>

158

<InteractiveDragHandle isActive={activeHandle === index} />

159

<span>{value}</span>

160

</li>

161

));

162

```

163

164

### Advanced Handle Patterns

165

166

#### Multiple Handles

167

168

You can have multiple handles within the same sortable element:

169

170

```typescript

171

const TopHandle = SortableHandle(() => <div className="top-handle">↕</div>);

172

const SideHandle = SortableHandle(() => <div className="side-handle">↔</div>);

173

174

const MultiHandleItem = SortableElement(({ value }) => (

175

<div className="multi-handle-item">

176

<TopHandle />

177

<div className="content">

178

<SideHandle />

179

<span>{value}</span>

180

</div>

181

</div>

182

));

183

```

184

185

#### Conditional Handles

186

187

Show handles only when appropriate:

188

189

```typescript

190

const ConditionalHandle = SortableHandle(({ show }) =>

191

show ? <span className="drag-handle">≡</span> : null

192

);

193

194

const SortableItem = SortableElement(({ value, editable, userCanSort }) => (

195

<li className="item">

196

<ConditionalHandle show={editable && userCanSort} />

197

<span>{value}</span>

198

</li>

199

));

200

```

201

202

#### Custom Handle Content

203

204

Create handles with rich content:

205

206

```typescript

207

const CustomDragHandle = SortableHandle(({ label, icon }) => (

208

<div className="custom-drag-handle">

209

{icon && <img src={icon} alt="drag" className="handle-icon" />}

210

<span className="handle-label">{label}</span>

211

<div className="handle-grip">

212

<div className="grip-dot"></div>

213

<div className="grip-dot"></div>

214

<div className="grip-dot"></div>

215

</div>

216

</div>

217

));

218

219

// Usage

220

const SortableTask = SortableElement(({ task }) => (

221

<div className="task-item">

222

<CustomDragHandle

223

label="Drag to reorder"

224

icon="/icons/drag.svg"

225

/>

226

<div className="task-content">

227

<h4>{task.title}</h4>

228

<p>{task.description}</p>

229

</div>

230

</div>

231

));

232

```

233

234

### Handle Events

235

236

#### Event Handling

237

238

Handles can include their own event handlers:

239

240

```typescript

241

const InteractiveHandle = SortableHandle(({ onHandleClick, onHandleHover }) => (

242

<div

243

className="interactive-handle"

244

onClick={onHandleClick}

245

onMouseEnter={onHandleHover}

246

>

247

<span>≡</span>

248

</div>

249

));

250

251

const SortableItem = SortableElement(({ value, onItemAction }) => (

252

<li>

253

<InteractiveHandle

254

onHandleClick={() => onItemAction('handle-clicked')}

255

onHandleHover={() => onItemAction('handle-hovered')}

256

/>

257

<span>{value}</span>

258

</li>

259

));

260

```

261

262

#### Preventing Event Bubbling

263

264

Sometimes you need to prevent handle events from affecting the parent:

265

266

```typescript

267

const SafeHandle = SortableHandle(() => (

268

<div

269

className="safe-handle"

270

onDoubleClick={(e) => {

271

e.stopPropagation(); // Prevent double-click from bubbling

272

console.log('Handle double-clicked');

273

}}

274

>

275

276

</div>

277

));

278

```

279

280

### Handle Accessibility

281

282

#### Keyboard Support

283

284

Drag handles work with keyboard navigation when the sortable element is focusable:

285

286

```typescript

287

const AccessibleSortableItem = SortableElement(({ value, index }) => (

288

<li

289

tabIndex={0} // Make focusable for keyboard navigation

290

role="listitem"

291

aria-label={`Item ${index + 1}: ${value}`}

292

>

293

<DragHandle />

294

<span>{value}</span>

295

</li>

296

));

297

```

298

299

#### Screen Reader Support

300

301

Add appropriate ARIA labels for screen readers:

302

303

```typescript

304

const AccessibleDragHandle = SortableHandle(() => (

305

<div

306

className="drag-handle"

307

role="button"

308

aria-label="Drag to reorder"

309

tabIndex={-1} // Handle shouldn't be directly focusable

310

>

311

312

</div>

313

));

314

```

315

316

### withRef Configuration

317

318

```typescript { .api }

319

interface Config {

320

/** Enable access to wrapped component instance */

321

withRef: boolean;

322

}

323

```

324

325

**withRef Example:**

326

327

```typescript

328

const DragHandle = SortableHandle(HandleComponent, { withRef: true });

329

330

// Access wrapped instance

331

const handleRef = useRef();

332

const wrappedInstance = handleRef.current?.getWrappedInstance();

333

334

<DragHandle ref={handleRef} />

335

```

336

337

### isSortableHandle Utility

338

339

Utility function to check if a DOM element is a sortable handle.

340

341

```typescript { .api }

342

/**

343

* Check if a DOM element is a sortable handle

344

* @param node - The DOM element to check

345

* @returns True if the element is a sortable handle

346

*/

347

function isSortableHandle(node: Element): boolean;

348

```

349

350

**Usage Examples:**

351

352

```typescript

353

import { isSortableHandle } from 'react-sortable-hoc';

354

355

// Check if an element is a sortable handle

356

const handleClick = (event) => {

357

if (isSortableHandle(event.target)) {

358

console.log('Clicked on a sortable handle');

359

} else {

360

console.log('Clicked outside handle area');

361

}

362

};

363

364

// Use in event handlers

365

const SortableItem = SortableElement(({ value }) => (

366

<div onClick={handleClick}>

367

<DragHandle />

368

<span>{value}</span>

369

</div>

370

));

371

372

// Use for conditional logic

373

const isHandleElement = isSortableHandle(document.querySelector('.drag-handle'));

374

```

375

376

### Integration Examples

377

378

#### Complete Example

379

380

```typescript

381

import React, { useState } from 'react';

382

import {

383

SortableContainer,

384

SortableElement,

385

SortableHandle

386

} from 'react-sortable-hoc';

387

import { arrayMove } from 'array-move';

388

389

const DragHandle = SortableHandle(() => (

390

<div className="drag-handle" title="Drag to reorder">

391

⋮⋮

392

</div>

393

));

394

395

const SortableItem = SortableElement(({ value, onEdit, onDelete }) => (

396

<div className="sortable-item">

397

<DragHandle />

398

<div className="item-content">

399

<span>{value}</span>

400

</div>

401

<div className="item-actions">

402

<button onClick={onEdit}>Edit</button>

403

<button onClick={onDelete}>Delete</button>

404

</div>

405

</div>

406

));

407

408

const SortableList = SortableContainer(({ items, onEdit, onDelete }) => (

409

<div className="sortable-list">

410

{items.map((value, index) => (

411

<SortableItem

412

key={`item-${index}`}

413

index={index}

414

value={value}

415

onEdit={() => onEdit(index)}

416

onDelete={() => onDelete(index)}

417

/>

418

))}

419

</div>

420

));

421

422

const App = () => {

423

const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);

424

425

const onSortEnd = ({ oldIndex, newIndex }) => {

426

setItems(arrayMove(items, oldIndex, newIndex));

427

};

428

429

return (

430

<SortableList

431

items={items}

432

onSortEnd={onSortEnd}

433

useDragHandle={true} // Enable drag handles

434

onEdit={(index) => console.log('Edit item', index)}

435

onDelete={(index) => console.log('Delete item', index)}

436

/>

437

);

438

};

439

```