or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

components.mdcomposables.mddata-display.mddirectives.mdfeedback.mdforms.mdframework-core.mdicons.mdindex.mdinternationalization.mdlab-components.mdnavigation.mdtheming.mdtransitions.mdutilities.md

composables.mddocs/

0

# Composables

1

2

Reactive composition functions for accessing Vuetify's framework features including theming, display management, internationalization, and utility functions.

3

4

## Capabilities

5

6

### useTheme

7

8

Composable for accessing and managing the application's theme system including colors, variants, and theme switching.

9

10

```typescript { .api }

11

/**

12

* Theme management composable

13

* @returns Theme instance with reactive theme state and controls

14

*/

15

function useTheme(): ThemeInstance;

16

17

interface ThemeInstance {

18

/** Currently active theme name */

19

current: Ref<ThemeDefinition>;

20

/** Available themes */

21

themes: Ref<Record<string, ThemeDefinition>>;

22

/** Whether current theme is dark */

23

isDisabled: Ref<boolean>;

24

/** Global theme name */

25

name: Ref<string>;

26

/** Theme computed styles */

27

computedThemes: ComputedRef<Record<string, ThemeDefinition>>;

28

/** Theme CSS variables */

29

themeClasses: Ref<Record<string, boolean>>;

30

/** Theme CSS variables as styles */

31

styles: ComputedRef<string>;

32

}

33

34

interface ThemeDefinition {

35

/** Whether theme is dark mode */

36

dark: boolean;

37

/** Theme colors palette */

38

colors: Record<string, string>;

39

/** CSS custom properties */

40

variables: Record<string, string | number>;

41

}

42

```

43

44

**Usage Examples:**

45

46

```vue

47

<template>

48

<div>

49

<!-- Theme toggle -->

50

<v-btn @click="toggleTheme" :color="theme.current.value.dark ? 'white' : 'black'">

51

{{ theme.current.value.dark ? 'Light Mode' : 'Dark Mode' }}

52

</v-btn>

53

54

<!-- Theme-aware styling -->

55

<v-card :color="theme.current.value.colors.surface">

56

<v-card-text :style="{ color: theme.current.value.colors.onSurface }">

57

Current theme: {{ theme.name.value }}

58

</v-card-text>

59

</v-card>

60

61

<!-- Custom theme switcher -->

62

<v-select

63

v-model="theme.name.value"

64

:items="themeOptions"

65

label="Select Theme"

66

/>

67

68

<!-- Access theme colors -->

69

<div class="color-palette">

70

<div

71

v-for="(color, name) in theme.current.value.colors"

72

:key="name"

73

:style="{ backgroundColor: color }"

74

class="color-swatch"

75

:title="`${name}: ${color}`"

76

>

77

{{ name }}

78

</div>

79

</div>

80

</div>

81

</template>

82

83

<script setup>

84

const theme = useTheme();

85

86

const themeOptions = computed(() =>

87

Object.keys(theme.themes.value).map(name => ({

88

title: name.charAt(0).toUpperCase() + name.slice(1),

89

value: name

90

}))

91

);

92

93

const toggleTheme = () => {

94

theme.name.value = theme.current.value.dark ? 'light' : 'dark';

95

};

96

97

// Create custom theme

98

const createCustomTheme = () => {

99

theme.themes.value.custom = {

100

dark: false,

101

colors: {

102

primary: '#6200EA',

103

secondary: '#03DAC6',

104

surface: '#F5F5F5',

105

onSurface: '#1C1B1F'

106

},

107

variables: {

108

'border-radius-root': '8px'

109

}

110

};

111

};

112

113

// Watch theme changes

114

watch(() => theme.current.value.dark, (isDark) => {

115

document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';

116

});

117

</script>

118

119

<style>

120

.color-palette {

121

display: grid;

122

grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));

123

gap: 8px;

124

margin-top: 16px;

125

}

126

127

.color-swatch {

128

padding: 12px;

129

border-radius: 4px;

130

font-size: 12px;

131

color: white;

132

text-shadow: 1px 1px 2px rgba(0,0,0,0.7);

133

}

134

</style>

135

```

136

137

### useDisplay

138

139

Composable for responsive design and display utilities including breakpoints, screen size detection, and mobile/desktop detection.

140

141

```typescript { .api }

142

/**

143

* Display and responsive design composable

144

* @returns Display instance with reactive breakpoint and size information

145

*/

146

function useDisplay(): DisplayInstance;

147

148

interface DisplayInstance {

149

/** Current display breakpoints */

150

xs: Ref<boolean>;

151

sm: Ref<boolean>;

152

md: Ref<boolean>;

153

lg: Ref<boolean>;

154

xl: Ref<boolean>;

155

xxl: Ref<boolean>;

156

157

/** Breakpoint names when active */

158

smAndUp: Ref<boolean>;

159

mdAndUp: Ref<boolean>;

160

lgAndUp: Ref<boolean>;

161

xlAndUp: Ref<boolean>;

162

163

smAndDown: Ref<boolean>;

164

mdAndDown: Ref<boolean>;

165

lgAndDown: Ref<boolean>;

166

xlAndDown: Ref<boolean>;

167

168

/** Current breakpoint name */

169

name: Ref<string>;

170

171

/** Display dimensions */

172

height: Ref<number>;

173

width: Ref<number>;

174

175

/** Platform detection */

176

mobile: Ref<boolean>;

177

mobileBreakpoint: Ref<number | string>;

178

179

/** Threshold values */

180

thresholds: Ref<DisplayThresholds>;

181

182

/** Platform type */

183

platform: Ref<'android' | 'ios' | 'desktop'>;

184

185

/** Touch capability */

186

touch: Ref<boolean>;

187

188

/** Screen pixel density */

189

ssr: boolean;

190

}

191

192

interface DisplayThresholds {

193

xs: number;

194

sm: number;

195

md: number;

196

lg: number;

197

xl: number;

198

xxl: number;

199

}

200

201

interface DisplayBreakpoint {

202

xs: boolean;

203

sm: boolean;

204

md: boolean;

205

lg: boolean;

206

xl: boolean;

207

xxl: boolean;

208

}

209

```

210

211

**Usage Examples:**

212

213

