11import { onMounted , ref , watch , computed , onBeforeUnmount } from "vue" ;
22import { isSSR } from "../utils/helper" ;
33
4- import type { Ref } from "vue" ;
4+ type ResponsiveConfig < T > = Partial < Record < BreakpointName , T > > ;
55
66enum BreakpointName {
77 Xs = "xs" ,
@@ -21,12 +21,16 @@ enum BreakpointWidth {
2121 "2xl" = 1536 ,
2222}
2323
24- export function useBreakpoint ( ) {
25- let animationId : number | undefined ;
24+ let isInitialized = false ;
25+ let animationId : number | undefined ;
26+
27+ const windowWidth = ref ( 0 ) ;
28+ const currentBreakpoint = ref ( BreakpointName . Xs ) ;
29+ const BREAKPOINT_KEYS = Object . keys ( BreakpointName ) as ( keyof typeof BreakpointName ) [ ] ;
2630
27- const windowWidth = ref ( 0 ) ;
28- const currentBreakpoint : Ref < BreakpointName > = ref ( BreakpointName . Xs ) ;
31+ watch ( windowWidth , setBreakpoint , { immediate : true } ) ;
2932
33+ export function useBreakpoint ( ) {
3034 const isPhone = computed ( ( ) => {
3135 return currentBreakpoint . value === BreakpointName . Xs ;
3236 } ) ;
@@ -63,50 +67,14 @@ export function useBreakpoint() {
6367 return isDesktop . value || isLargeDesktop . value ;
6468 } ) ;
6569
66- watch ( windowWidth , setBreakpoint , { immediate : true } ) ;
67-
6870 onMounted ( ( ) => {
69- if ( isSSR ) return ;
70-
71- windowWidth . value = window . innerWidth ;
72-
73- window . addEventListener ( "resize" , resizeListener , { passive : true } ) ;
71+ initBreakpointListener ( ) ;
7472 } ) ;
7573
7674 onBeforeUnmount ( ( ) => {
77- if ( isSSR ) return ;
78-
7975 window . removeEventListener ( "resize" , resizeListener ) ;
8076 } ) ;
8177
82- function resizeListener ( ) {
83- if ( isSSR ) return ;
84-
85- if ( animationId ) {
86- window . cancelAnimationFrame ( animationId ) ;
87- }
88-
89- animationId = window . requestAnimationFrame ( ( ) => {
90- windowWidth . value = window . innerWidth ;
91- } ) ;
92- }
93-
94- function setBreakpoint ( newWindowWidth : number ) {
95- if ( newWindowWidth === undefined ) return ;
96-
97- const breakpoints = [
98- { width : BreakpointWidth [ "2xl" ] , name : BreakpointName [ "2xl" ] } ,
99- { width : BreakpointWidth . Xl , name : BreakpointName . Xl } ,
100- { width : BreakpointWidth . Lg , name : BreakpointName . Lg } ,
101- { width : BreakpointWidth . Md , name : BreakpointName . Md } ,
102- { width : BreakpointWidth . Sm , name : BreakpointName . Sm } ,
103- ] ;
104-
105- currentBreakpoint . value =
106- breakpoints . find ( ( breakpoint ) => newWindowWidth >= breakpoint . width ) ?. name ||
107- BreakpointName . Xs ;
108- }
109-
11078 return {
11179 isPhone,
11280 isLargePhone,
@@ -120,3 +88,90 @@ export function useBreakpoint() {
12088 breakpoint : currentBreakpoint ,
12189 } ;
12290}
91+
92+ /**
93+ * Shorthand function that can be used directly in templates.
94+ * Returns the appropriate value based on the current breakpoint.
95+ * Vue will track the reactive dependency and re-render when the breakpoint changes.
96+ *
97+ * @example
98+ * ```vue
99+ * <template>
100+ * <UButton :size="r({ sm: 'sm', md: 'md' })">Click me</UButton>
101+ * </template>
102+ *
103+ * <script setup>
104+ * import { r } from "vueless";
105+ * </script>
106+ * ```
107+ */
108+ export function r < T > ( config : ResponsiveConfig < T > ) : T {
109+ initBreakpointListener ( ) ;
110+
111+ const definedKeys = BREAKPOINT_KEYS . filter ( ( key ) => BreakpointName [ key ] in config ) ;
112+
113+ if ( ! definedKeys . length ) {
114+ return undefined as T ;
115+ }
116+
117+ const currentIndex = BREAKPOINT_KEYS . findIndex (
118+ ( key ) => BreakpointName [ key ] === currentBreakpoint . value ,
119+ ) ;
120+ const smallestDefinedIndex = BREAKPOINT_KEYS . indexOf ( definedKeys [ 0 ] ) ;
121+ const largestDefinedIndex = BREAKPOINT_KEYS . indexOf ( definedKeys [ definedKeys . length - 1 ] ) ;
122+
123+ if ( currentIndex <= smallestDefinedIndex ) {
124+ return config [ BreakpointName [ definedKeys [ 0 ] ] ] as T ;
125+ }
126+
127+ if ( currentIndex >= largestDefinedIndex ) {
128+ return config [ BreakpointName [ definedKeys [ definedKeys . length - 1 ] ] ] as T ;
129+ }
130+
131+ for ( let i = currentIndex ; i >= 0 ; i -- ) {
132+ const bp = BreakpointName [ BREAKPOINT_KEYS [ i ] ] ;
133+
134+ if ( bp in config ) {
135+ return config [ bp ] as T ;
136+ }
137+ }
138+
139+ return config [ BreakpointName [ definedKeys [ 0 ] ] ] as T ;
140+ }
141+
142+ function setBreakpoint ( newWindowWidth : number ) {
143+ if ( newWindowWidth === undefined ) return ;
144+
145+ for ( let i = BREAKPOINT_KEYS . length - 1 ; i >= 0 ; i -- ) {
146+ const key = BREAKPOINT_KEYS [ i ] ;
147+
148+ if ( newWindowWidth >= BreakpointWidth [ key ] ) {
149+ currentBreakpoint . value = BreakpointName [ key ] ;
150+
151+ return ;
152+ }
153+ }
154+
155+ currentBreakpoint . value = BreakpointName . Xs ;
156+ }
157+
158+ function resizeListener ( ) {
159+ if ( isSSR ) return ;
160+
161+ if ( animationId ) {
162+ window . cancelAnimationFrame ( animationId ) ;
163+ }
164+
165+ animationId = window . requestAnimationFrame ( ( ) => {
166+ windowWidth . value = window . innerWidth ;
167+ } ) ;
168+ }
169+
170+ function initBreakpointListener ( ) {
171+ if ( isInitialized || isSSR ) return ;
172+
173+ isInitialized = true ;
174+ windowWidth . value = window . innerWidth ;
175+
176+ window . addEventListener ( "resize" , resizeListener , { passive : true } ) ;
177+ }
0 commit comments