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';