or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

form-validation.mdhttp-client.mdindex.mdsearch-data.mdstorage-state.mdui-interactions.mdvisual-effects.md

ui-interactions.mddocs/

0

# UI Interactions

1

2

User interface enhancements including drag-and-drop sorting and focus management for improved accessibility and user experience.

3

4

## Capabilities

5

6

### useSortable

7

8

Drag-and-drop sorting functionality using SortableJS with reactive list updates.

9

10

```typescript { .api }

11

/**

12

* Drag-and-drop sorting with reactive list updates

13

* @param selector - CSS selector for sortable container

14

* @param list - Reactive array to keep in sync with DOM order

15

* @param options - SortableJS and VueUse configuration options

16

* @returns Sortable control interface

17

*/

18

function useSortable<T>(

19

selector: string,

20

list: MaybeRef<T[]>,

21

options?: UseSortableOptions

22

): UseSortableReturn;

23

24

/**

25

* Drag-and-drop sorting with element reference

26

* @param el - Element or element getter function

27

* @param list - Reactive array to keep in sync with DOM order

28

* @param options - SortableJS and VueUse configuration options

29

* @returns Sortable control interface

30

*/

31

function useSortable<T>(

32

el: MaybeRefOrGetter<MaybeElement>,

33

list: MaybeRef<T[]>,

34

options?: UseSortableOptions

35

): UseSortableReturn;

36

37

interface UseSortableReturn {

38

/** Start the sortable functionality */

39

start: () => void;

40

/** Stop the sortable functionality */

41

stop: () => void;

42

/** Get or set SortableJS options */

43

option: (<K extends keyof Sortable.Options>(name: K, value: Sortable.Options[K]) => void)

44

& (<K extends keyof Sortable.Options>(name: K) => Sortable.Options[K]);

45

}

46

47

type UseSortableOptions = Sortable.Options & ConfigurableDocument;

48

49

// SortableJS Options (key ones)

50

namespace Sortable {

51

interface Options {

52

group?: string | GroupOptions;

53

sort?: boolean;

54

disabled?: boolean;

55

animation?: number;

56

handle?: string;

57

filter?: string;

58

draggable?: string;

59

ghostClass?: string;

60

chosenClass?: string;

61

dragClass?: string;

62

direction?: 'vertical' | 'horizontal';

63

touchStartThreshold?: number;

64

emptyInsertThreshold?: number;

65

onStart?: (evt: SortableEvent) => void;

66

onEnd?: (evt: SortableEvent) => void;

67

onAdd?: (evt: SortableEvent) => void;

68

onUpdate?: (evt: SortableEvent) => void;

69

onSort?: (evt: SortableEvent) => void;

70

onRemove?: (evt: SortableEvent) => void;

71

onMove?: (evt: MoveEvent) => boolean | -1 | 1 | void;

72

onClone?: (evt: SortableEvent) => void;

73

onChange?: (evt: SortableEvent) => void;

74

}

75

76

interface SortableEvent {

77

oldIndex?: number;

78

newIndex?: number;

79

item: HTMLElement;

80

from: HTMLElement;

81

to: HTMLElement;

82

clone: HTMLElement;

83

}

84

}

85

```

86

87

**Usage Examples:**

88

89

```typescript

90

import { useSortable } from "@vueuse/integrations/useSortable";

91

import { ref } from 'vue';

92

93

// Basic sortable list

94

const items = ref(['Item 1', 'Item 2', 'Item 3', 'Item 4']);

95

96

const { start, stop } = useSortable('.sortable-list', items, {

97

animation: 150,

98

ghostClass: 'ghost',

99

onEnd: (evt) => {

100

console.log('Item moved from', evt.oldIndex, 'to', evt.newIndex);

101

}

102

});

103

104

// With element reference

105

const sortableEl = ref<HTMLElement>();

106

const { start, stop } = useSortable(sortableEl, items);

107

108

// Advanced configuration

109

const todoItems = ref([

110

{ id: 1, text: 'Learn Vue.js', completed: false },

111

{ id: 2, text: 'Build an app', completed: false },

112

{ id: 3, text: 'Deploy to production', completed: true }

113

]);

114

115

const { start, stop, option } = useSortable('.todo-list', todoItems, {

116

handle: '.drag-handle',

117

filter: '.no-drag',

118

animation: 200,

119

ghostClass: 'sortable-ghost',

120

chosenClass: 'sortable-chosen',

121

dragClass: 'sortable-drag',

122

onStart: (evt) => {

123

console.log('Drag started');

124

},

125

onEnd: (evt) => {

126

console.log('Drag ended');

127

// List is automatically updated

128

}

129

});

130

131

// Dynamic options

132

option('animation', 300);

133

const currentAnimation = option('animation');

134

135

// Multiple lists with shared group

136

const list1 = ref(['A', 'B', 'C']);

137

const list2 = ref(['X', 'Y', 'Z']);

138

139

useSortable('.list-1', list1, {

140

group: 'shared',

141

animation: 150

142

});

143

144

useSortable('.list-2', list2, {

145

group: 'shared',

146

animation: 150

147

});

148

```