```vue

214

<template>

215

<div>

216

<!-- Responsive layout -->

217

<v-container>

218

<v-row>

219

<v-col

220

:cols="display.xs.value ? 12 : display.sm.value ? 6 : 4"

221

v-for="item in items"

222

:key="item.id"

223

>

224

<v-card>{{ item.title }}</v-card>

225

</v-col>

226

</v-row>

227

</v-container>

228

229

<!-- Conditional rendering based on screen size -->

230

<v-navigation-drawer v-if="display.mdAndUp.value" permanent>

231

Desktop navigation

232

</v-navigation-drawer>

233

234

<v-bottom-navigation v-if="display.mobile.value">

235

Mobile navigation

236

</v-bottom-navigation>

237

238

<!-- Display information -->

239

<v-card>

240

<v-card-title>Display Information</v-card-title>

241

<v-card-text>

242

<p>Breakpoint: {{ display.name.value }}</p>

243

<p>Size: {{ display.width.value }}x{{ display.height.value }}</p>

244

<p>Mobile: {{ display.mobile.value }}</p>

245

<p>Platform: {{ display.platform.value }}</p>

246

<p>Touch: {{ display.touch.value }}</p>

247

248

<div class="breakpoints">

249

<v-chip

250

v-for="breakpoint in breakpoints"

251

:key="breakpoint"

252

:color="display[breakpoint].value ? 'primary' : 'default'"

253

:variant="display[breakpoint].value ? 'flat' : 'outlined'"

254

>

255

{{ breakpoint.toUpperCase() }}

256

</v-chip>

257

</div>

258

</v-card-text>

259

</v-card>

260

261

<!-- Responsive component props -->

262

<v-btn

263

:size="display.xs.value ? 'small' : 'large'"

264

:variant="display.mobile.value ? 'outlined' : 'elevated'"

265

:block="display.smAndDown.value"

266

>

267

Responsive Button

268

</v-btn>

269

270

<!-- Responsive images -->

271

<v-img

272

:src="getResponsiveImageSrc()"

273

:aspect-ratio="display.xs.value ? 1 : 16/9"

274

:height="display.mobile.value ? 200 : 400"

275

/>

276

</div>

277

</template>

278

279

<script setup>

280

const display = useDisplay();

281

282

const breakpoints = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];

283

284

const items = ref([

285

{ id: 1, title: 'Item 1' },

286

{ id: 2, title: 'Item 2' },

287

{ id: 3, title: 'Item 3' },

288

{ id: 4, title: 'Item 4' },

289

]);

290

291

const getResponsiveImageSrc = () => {

292

if (display.xs.value) return '/images/small.jpg';

293

if (display.sm.value) return '/images/medium.jpg';

294

return '/images/large.jpg';

295

};

296

297

// Computed properties for complex responsive logic

298

const columns = computed(() => {

299

if (display.xs.value) return 1;

300

if (display.sm.value) return 2;

301

if (display.md.value) return 3;

302

return 4;

303

});

304

305

const sidebarVisible = computed(() => {

306

return display.lgAndUp.value && !display.mobile.value;

307

});

308

309

// Watch breakpoint changes

310

watch(() => display.name.value, (newBreakpoint, oldBreakpoint) => {

311

console.log(`Breakpoint changed from ${oldBreakpoint} to ${newBreakpoint}`);

312

});

313

314

// Responsive data fetching

315

watchEffect(() => {

316

if (display.mobile.value) {

317

// Load mobile-optimized data

318

loadMobileData();

319

} else {

320

// Load desktop data with more details

321

loadDesktopData();

322

}

323

});

324

</script>

325

326

<style>

327

.breakpoints {

328

display: flex;

329

gap: 8px;

330

flex-wrap: wrap;

331

margin-top: 16px;

332

}

333

</style>

334

```

335

336

### useLocale

337

338

Composable for internationalization, managing locale settings, translations, and text formatting.

339

340

```typescript { .api }

341

/**

342

* Internationalization composable

343

* @returns Locale instance for managing translations and locale settings

344

*/

345

function useLocale(): LocaleInstance;

346

347

interface LocaleInstance {

348

/** Current locale code */

349

current: Ref<string>;

350

/** Fallback locale code */

351

fallback: Ref<string>;

352

/** Available locales */

353

locales: Ref<Record<string, LocaleMessages>>;

354

/** Translation messages */

355

messages: Ref<Record<string, LocaleMessages>>;

356

/** Translate function */

357

t: (key: string, ...params: unknown[]) => string;

358

/** Number formatting function */

359

n: (value: number) => string;

360

/** Provide locale to components */

361

provide: Record<string, any>;

362

/** RTL (right-to-left) state */

363

isRtl: Ref<boolean>;

364

}

365

366

interface LocaleMessages {

367

[key: string]: string | LocaleMessages;

368

}

369

370

interface LocaleOptions {

371

/** Default locale */

372

locale?: string;

373

/** Fallback locale */

374

fallback?: string;

375

/** Translation messages */

376

messages?: Record<string, LocaleMessages>;

377

/** RTL locale configuration */

378

rtl?: Record<string, boolean>;

379

}

380

```

381

382

**Usage Examples:**

383

384

```vue

385

<template>

386

<div>

387

<!-- Language selector -->

388

<v-select

389

v-model="locale.current.value"

390

:items="availableLocales"

391

:label="locale.t('selectLanguage')"

392

prepend-icon="mdi-translate"

393

/>

394

395

<!-- Translated content -->

396

<v-card>

397

<v-card-title>{{ locale.t('welcome') }}</v-card-title>

398

<v-card-text>

399

<p>{{ locale.t('description') }}</p>

400

<p>{{ locale.t('userCount', userCount) }}</p>

401

<p>{{ locale.t('lastLogin', formattedDate) }}</p>

402

</v-card-text>

403

</v-card>

404

405

<!-- Formatted numbers -->

406

<v-list>

407

<v-list-item>

408

<v-list-item-title>{{ locale.t('price') }}</v-list-item-title>

409

<v-list-item-subtitle>{{ locale.n(1234.56) }}</v-list-item-subtitle>

410

</v-list-item>

411

<v-list-item>

412

<v-list-item-title>{{ locale.t('quantity') }}</v-list-item-title>

413

<v-list-item-subtitle>{{ locale.n(42) }}</v-list-item-subtitle>

414

</v-list-item>

415

</v-list>

416

417

<!-- RTL awareness -->

418

<div :class="{ 'text-right': locale.isRtl.value }">

419

{{ locale.t('directionalText') }}

420

</div>

421

422

<!-- Conditional content based on locale -->

423

<v-alert

424

v-if="locale.current.value === 'ar'"

425

type="info"

426

>

427

{{ locale.t('arabicSpecificMessage') }}

428

</v-alert>

429

</div>

430

</template>

431

432

<script setup>

433

const locale = useLocale();

434

435

const availableLocales = computed(() => [

436

{ title: 'English', value: 'en' },

437

{ title: 'Español', value: 'es' },

438

{ title: 'Français', value: 'fr' },

439

{ title: 'العربية', value: 'ar' },

440

{ title: '中文', value: 'zh' },

441

]);

442

443

const userCount = ref(1337);

444

445

const formattedDate = computed(() => {

446

return new Date().toLocaleDateString(locale.current.value);

447

});

448

449

// Add custom translations

450

const addTranslations = () => {

451

locale.messages.value = {

452

...locale.messages.value,

453

en: {

454

welcome: 'Welcome',

455

description: 'This is a multilingual application',

456

userCount: 'We have {0} users',

457

lastLogin: 'Last login: {0}',

458

selectLanguage: 'Select Language',

459

price: 'Price',

460

quantity: 'Quantity',

461

directionalText: 'This text respects text direction',

462

...locale.messages.value.en

463

},

464

es: {

465

welcome: 'Bienvenido',

466

description: 'Esta es una aplicación multiidioma',

467

userCount: 'Tenemos {0} usuarios',

468

lastLogin: 'Último inicio de sesión: {0}',

469

selectLanguage: 'Seleccionar Idioma',

470

price: 'Precio',

471

quantity: 'Cantidad',

472

directionalText: 'Este texto respeta la dirección del texto',

473

},

474

fr: {

475

welcome: 'Bienvenue',

476

description: 'Ceci est une application multilingue',

477

userCount: 'Nous avons {0} utilisateurs',

478

lastLogin: 'Dernière connexion : {0}',

479

selectLanguage: 'Choisir la Langue',

480

price: 'Prix',

481

quantity: 'Quantité',

482

directionalText: 'Ce texte respecte la direction du texte',

483

},

484

ar: {

485

welcome: 'مرحبا',

486

description: 'هذا تطبيق متعدد اللغات',

487

userCount: 'لدينا {0} مستخدم',

488

lastLogin: 'آخر تسجيل دخول: {0}',

489

selectLanguage: 'اختر اللغة',

490

price: 'السعر',

491

quantity: 'الكمية',

492

directionalText: 'هذا النص يحترم اتجاه النص',

493

arabicSpecificMessage: 'رسالة خاصة باللغة العربية'

494

}

495

};

496

};

497

498

// Dynamic translations loading

499

const loadLocaleMessages = async (localeCode) => {

500

try {

501

const messages = await import(`../locales/${localeCode}.js`);

502

locale.messages.value[localeCode] = messages.default;

503

} catch (error) {

504

console.warn(`Failed to load locale: ${localeCode}`);

505

}

506

};

507

508

// Watch locale changes

509

watch(() => locale.current.value, (newLocale) => {

510

// Update document language

511

document.documentElement.lang = newLocale;

512

// Load locale-specific data

513

loadLocaleMessages(newLocale);

514

});

515

516

onMounted(() => {

517

addTranslations();

518

});

519

</script>

520

```

521

522

### useRtl

523

524

Composable for right-to-left text direction support and layout management.

525

526

```typescript { .api }

527

/**

528

* Right-to-left text direction composable

529

* @returns RTL instance for managing text direction

530

*/

531

function useRtl(): RtlInstance;

532

533

interface RtlInstance {

534

/** Whether current locale uses RTL */

535

isRtl: Ref<boolean>;

536

/** RTL value as computed property */

537

rtl: Ref<boolean>;

538

/** RTL class name */

539

rtlClasses: Ref<string>;

540

}

541

542

interface RtlOptions {

543

/** RTL configuration per locale */

544

rtl?: Record<string, boolean>;

545

}

546

```

547

548

**Usage Examples:**

549

550

```vue

551

<template>

552

<div :dir="rtl.isRtl.value ? 'rtl' : 'ltr'">

553

<!-- RTL-aware layout -->

554

<v-app-bar :class="rtl.rtlClasses.value">

555

<v-btn

556

:icon="rtl.isRtl.value ? 'mdi-menu-right' : 'mdi-menu'"

557

@click="toggleDrawer"

558

/>

559

<v-toolbar-title>{{ title }}</v-toolbar-title>

560

<v-spacer />

561

<v-btn @click="toggleRtl">

562

{{ rtl.isRtl.value ? 'LTR' : 'RTL' }}

563

</v-btn>

564

</v-app-bar>

565

566

<!-- Navigation drawer with RTL awareness -->

567

<v-navigation-drawer

568

v-model="drawer"

569

:location="rtl.isRtl.value ? 'right' : 'left'"

570

>

571

<v-list>

572

<v-list-item

573

v-for="item in navigationItems"

574

:key="item.id"

575

:prepend-icon="item.icon"

576

:title="item.title"

577

@click="navigateTo(item.route)"

578

/>

579

</v-list>

580

</v-navigation-drawer>

581

582

<!-- Content with RTL styling -->

583

<v-main>

584

<v-container>

585

<!-- Text alignment -->

586

<v-card :class="{ 'text-right': rtl.isRtl.value }">

587

<v-card-title>{{ cardTitle }}</v-card-title>

588

<v-card-text>

589

<p>{{ longText }}</p>

590

</v-card-text>

591

</v-card>

592

593

<!-- Form with RTL layout -->

594

<v-form class="mt-4">

595

<v-text-field

596

v-model="name"

597

:label="nameLabel"

598

:prepend-icon="rtl.isRtl.value ? 'mdi-account-outline' : undefined"

599

:append-icon="rtl.isRtl.value ? undefined : 'mdi-account-outline'"

600

/>

601

602

<v-textarea

603

v-model="message"

604

:label="messageLabel"

605

:style="{ textAlign: rtl.isRtl.value ? 'right' : 'left' }"

606

/>

607

</v-form>

608

609

<!-- Chips with RTL spacing -->

610

<div :class="['d-flex', 'flex-wrap', rtl.isRtl.value ? 'justify-end' : 'justify-start']">

611

<v-chip

612

v-for="tag in tags"

613

:key="tag"

614

:class="rtl.isRtl.value ? 'ml-2 mb-2' : 'mr-2 mb-2'"

615

>

616

{{ tag }}

617

</v-chip>

618

</div>

619

</v-container>

620

</v-main>

621

</div>

622

</template>

623

624

<script setup>

625

const rtl = useRtl();

626

const locale = useLocale();

627

628

const drawer = ref(false);

629

const name = ref('');

630

const message = ref('');

631

632

const title = computed(() =>

633

rtl.isRtl.value ? 'تطبيق ويب' : 'Web Application'

634

);

635

636

const cardTitle = computed(() =>

637

rtl.isRtl.value ? 'المحتوى' : 'Content'

638

);

639

640

const nameLabel = computed(() =>

641

rtl.isRtl.value ? 'الاسم' : 'Name'

642

);

643

644

const messageLabel = computed(() =>

645

rtl.isRtl.value ? 'الرسالة' : 'Message'

646

);

647

648

const longText = computed(() =>

649

rtl.isRtl.value

650

? 'هذا نص طويل لتوضيح كيفية تعامل التطبيق مع النصوص باللغة العربية والتي تُكتب من اليمين إلى اليسار.'

651

: 'This is a long text to demonstrate how the application handles right-to-left text direction and layout adjustments.'

652

);

653

654

const navigationItems = computed(() => [

655

{

656

id: 1,

657

title: rtl.isRtl.value ? 'الرئيسية' : 'Home',

658

icon: 'mdi-home',

659

route: '/'

660

},

661

{

662

id: 2,

663

title: rtl.isRtl.value ? 'حول' : 'About',

664

icon: 'mdi-information',

665

route: '/about'

666

},

667

{

668

id: 3,

669

title: rtl.isRtl.value ? 'اتصال' : 'Contact',

670

icon: 'mdi-phone',

671

route: '/contact'

672

}

673

]);

674

675

const tags = computed(() =>

676

rtl.isRtl.value

677

? ['تقنية', 'ويب', 'تطوير']

678

: ['Technology', 'Web', 'Development']

679

);

680

681

const toggleDrawer = () => {

682

drawer.value = !drawer.value;

683

};

684

685

const toggleRtl = () => {

686

// Toggle between RTL and LTR for demonstration

687

locale.current.value = rtl.isRtl.value ? 'en' : 'ar';

688

};

689

690

const navigateTo = (route) => {

691

console.log(`Navigating to: ${route}`);

692

drawer.value = false;

693

};

694

695

// Watch RTL changes for additional setup

696

watch(() => rtl.isRtl.value, (isRtl) => {

697

// Update document direction

698

document.documentElement.dir = isRtl ? 'rtl' : 'ltr';

699

700

// Apply global RTL styles

701

document.body.classList.toggle('v-locale--is-rtl', isRtl);

702

703

// Close drawer on direction change to prevent layout issues

704

drawer.value = false;

705

});

706

</script>

707

708

<style>

709

.v-locale--is-rtl .v-navigation-drawer--left {

710

right: 0;

711

left: auto;

712

}

713

714

.v-locale--is-rtl .v-list-item__prepend {

715

margin-inline-start: 0;

716

margin-inline-end: 16px;

717

}

718

</style>

719

```

720

721

### useDefaults

722

723

Composable for managing component default props and global configuration.

724

725

```typescript { .api }

726

/**

727

* Component defaults management composable

728

* @returns Defaults instance for managing component defaults

729

*/

730

function useDefaults(): DefaultsInstance;

731

732

interface DefaultsInstance {

733

/** Get component defaults */

734

getDefaults(): Record<string, any>;

735

/** Reset to initial defaults */

736

reset(): void;

737

/** Set global defaults for components */

738

global: Record<string, any>;

739

}

740

741

interface DefaultsOptions {

742

/** Default props for components */

743

[componentName: string]: Record<string, any>;

744

}

745

```

746

747

**Usage Examples:**

748

749

```vue

750

<template>

751

<div>

752

<!-- Components using default props -->

753

<v-btn>Default Button</v-btn>

754

<v-card>

755

<v-card-title>Default Card</v-card-title>

756

</v-card>

757

758

<!-- Override defaults with explicit props -->

759

<v-btn color="success" variant="outlined">

760

Custom Button

761

</v-btn>

762

763

<!-- Form with default styling -->

764

<v-form>

765

<v-text-field label="Name" />

766

<v-text-field label="Email" type="email" />

767

<v-select :items="options" label="Options" />

768

</v-form>

769

</div>

770

</template>

771

772

<script setup>

773

const defaults = useDefaults();

774

775

// Set global defaults for components

776

const setGlobalDefaults = () => {

777

defaults.global = {

778

VBtn: {

779

color: 'primary',

780

variant: 'elevated',

781

size: 'default'

782

},

783

VCard: {

784

variant: 'outlined',

785

elevation: 2

786

},

787

VTextField: {

788

variant: 'outlined',

789

density: 'compact'

790

},

791

VSelect: {

792

variant: 'outlined',

793

density: 'compact'

794

}

795

};

796

};

797

798

// Get current defaults

799

const currentDefaults = computed(() => defaults.getDefaults());

800

801

// Reset defaults

802

const resetDefaults = () => {

803

defaults.reset();

804

};

805

806

const options = ref([

807

{ title: 'Option 1', value: 1 },

808

{ title: 'Option 2', value: 2 }

809

]);

810

811

onMounted(() => {

812

setGlobalDefaults();

813

});

814

</script>

815

```