149

150

### Utility Functions

151

152

Helper functions for DOM manipulation in sortable contexts.

153

154

```typescript { .api }

155

/**

156

* Insert a DOM node at a specific index within a parent element

157

* @param parentElement - Parent container element

158

* @param element - Element to insert

159

* @param index - Target index position

160

*/

161

function insertNodeAt(parentElement: Element, element: Element, index: number): void;

162

163

/**

164

* Remove a DOM node from its parent

165

* @param node - Node to remove

166

*/

167

function removeNode(node: Node): void;

168

169

/**

170

* Move an array element from one index to another

171

* @param list - Reactive array to modify

172

* @param from - Source index

173

* @param to - Target index

174

* @param e - Optional SortableJS event for additional context

175

*/

176

function moveArrayElement<T>(

177

list: MaybeRef<T[]>,

178

from: number,

179

to: number,

180

e?: Sortable.SortableEvent | null

181

): void;

182

```

183

184

### UseSortable Component

185

186

Declarative sortable component for template-based usage.

187

188

```typescript { .api }

189

/**

190

* Declarative sortable component

191

*/

192

const UseSortable = defineComponent({

193

name: 'UseSortable',

194

props: {

195

/** Reactive array model */

196

modelValue: {

197

type: Array as PropType<any[]>,

198

required: true

199

},

200

/** HTML tag to render */

201

tag: {

202

type: String,

203

default: 'div'

204

},

205

/** SortableJS options */

206

options: {

207

type: Object as PropType<UseSortableOptions>,

208

required: true

209

}

210

},

211

emits: ['update:modelValue'],

212

slots: {

213

default: (props: {

214

item: any;

215

index: number;

216

}) => any;

217

}

218

});

219

```

220

221

**Component Usage:**

222

223

```vue

224

<template>

225

<UseSortable

226

v-model="items"

227

tag="ul"

228

:options="sortableOptions"

229

class="sortable-list"

230

>

231

<template #default="{ item, index }">

232

<li :key="item.id" class="sortable-item">

233

<span class="drag-handle">⋮⋮</span>

234

{{ item.text }}

235

<button @click="removeItem(index)">Remove</button>

236

</li>

237

</template>

238

</UseSortable>

239

</template>

240

241

<script setup>

242

import { UseSortable } from "@vueuse/integrations/useSortable";

243

import { ref } from 'vue';

244

245

const items = ref([

246

{ id: 1, text: 'First item' },

247

{ id: 2, text: 'Second item' },

248

{ id: 3, text: 'Third item' }

249

]);

250

251

const sortableOptions = {

252

handle: '.drag-handle',

253

animation: 150,

254

ghostClass: 'ghost-item'

255

};

256

257

const removeItem = (index) => {

258

items.value.splice(index, 1);

259

};

260

</script>

261

262

<style>

263

.sortable-list {

264

list-style: none;

265

padding: 0;

266

}

267

268

.sortable-item {

269

padding: 10px;

270

border: 1px solid #ddd;

271

margin-bottom: 5px;

272

cursor: move;

273

}

274

275

.ghost-item {

276

opacity: 0.5;

277

}

278

279

.drag-handle {

280

color: #999;

281

margin-right: 10px;

282

cursor: grab;

283

}

284

</style>

285

```

286

287

### useFocusTrap

288

289

Focus management and accessibility enhancement using focus-trap.

290

291