816

817

### useDate

818

819

Composable for date handling, formatting, and localization with different date adapter support.

820

821

```typescript { .api }

822

/**

823

* Date handling and formatting composable

824

* @returns Date instance for date operations and formatting

825

*/

826

function useDate(): DateInstance;

827

828

interface DateInstance {

829

/** Date adapter instance */

830

adapter: DateAdapter;

831

/** Current locale */

832

locale: Ref<string>;

833

/** Format date to string */

834

format: (date: any, formatString: string) => string;

835

/** Parse string to date */

836

parse: (value: string, format: string) => any;

837

/** Add time to date */

838

addDays: (date: any, days: number) => any;

839

addMonths: (date: any, months: number) => any;

840

addYears: (date: any, years: number) => any;

841

/** Date calculations */

842

startOfDay: (date: any) => any;

843

endOfDay: (date: any) => any;

844

startOfMonth: (date: any) => any;

845

endOfMonth: (date: any) => any;

846

/** Date comparisons */

847

isEqual: (date1: any, date2: any) => boolean;

848

isBefore: (date1: any, date2: any) => boolean;

849

isAfter: (date1: any, date2: any) => boolean;

850

isSameDay: (date1: any, date2: any) => boolean;

851

isSameMonth: (date1: any, date2: any) => boolean;

852

/** Date properties */

853

getYear: (date: any) => number;

854

getMonth: (date: any) => number;

855

getDate: (date: any) => number;

856

getDayOfWeek: (date: any) => number;

857

/** Validation */

858

isValid: (date: any) => boolean;

859

}

860

861

interface DateAdapter {

862

date(value?: any): any;

863

format(date: any, formatString: string): string;

864

parseISO(date: string): any;

865

toISO(date: any): string;

866

}

867

868

interface DateOptions {

869

/** Date adapter to use */

870

adapter?: DateAdapter;

871

/** Locale for date formatting */

872

locale?: string;

873

/** Date formats */

874

formats?: Record<string, string>;

875

}

876

```

877

878

**Usage Examples:**

879

880

```vue

881

<template>

882

<div>

883

<!-- Date display -->

884

<v-card>

885

<v-card-title>Date Information</v-card-title>

886

<v-card-text>

887

<p>Today: {{ date.format(today, 'fullDate') }}</p>

888

<p>Short: {{ date.format(today, 'short') }}</p>

889

<p>ISO: {{ date.format(today, 'YYYY-MM-DD') }}</p>

890

<p>Custom: {{ date.format(today, 'MMMM Do, YYYY') }}</p>

891

</v-card-text>

892

</v-card>

893

894

<!-- Date picker with formatting -->

895

<v-date-picker

896

v-model="selectedDate"

897

:format="dateFormat"

898

:locale="date.locale.value"

899

/>

900

901

<!-- Date operations -->

902

<v-list>

903

<v-list-item>

904

<v-list-item-title>Start of Month</v-list-item-title>

905

<v-list-item-subtitle>{{ date.format(startOfMonth, 'short') }}</v-list-item-subtitle>

906

</v-list-item>

907

<v-list-item>

908

<v-list-item-title>End of Month</v-list-item-title>

909

<v-list-item-subtitle>{{ date.format(endOfMonth, 'short') }}</v-list-item-subtitle>

910

</v-list-item>

911

<v-list-item>

912

<v-list-item-title>Next Week</v-list-item-title>

913

<v-list-item-subtitle>{{ date.format(nextWeek, 'short') }}</v-list-item-subtitle>

914

</v-list-item>

915

</v-list>

916

917

<!-- Date range picker -->

918

<v-form>

919

<v-text-field

920

v-model="startDateString"

921

label="Start Date"

922

type="date"

923

@update:model-value="validateDateRange"

924

/>

925

<v-text-field

926

v-model="endDateString"

927

label="End Date"

928

type="date"

929

:error="dateRangeError"

930

:error-messages="dateRangeError ? 'End date must be after start date' : ''"

931

@update:model-value="validateDateRange"

932

/>

933

</v-form>

934

935

<!-- Relative time display -->

936

<v-chip-group>

937

<v-chip

938

v-for="event in upcomingEvents"

939

:key="event.id"

940

:color="getEventColor(event.date)"

941

>

942

{{ event.title }} - {{ getRelativeTime(event.date) }}

943

</v-chip>

944

</v-chip-group>

945

</div>

946

</template>

947

948

<script setup>

949

const date = useDate();

950

951

const today = ref(date.adapter.date());

952

const selectedDate = ref(date.adapter.date());

953

const startDateString = ref('');

954

const endDateString = ref('');

955

const dateRangeError = ref(false);

956

957

const dateFormat = computed(() => {

958

return date.locale.value === 'en' ? 'MM/DD/YYYY' : 'DD/MM/YYYY';

959

});

960

961

const startOfMonth = computed(() => date.startOfMonth(today.value));

962

const endOfMonth = computed(() => date.endOfMonth(today.value));

963

const nextWeek = computed(() => date.addDays(today.value, 7));

964

965

const upcomingEvents = ref([

966

{ id: 1, title: 'Meeting', date: date.addDays(today.value, 1) },

967

{ id: 2, title: 'Deadline', date: date.addDays(today.value, 5) },

968

{ id: 3, title: 'Conference', date: date.addDays(today.value, 14) },

969

]);

970

971

const validateDateRange = () => {

972

if (startDateString.value && endDateString.value) {

973

const startDate = date.parse(startDateString.value, 'YYYY-MM-DD');

974

const endDate = date.parse(endDateString.value, 'YYYY-MM-DD');

975

976

dateRangeError.value = date.isAfter(startDate, endDate);

977

} else {

978

dateRangeError.value = false;

979

}

980

};

981

982

const getRelativeTime = (eventDate) => {

983

const diffDays = Math.ceil((eventDate - today.value) / (1000 * 60 * 60 * 24));

984

985

if (diffDays === 0) return 'Today';

986

if (diffDays === 1) return 'Tomorrow';

987

if (diffDays < 7) return `In ${diffDays} days`;

988

if (diffDays < 30) return `In ${Math.ceil(diffDays / 7)} weeks`;

989

return `In ${Math.ceil(diffDays / 30)} months`;

990

};

991

992

const getEventColor = (eventDate) => {

993

const diffDays = Math.ceil((eventDate - today.value) / (1000 * 60 * 60 * 24));

994

995

if (diffDays <= 1) return 'error';

996

if (diffDays <= 7) return 'warning';

997

return 'success';

998

};

999

1000

// Custom date formatting

1001

const formatCustomDate = (dateValue, format) => {

1002

if (!date.isValid(dateValue)) return 'Invalid date';

1003

return date.format(dateValue, format);

1004

};

1005

1006

// Date validation

1007

const isWeekend = (dateValue) => {

1008

const dayOfWeek = date.getDayOfWeek(dateValue);

1009

return dayOfWeek === 0 || dayOfWeek === 6; // Sunday or Saturday

1010

};

1011

1012

// Business days calculation

1013

const addBusinessDays = (startDate, days) => {

1014

let result = startDate;

1015

let addedDays = 0;

1016

1017

while (addedDays < days) {

1018

result = date.addDays(result, 1);

1019

if (!isWeekend(result)) {

1020

addedDays++;

1021

}

1022

}

1023

1024

return result;

1025

};

1026

</script>

1027

```