```typescript { .api }

292

/**

293

* Focus management and accessibility enhancement

294

* @param target - Target element(s) to trap focus within

295

* @param options - Focus trap configuration options

296

* @returns Focus trap control interface

297

*/

298

function useFocusTrap(

299

target: MaybeRefOrGetter<Arrayable<MaybeRefOrGetter<string> | MaybeComputedElementRef>>,

300

options?: UseFocusTrapOptions

301

): UseFocusTrapReturn;

302

303

interface UseFocusTrapReturn {

304

/** Whether focus trap currently has focus */

305

hasFocus: ShallowRef<boolean>;

306

/** Whether focus trap is paused */

307

isPaused: ShallowRef<boolean>;

308

/** Activate the focus trap */

309

activate: (opts?: ActivateOptions) => void;

310

/** Deactivate the focus trap */

311

deactivate: (opts?: DeactivateOptions) => void;

312

/** Pause the focus trap */

313

pause: Fn;

314

/** Unpause the focus trap */

315

unpause: Fn;

316

}

317

318

interface UseFocusTrapOptions extends Options {

319

/** Activate focus trap immediately */

320

immediate?: boolean;

321

}

322

323

// focus-trap options

324

interface Options {

325

onActivate?: (focusTrapInstance: FocusTrap) => void;

326

onDeactivate?: (focusTrapInstance: FocusTrap) => void;

327

onPause?: (focusTrapInstance: FocusTrap) => void;

328

onUnpause?: (focusTrapInstance: FocusTrap) => void;

329

onPostActivate?: (focusTrapInstance: FocusTrap) => void;

330

onPostDeactivate?: (focusTrapInstance: FocusTrap) => void;

331

checkCanFocusTrap?: (focusTrapContainers: HTMLElement[]) => Promise<void>;

332

checkCanReturnFocus?: (triggerElement: HTMLElement) => Promise<void>;

333

initialFocus?: string | HTMLElement | (() => HTMLElement | string) | false;

334

fallbackFocus?: string | HTMLElement | (() => HTMLElement | string);

335

escapeDeactivates?: boolean | ((e: KeyboardEvent) => boolean);

336

clickOutsideDeactivates?: boolean | ((e: MouseEvent | TouchEvent) => boolean);

337

returnFocusOnDeactivate?: boolean;

338

setReturnFocus?: HTMLElement | string | ((nodeFocusedBeforeActivation: HTMLElement) => HTMLElement | string);

339

allowOutsideClick?: boolean | ((e: MouseEvent | TouchEvent) => boolean);

340

preventScroll?: boolean;

341

tabbableOptions?: TabbableOptions;

342

}

343

344

interface ActivateOptions {

345

onActivate?: (focusTrapInstance: FocusTrap) => void;

346

}

347

348

interface DeactivateOptions {

349

onDeactivate?: (focusTrapInstance: FocusTrap) => void;

350

checkCanReturnFocus?: (trigger: HTMLElement) => Promise<void>;

351

}

352

```

353

354

**Usage Examples:**

355

356

```typescript

357

import { useFocusTrap } from "@vueuse/integrations/useFocusTrap";

358

import { ref } from 'vue';

359

360

// Basic focus trap

361

const modalRef = ref<HTMLElement>();

362

const { activate, deactivate, hasFocus } = useFocusTrap(modalRef);

363

364

// Show modal with focus trap

365

const showModal = () => {

366

// Show modal UI

367

activate();

368

};

369

370

const closeModal = () => {

371

deactivate();

372

// Hide modal UI

373

};

374

375

// Multiple containers

376

const containers = [

377

ref<HTMLElement>(),

378

ref<HTMLElement>()

379

];

380

381

const { activate, deactivate } = useFocusTrap(containers, {

382

immediate: false,

383

escapeDeactivates: true,

384

clickOutsideDeactivates: true

385

});

386

387

// Advanced configuration

388

const { activate, deactivate, pause, unpause } = useFocusTrap(modalRef, {

389

initialFocus: '#first-input',

390

fallbackFocus: '#cancel-button',

391

onActivate: () => console.log('Focus trap activated'),

392

onDeactivate: () => console.log('Focus trap deactivated'),

393

escapeDeactivates: (e) => {

394

// Custom logic for escape key

395

return !e.shiftKey;

396

},

397

clickOutsideDeactivates: false

398

});

399

400

// Temporarily pause focus trap

401

const handleOverlay = () => {

402

pause();

403

// Handle overlay interaction

404

setTimeout(unpause, 1000);

405

};

406

```

407

408

### UseFocusTrap Component

409

410

Declarative focus trap component wrapper.

411

412

```typescript { .api }

413

/**

414

* Declarative focus trap component

415

*/

416

const UseFocusTrap = defineComponent({

417

name: 'UseFocusTrap',

418

props: {

419

/** HTML tag to render */

420

as: {

421

type: [String, Object] as PropType<string | Component>,

422

default: 'div'

423

},

424

/** Focus trap options */

425

options: {

426

type: Object as PropType<UseFocusTrapOptions>,

427

default: () => ({})

428

}

429

},

430

slots: {

431

default: (props: {

432

hasFocus: boolean;

433

isPaused: boolean;

434

activate: (opts?: ActivateOptions) => void;

435

deactivate: (opts?: DeactivateOptions) => void;

436

pause: Fn;

437

unpause: Fn;

438

}) => any;

439

}

440

});

441

```

442

443

**Component Usage:**

444

445

```vue

446

<template>

447

<UseFocusTrap

448

as="div"

449

:options="focusTrapOptions"

450

v-slot="{ hasFocus, activate, deactivate }"

451

>

452

<div v-if="isModalOpen" class="modal">

453

<h2>Modal Title</h2>

454

<input ref="firstInput" placeholder="First input" />

455

<input placeholder="Second input" />

456

<button @click="closeModal">Close</button>

457

</div>

458

</UseFocusTrap>

459

</template>

460

461

<script setup>

462

import { UseFocusTrap } from "@vueuse/integrations/useFocusTrap";

463

import { ref } from 'vue';

464

465

const isModalOpen = ref(false);

466

const firstInput = ref<HTMLInputElement>();

467

468

const focusTrapOptions = {

469

immediate: true,

470

initialFocus: () => firstInput.value,

471

escapeDeactivates: true

472

};

473

474

const closeModal = () => {

475

isModalOpen.value = false;

476

};

477

</script>

478

```