1028

1029

### useGoTo

1030

1031

Composable for smooth scrolling navigation and programmatic page navigation.

1032

1033

```typescript { .api }

1034

/**

1035

* Smooth scrolling navigation composable

1036

* @returns GoTo instance for smooth scrolling functionality

1037

*/

1038

function useGoTo(): GoToInstance;

1039

1040

interface GoToInstance {

1041

/** Scroll to target element or position */

1042

goTo: (

1043

target: string | number | Element,

1044

options?: GoToOptions

1045

) => Promise<void>;

1046

}

1047

1048

interface GoToOptions {

1049

/** Container element to scroll within */

1050

container?: string | Element;

1051

/** Scroll duration in milliseconds */

1052

duration?: number;

1053

/** Easing function */

1054

easing?: string | ((t: number) => number);

1055

/** Offset from target position */

1056

offset?: number;

1057

}

1058

```

1059

1060

**Usage Examples:**

1061

1062

```vue

1063

<template>

1064

<div>

1065

<!-- Navigation menu -->

1066

<v-app-bar fixed>

1067

<v-btn @click="goTo.goTo('#section1')">Section 1</v-btn>

1068

<v-btn @click="goTo.goTo('#section2')">Section 2</v-btn>

1069

<v-btn @click="goTo.goTo('#section3')">Section 3</v-btn>

1070

<v-btn @click="scrollToTop">Top</v-btn>

1071

</v-app-bar>

1072

1073

<!-- Page sections -->

1074

<v-main>

1075

<section id="section1" class="section">

1076

<v-container>

1077

<h2>Section 1</h2>

1078

<p>Content for section 1...</p>

1079

<v-btn @click="scrollToNext('#section2')">Next Section</v-btn>

1080

</v-container>

1081

</section>

1082

1083

<section id="section2" class="section">

1084

<v-container>

1085

<h2>Section 2</h2>

1086

<p>Content for section 2...</p>

1087

<v-btn @click="scrollToNext('#section3')">Next Section</v-btn>

1088

</v-container>

1089

</section>

1090

1091

<section id="section3" class="section">

1092

<v-container>

1093

<h2>Section 3</h2>

1094

<p>Content for section 3...</p>

1095

<v-btn @click="scrollToTop">Back to Top</v-btn>

1096

</v-container>

1097

</section>

1098

1099

<!-- Scrollable container -->

1100

<v-card class="ma-4">

1101

<v-card-title>

1102

Scrollable List

1103

<v-spacer />

1104

<v-btn size="small" @click="scrollToListItem(50)">

1105

Go to Item 50

1106

</v-btn>

1107

</v-card-title>

1108

<v-card-text>

1109

<div ref="scrollContainer" class="scroll-container">

1110

<div

1111

v-for="n in 100"

1112

:key="n"

1113

:id="`item-${n}`"

1114

class="list-item"

1115

:class="{ highlighted: n === 50 }"

1116

>

1117

Item {{ n }}

1118

</div>

1119

</div>

1120

</v-card-text>

1121

</v-card>

1122

1123

<!-- Smooth scroll with custom options -->

1124

<v-card class="ma-4">

1125

<v-card-title>Custom Scroll Options</v-card-title>

1126

<v-card-text>

1127

<v-btn-group>

1128

<v-btn @click="customScroll('fast')">Fast</v-btn>

1129

<v-btn @click="customScroll('slow')">Slow</v-btn>

1130

<v-btn @click="customScroll('bounce')">Bounce</v-btn>

1131

</v-btn-group>

1132

</v-card-text>

1133

</v-card>

1134

</v-main>

1135

1136

<!-- Floating action button for scroll to top -->

1137

<v-fab

1138

v-show="showScrollButton"

1139

location="bottom right"

1140

icon="mdi-chevron-up"

1141

@click="scrollToTop"

1142

/>

1143

</div>

1144

</template>

1145

1146

<script setup>

1147

const goTo = useGoTo();

1148

1149

const scrollContainer = ref();

1150

const showScrollButton = ref(false);

1151

1152

const scrollToTop = async () => {

1153

await goTo.goTo(0, {

1154

duration: 800,

1155

easing: 'easeInOutCubic'

1156

});

1157

};

1158

1159

const scrollToNext = async (target) => {

1160

await goTo.goTo(target, {

1161

duration: 600,

1162

offset: -64, // Account for app bar height

1163

easing: 'easeOutQuart'

1164

});

1165

};

1166

1167

const scrollToListItem = async (itemNumber) => {

1168

await goTo.goTo(`#item-${itemNumber}`, {

1169

container: scrollContainer.value,

1170

duration: 400,

1171

easing: 'easeInOutQuad'

1172

});

1173

};

1174

1175

const customScroll = async (type) => {

1176

const target = '#section2';

1177

1178

switch (type) {

1179

case 'fast':

1180

await goTo.goTo(target, {

1181

duration: 200,

1182

easing: 'linear'

1183

});

1184

break;

1185

case 'slow':

1186

await goTo.goTo(target, {

1187

duration: 2000,

1188

easing: 'easeInOutSine'

1189

});

1190

break;

1191

case 'bounce':

1192

await goTo.goTo(target, {

1193

duration: 1000,

1194

easing: (t) => {

1195

// Custom bounce easing

1196

return t < 0.5

1197

? 2 * t * t

1198

: -1 + (4 - 2 * t) * t;

1199

}

1200

});

1201

break;

1202

}

1203

};

1204

1205

// Track scroll position for showing scroll button

1206

const handleScroll = () => {

1207

showScrollButton.value = window.scrollY > 300;

1208

};

1209

1210

// Programmatic navigation with animations

1211

const navigateWithAnimation = async (route) => {

1212

// Scroll to top before navigation

1213

await goTo.goTo(0, { duration: 300 });

1214

1215

// Navigate to new route

1216

await router.push(route);

1217

};

1218

1219

onMounted(() => {

1220

window.addEventListener('scroll', handleScroll);

1221

});

1222

1223

onUnmounted(() => {

1224

window.removeEventListener('scroll', handleScroll);

1225

});

1226

</script>

1227

1228

<style>

1229

.section {

1230

min-height: 100vh;

1231

padding: 80px 0;

1232

}

1233

1234

.section:nth-child(odd) {

1235

background-color: #f5f5f5;

1236

}

1237

1238

.scroll-container {

1239

height: 300px;

1240

overflow-y: auto;

1241

}

1242

1243

.list-item {

1244

padding: 16px;

1245

border-bottom: 1px solid #e0e0e0;

1246

transition: background-color 0.2s;

1247

}

1248

1249

.list-item.highlighted {

1250

background-color: #e3f2fd;

1251

}

1252

1253

.list-item:hover {

1254

background-color: #f0f0f0;

1255

}

1256

</style>

1257

```

1258

1259

### useLayout

1260

1261

Composable for managing layout dimensions, spacing, and responsive layout calculations.

1262

1263

```typescript { .api }

1264

/**

1265

* Layout management composable

1266

* @returns Layout instance for layout dimensions and calculations

1267

*/

1268

function useLayout(): LayoutInstance;

1269

1270

interface LayoutInstance {

1271

/** Layout dimensions */

1272

dimensions: Ref<LayoutDimensions>;

1273

/** Get layout item by name */

1274

getLayoutItem: (name: string) => LayoutItem | undefined;

1275

/** Register layout item */

1276

register: (name: string, item: LayoutItem) => void;

1277

/** Unregister layout item */

1278

unregister: (name: string) => void;

1279

/** Main content area */

1280

mainStyles: ComputedRef<Record<string, string>>;

1281

}

1282

1283

interface LayoutDimensions {

1284

/** Total layout width */

1285

width: number;

1286

/** Total layout height */

1287

height: number;

1288

/** Available content width */

1289

contentWidth: number;

1290

/** Available content height */

1291

contentHeight: number;

1292

}

1293

1294

interface LayoutItem {

1295

/** Layout item ID */

1296

id: string;

1297

/** Item size */

1298

size: number;

1299

/** Item position */

1300

position: 'top' | 'bottom' | 'left' | 'right';

1301

/** Whether item is visible */

1302

visible: boolean;

1303

}

1304

```

1305

1306

**Usage Examples:**

1307

1308

```vue

1309

<template>

1310

<v-app>

1311

<!-- App bar -->

1312

<v-app-bar

1313

ref="appBar"

1314

color="primary"

1315

height="64"

1316

fixed

1317

>

1318

<v-app-bar-nav-icon @click="drawer = !drawer" />

1319

<v-toolbar-title>Layout Demo</v-toolbar-title>

1320

<v-spacer />

1321

<v-btn icon @click="toggleBottomNav">

1322

<v-icon>mdi-view-dashboard</v-icon>

1323

</v-btn>

1324

</v-app-bar>

1325

1326

<!-- Navigation drawer -->

1327

<v-navigation-drawer

1328

v-model="drawer"

1329

ref="navigationDrawer"

1330

:width="drawerWidth"

1331

temporary

1332

>

1333

<v-list>

1334

<v-list-item title="Home" prepend-icon="mdi-home" />

1335

<v-list-item title="About" prepend-icon="mdi-information" />

1336

<v-list-item title="Contact" prepend-icon="mdi-phone" />

1337

</v-list>

1338

</v-navigation-drawer>

1339

1340

<!-- Main content -->

1341

<v-main :style="layout.mainStyles.value">

1342

<v-container>

1343

<!-- Layout information -->

1344

<v-card class="mb-4">

1345

<v-card-title>Layout Information</v-card-title>

1346

<v-card-text>

1347

<v-row>

1348

<v-col cols="6">

1349

<strong>Total Size:</strong><br>

1350

{{ layout.dimensions.value.width }} x {{ layout.dimensions.value.height }}

1351

</v-col>

1352

<v-col cols="6">

1353

<strong>Content Size:</strong><br>

1354

{{ layout.dimensions.value.contentWidth }} x {{ layout.dimensions.value.contentHeight }}

1355

</v-col>

1356

</v-row>

1357

</v-card-text>

1358

</v-card>

1359

1360

<!-- Responsive grid based on layout -->

1361

<v-row>

1362

<v-col

1363

v-for="item in gridItems"

1364

:key="item.id"

1365

:cols="getColumnSize()"

1366

>

1367

<v-card height="200">

1368

<v-card-title>{{ item.title }}</v-card-title>

1369

<v-card-text>{{ item.description }}</v-card-text>

1370

</v-card>

1371

</v-col>

1372

</v-row>

1373

1374

<!-- Adaptive content based on available space -->

1375

<v-card class="mt-4">

1376

<v-card-title>Adaptive Content</v-card-title>

1377

<v-card-text>

1378

<div :class="getContentLayoutClass()">

1379

<div class="content-section">

1380

<h3>Primary Content</h3>

1381

<p>This content adapts based on available layout space.</p>

1382

</div>

1383

<div class="sidebar-section" v-if="shouldShowSidebar">

1384

<h4>Sidebar</h4>

1385

<p>Additional information shown when space allows.</p>

1386

</div>

1387

</div>

1388

</v-card-text>

1389

</v-card>

1390

</v-container>

1391

</v-main>

1392

1393

<!-- Bottom navigation (conditional) -->

1394

<v-bottom-navigation

1395

v-if="showBottomNav"

1396

ref="bottomNavigation"

1397

v-model="bottomNavValue"

1398

height="64"

1399

>

1400

<v-btn value="home">

1401

<v-icon>mdi-home</v-icon>

1402

<span>Home</span>

1403

</v-btn>

1404

<v-btn value="search">

1405

<v-icon>mdi-magnify</v-icon>

1406

<span>Search</span>

1407

</v-btn>

1408

<v-btn value="profile">

1409

<v-icon>mdi-account</v-icon>

1410

<span>Profile</span>

1411

</v-btn>

1412

</v-bottom-navigation>

1413

1414

<!-- System bar (conditional) -->

1415

<v-system-bar

1416

v-if="showSystemBar"

1417

ref="systemBar"

1418

height="24"

1419

color="primary"

1420

dark

1421

>

1422

<v-icon size="small">mdi-wifi</v-icon>

1423

<v-icon size="small">mdi-signal</v-icon>

1424

<v-icon size="small">mdi-battery</v-icon>

1425

<span class="text-caption ml-2">12:34 PM</span>

1426

</v-system-bar>

1427

</v-app>

1428

</template>

1429

1430

<script setup>

1431

const layout = useLayout();

1432

const display = useDisplay();

1433

1434

const drawer = ref(false);

1435

const drawerWidth = ref(280);

1436

const showBottomNav = ref(false);

1437

const showSystemBar = ref(false);

1438

const bottomNavValue = ref('home');

1439

1440

const gridItems = ref([

1441

{ id: 1, title: 'Card 1', description: 'First card content' },

1442

{ id: 2, title: 'Card 2', description: 'Second card content' },

1443

{ id: 3, title: 'Card 3', description: 'Third card content' },

1444

{ id: 4, title: 'Card 4', description: 'Fourth card content' },

1445

]);

1446

1447

// Reactive column sizing based on layout

1448

const getColumnSize = () => {

1449

const contentWidth = layout.dimensions.value.contentWidth;

1450

1451

if (contentWidth < 600) return 12;

1452

if (contentWidth < 900) return 6;

1453

if (contentWidth < 1200) return 4;

1454

return 3;

1455

};

1456

1457

// Adaptive layout classes

1458

const getContentLayoutClass = () => {

1459

const contentWidth = layout.dimensions.value.contentWidth;

1460

return contentWidth > 800 ? 'd-flex' : 'd-block';

1461

};

1462

1463

// Conditional sidebar display

1464

const shouldShowSidebar = computed(() => {

1465

return layout.dimensions.value.contentWidth > 800;

1466

});

1467

1468

// Layout item registration

1469

const registerLayoutItems = () => {

1470

// These would typically be registered by the components themselves

1471

layout.register('appBar', {

1472

id: 'app-bar',

1473

size: 64,

1474

position: 'top',

1475

visible: true

1476

});

1477

1478

if (showBottomNav.value) {

1479

layout.register('bottomNav', {

1480

id: 'bottom-navigation',

1481

size: 64,

1482

position: 'bottom',

1483

visible: true

1484

});

1485

}

1486

1487

if (showSystemBar.value) {

1488

layout.register('systemBar', {

1489

id: 'system-bar',

1490

size: 24,

1491

position: 'top',

1492

visible: true

1493

});

1494

}

1495

};

1496

1497

const toggleBottomNav = () => {

1498

showBottomNav.value = !showBottomNav.value;

1499

1500

if (showBottomNav.value) {

1501

layout.register('bottomNav', {

1502

id: 'bottom-navigation',

1503

size: 64,

1504

position: 'bottom',

1505

visible: true

1506

});

1507

} else {

1508

layout.unregister('bottomNav');

1509

}

1510

};

1511

1512

// Watch layout changes

1513

watch(() => layout.dimensions.value, (newDimensions, oldDimensions) => {

1514

console.log('Layout changed:', {

1515

from: oldDimensions,

1516

to: newDimensions

1517

});

1518

}, { deep: true });

1519

1520

// Responsive drawer width

1521

watchEffect(() => {

1522

if (display.xs.value) {

1523

drawerWidth.value = Math.min(280, layout.dimensions.value.width * 0.8);

1524

} else {

1525

drawerWidth.value = 280;

1526

}

1527

});

1528

1529

onMounted(() => {

1530

registerLayoutItems();

1531

});

1532

</script>

1533

1534

<style>

1535

.content-section {

1536

flex: 1;

1537

margin-right: 24px;

1538

}

1539

1540

.sidebar-section {

1541

width: 200px;

1542

background: #f5f5f5;

1543

padding: 16px;

1544

border-radius: 4px;

1545

}

1546

1547

@media (max-width: 800px) {

1548

.content-section {

1549

margin-right: 0;

1550

margin-bottom: 16px;

1551

}

1552

}

1553

</style>

1554

```

1555

1556

### useHotkey

1557

1558

Composable for registering and managing keyboard shortcuts throughout the application.

1559

1560

```typescript { .api }

1561

/**

1562

* Keyboard shortcut management composable

1563

* @returns Hotkey instance for managing keyboard shortcuts

1564

*/

1565

function useHotkey(): HotkeyInstance;

1566

1567

interface HotkeyInstance {

1568

/** Register a hotkey */

1569

register: (

1570

keys: string | string[],

1571

handler: (event: KeyboardEvent) => void,

1572

options?: HotkeyOptions

1573

) => () => void;

1574

/** Unregister a hotkey */

1575

unregister: (keys: string | string[]) => void;

1576

/** Get all registered hotkeys */

1577

getHotkeys: () => HotkeyRegistration[];

1578

/** Enable/disable all hotkeys */

1579

enabled: Ref<boolean>;

1580

}

1581

1582

interface HotkeyOptions {

1583

/** Scope for the hotkey */

1584

scope?: string;

1585

/** Element to bind the hotkey to */

1586

element?: Element | string;

1587

/** Prevent default behavior */

1588

preventDefault?: boolean;

1589

/** Stop event propagation */

1590

stopPropagation?: boolean;

1591

/** Description for the hotkey */

1592

description?: string;

1593

}

1594

1595

interface HotkeyRegistration {

1596

keys: string[];

1597

handler: (event: KeyboardEvent) => void;

1598

options: HotkeyOptions;

1599

scope: string;

1600

}

1601

```

1602

1603

**Usage Examples:**

1604

1605

```vue

1606

<template>

1607

<div>

1608

<!-- Hotkey status display -->

1609

<v-card class="mb-4">

1610

<v-card-title>

1611

Keyboard Shortcuts

1612

<v-spacer />

1613

<v-switch

1614

v-model="hotkey.enabled.value"

1615

label="Enable Shortcuts"

1616

inset

1617

/>

1618

</v-card-title>

1619

<v-card-text>

1620

<v-list density="compact">

1621

<v-list-item

1622

v-for="shortcut in registeredHotkeys"

1623

:key="shortcut.keys.join('+')"

1624

>

1625

<v-list-item-title>

1626

<v-chip size="small" variant="outlined">

1627

{{ shortcut.keys.join(' + ') }}

1628

</v-chip>

1629

</v-list-item-title>

1630

<v-list-item-subtitle>{{ shortcut.options.description }}</v-list-item-subtitle>

1631

</v-list-item>

1632

</v-list>

1633

</v-card-text>

1634

</v-card>

1635

1636

<!-- Demo content -->

1637

<v-row>

1638

<v-col cols="12" md="6">

1639

<v-card>

1640

<v-card-title>Text Editor</v-card-title>

1641

<v-card-text>

1642

<v-textarea

1643

ref="textEditor"

1644

v-model="editorContent"

1645

label="Press Ctrl+S to save, Ctrl+Z to undo"

1646

rows="10"

1647

/>

1648

<v-chip-group class="mt-2">

1649

<v-chip v-if="lastAction" color="success">

1650

{{ lastAction }}

1651

</v-chip>

1652

</v-chip-group>

1653

</v-card-text>

1654

</v-card>

1655

</v-col>

1656

1657

<v-col cols="12" md="6">

1658

<v-card>

1659

<v-card-title>Navigation</v-card-title>

1660

<v-card-text>

1661

<p>Use keyboard shortcuts to navigate:</p>

1662

<v-list density="compact">

1663

<v-list-item title="Home" subtitle="Press '1'" />

1664

<v-list-item title="About" subtitle="Press '2'" />

1665

<v-list-item title="Contact" subtitle="Press '3'" />

1666

<v-list-item title="Help" subtitle="Press '?'" />

1667

</v-list>

1668

<v-chip v-if="currentPage" color="primary" class="mt-2">

1669

Current: {{ currentPage }}

1670

</v-chip>

1671

</v-card-text>

1672

</v-card>

1673

</v-col>

1674

</v-row>

1675

1676

<!-- Action buttons with hotkeys -->

1677

<v-card class="mt-4">

1678

<v-card-title>Actions</v-card-title>

1679

<v-card-text>

1680

<v-btn-group>

1681

<v-btn @click="newFile" prepend-icon="mdi-file-plus">

1682

New (Ctrl+N)

1683

</v-btn>

1684

<v-btn @click="openFile" prepend-icon="mdi-folder-open">

1685

Open (Ctrl+O)

1686

</v-btn>

1687

<v-btn @click="saveFile" prepend-icon="mdi-content-save" color="primary">

1688

Save (Ctrl+S)

1689

</v-btn>

1690

</v-btn-group>

1691

</v-card-text>

1692

</v-card>

1693

1694

<!-- Modal with scoped hotkeys -->

1695

<v-dialog v-model="dialogOpen" max-width="500">

1696

<template #activator="{ props }">

1697

<v-btn v-bind="props" class="mt-4">

1698

Open Dialog (Press Esc to close when open)

1699

</v-btn>

1700

</template>

1701

1702

<v-card>

1703

<v-card-title>Modal Dialog</v-card-title>

1704

<v-card-text>

1705

This dialog has scoped hotkeys. Press Esc to close or Enter to confirm.

1706

</v-card-text>

1707

<v-card-actions>

1708

<v-spacer />

1709

<v-btn @click="dialogOpen = false">Cancel</v-btn>

1710

<v-btn color="primary" @click="confirmDialog">Confirm</v-btn>

1711

</v-card-actions>

1712

</v-card>

1713

</v-dialog>

1714

1715

<!-- Help overlay -->

1716

<v-overlay v-model="showHelp" class="d-flex align-center justify-center">

1717

<v-card max-width="600">

1718

<v-card-title>

1719

Keyboard Shortcuts Help

1720

<v-spacer />

1721

<v-btn icon size="small" @click="showHelp = false">

1722

<v-icon>mdi-close</v-icon>

1723

</v-btn>

1724

</v-card-title>

1725

<v-card-text>

1726

<v-simple-table>

1727

<template #default>

1728

<tbody>

1729

<tr v-for="shortcut in allHotkeys" :key="shortcut.keys.join('+')">

1730

<td>

1731

<code>{{ shortcut.keys.join(' + ') }}</code>

1732

</td>

1733

<td>{{ shortcut.options.description }}</td>

1734

</tr>

1735

</tbody>

1736

</template>

1737

</v-simple-table>

1738

</v-card-text>

1739

</v-card>

1740

</v-overlay>

1741

</div>

1742

</template>

1743

1744

<script setup>

1745

const hotkey = useHotkey();

1746

1747

const editorContent = ref('Type something here...');

1748

const lastAction = ref('');

1749

const currentPage = ref('Home');

1750

const dialogOpen = ref(false);

1751

const showHelp = ref(false);

1752

const textEditor = ref();

1753

1754

const registeredHotkeys = computed(() => hotkey.getHotkeys());

1755

const allHotkeys = computed(() => hotkey.getHotkeys());

1756

1757

// File operations

1758

const newFile = () => {

1759

editorContent.value = '';

1760

lastAction.value = 'New file created';

1761

setTimeout(() => lastAction.value = '', 2000);

1762

};

1763

1764

const openFile = () => {

1765

lastAction.value = 'File opened';

1766

setTimeout(() => lastAction.value = '', 2000);

1767

};

1768

1769

const saveFile = () => {

1770

lastAction.value = 'File saved';

1771

setTimeout(() => lastAction.value = '', 2000);

1772

};

1773

1774

const confirmDialog = () => {

1775

lastAction.value = 'Dialog confirmed';

1776

dialogOpen.value = false;

1777

setTimeout(() => lastAction.value = '', 2000);

1778

};

1779

1780

// Register global hotkeys

1781

onMounted(() => {

1782

// File operations

1783

hotkey.register(['ctrl+n', 'cmd+n'], newFile, {

1784

description: 'Create new file',

1785

preventDefault: true

1786

});

1787

1788

hotkey.register(['ctrl+o', 'cmd+o'], openFile, {

1789

description: 'Open file',

1790

preventDefault: true

1791

});

1792

1793

hotkey.register(['ctrl+s', 'cmd+s'], saveFile, {

1794

description: 'Save file',

1795

preventDefault: true

1796

});

1797

1798

// Navigation shortcuts

1799

hotkey.register('1', () => {

1800

currentPage.value = 'Home';

1801

}, {

1802

description: 'Go to Home page'

1803

});

1804

1805

hotkey.register('2', () => {

1806

currentPage.value = 'About';

1807

}, {

1808

description: 'Go to About page'

1809

});

1810

1811

hotkey.register('3', () => {

1812

currentPage.value = 'Contact';

1813

}, {

1814

description: 'Go to Contact page'

1815

});

1816

1817

// Help

1818

hotkey.register(['?', 'F1'], () => {

1819

showHelp.value = true;

1820

}, {

1821

description: 'Show help'

1822

});

1823

1824

// Focus text editor

1825

hotkey.register('ctrl+e', () => {

1826

textEditor.value?.focus();

1827

}, {

1828

description: 'Focus text editor',

1829

preventDefault: true

1830

});

1831

});

1832

1833

// Register scoped hotkeys for dialog

1834

watch(dialogOpen, (isOpen) => {

1835

if (isOpen) {

1836

// Register dialog-specific hotkeys

1837

const unregisterEsc = hotkey.register('escape', () => {

1838

dialogOpen.value = false;

1839

}, {

1840

scope: 'dialog',

1841

description: 'Close dialog'

1842

});

1843

1844

const unregisterEnter = hotkey.register('enter', confirmDialog, {

1845

scope: 'dialog',

1846

description: 'Confirm dialog',

1847

preventDefault: true

1848

});

1849

1850

// Cleanup when dialog closes

1851

watch(dialogOpen, (stillOpen) => {

1852

if (!stillOpen) {

1853

unregisterEsc();

1854

unregisterEnter();

1855

}

1856

}, { once: true });

1857

}

1858

});

1859

1860

// Global escape for help

1861

watch(showHelp, (isOpen) => {

1862

if (isOpen) {

1863

const unregisterEsc = hotkey.register('escape', () => {

1864

showHelp.value = false;

1865

}, {

1866

scope: 'help',

1867

description: 'Close help'

1868

});

1869

1870

watch(showHelp, (stillOpen) => {

1871

if (!stillOpen) {

1872

unregisterEsc();

1873

}

1874

}, { once: true });

1875

}

1876

});

1877

</script>

1878

1879

<style>

1880

code {

1881

background: rgba(0,0,0,0.1);

1882

padding: 2px 6px;

1883

border-radius: 4px;

1884

font-family: 'Roboto Mono', monospace;

1885

font-size: 0.875em;

1886

}

1887

1888

.v-simple-table tbody tr td {

1889

padding: 8px 16px;

1890

}

1891

</style>

1892

```

1893

1894

## Types

1895

1896

```typescript { .api }

1897

// Composable return types

1898

type ThemeInstance = {

1899

current: Ref<ThemeDefinition>;

1900

themes: Ref<Record<string, ThemeDefinition>>;

1901

isDisabled: Ref<boolean>;

1902

name: Ref<string>;

1903

computedThemes: ComputedRef<Record<string, ThemeDefinition>>;

1904

themeClasses: Ref<Record<string, boolean>>;

1905

styles: ComputedRef<string>;

1906

};

1907

1908

type DisplayInstance = {

1909

xs: Ref<boolean>;

1910

sm: Ref<boolean>;

1911

md: Ref<boolean>;

1912

lg: Ref<boolean>;

1913

xl: Ref<boolean>;

1914

xxl: Ref<boolean>;

1915

smAndUp: Ref<boolean>;

1916

mdAndUp: Ref<boolean>;

1917

lgAndUp: Ref<boolean>;

1918

xlAndUp: Ref<boolean>;

1919

smAndDown: Ref<boolean>;

1920

mdAndDown: Ref<boolean>;

1921

lgAndDown: Ref<boolean>;

1922

xlAndDown: Ref<boolean>;

1923

name: Ref<string>;

1924

height: Ref<number>;

1925

width: Ref<number>;

1926

mobile: Ref<boolean>;

1927

mobileBreakpoint: Ref<number | string>;

1928

thresholds: Ref<DisplayThresholds>;

1929

platform: Ref<'android' | 'ios' | 'desktop'>;

1930

touch: Ref<boolean>;

1931

ssr: boolean;

1932

};

1933

1934

type LocaleInstance = {

1935

current: Ref<string>;

1936

fallback: Ref<string>;

1937

locales: Ref<Record<string, LocaleMessages>>;

1938

messages: Ref<Record<string, LocaleMessages>>;

1939

t: (key: string, ...params: unknown[]) => string;

1940

n: (value: number) => string;

1941

provide: Record<string, any>;

1942

isRtl: Ref<boolean>;

1943

};

1944

1945

type RtlInstance = {

1946

isRtl: Ref<boolean>;

1947

rtl: Ref<boolean>;

1948

rtlClasses: Ref<string>;

1949

};

1950

1951

type DefaultsInstance = {

1952

getDefaults(): Record<string, any>;

1953

reset(): void;

1954

global: Record<string, any>;

1955

};

1956

1957

type DateInstance = {

1958

adapter: DateAdapter;

1959

locale: Ref<string>;

1960

format: (date: any, formatString: string) => string;

1961

parse: (value: string, format: string) => any;

1962

addDays: (date: any, days: number) => any;

1963

addMonths: (date: any, months: number) => any;

1964

addYears: (date: any, years: number) => any;

1965

startOfDay: (date: any) => any;

1966

endOfDay: (date: any) => any;

1967

startOfMonth: (date: any) => any;

1968

endOfMonth: (date: any) => any;

1969

isEqual: (date1: any, date2: any) => boolean;

1970

isBefore: (date1: any, date2: any) => boolean;

1971

isAfter: (date1: any, date2: any) => boolean;

1972

isSameDay: (date1: any, date2: any) => boolean;

1973

isSameMonth: (date1: any, date2: any) => boolean;

1974

getYear: (date: any) => number;

1975

getMonth: (date: any) => number;

1976

getDate: (date: any) => number;

1977

getDayOfWeek: (date: any) => number;

1978

isValid: (date: any) => boolean;

1979

};

1980

1981

type GoToInstance = {

1982

goTo: (

1983

target: string | number | Element,

1984

options?: GoToOptions

1985

) => Promise<void>;

1986

};

1987

1988

type LayoutInstance = {

1989

dimensions: Ref<LayoutDimensions>;

1990

getLayoutItem: (name: string) => LayoutItem | undefined;

1991

register: (name: string, item: LayoutItem) => void;

1992

unregister: (name: string) => void;

1993

mainStyles: ComputedRef<Record<string, string>>;

1994

};

1995

1996

type HotkeyInstance = {

1997

register: (

1998

keys: string | string[],

1999

handler: (event: KeyboardEvent) => void,

2000

options?: HotkeyOptions

2001

) => () => void;

2002

unregister: (keys: string | string[]) => void;

2003

getHotkeys: () => HotkeyRegistration[];

2004

enabled: Ref<boolean>;

2005

};

2006

2007

// Configuration types

2008

type EasingFunction = (t: number) => number;

2009

type Platform = 'android' | 'ios' | 'desktop';

2010

type BreakpointName = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';