1+ import { ref , computed , watch , type Ref , type ComputedRef } from 'vue' ;
2+
3+ export function useControlledState < T , C = T > (
4+ value : Ref < T | undefined > ,
5+ defaultValue : T ,
6+ onChange ?: ( v : C , ...args : any [ ] ) => void
7+ ) : [ ComputedRef < T > , ( value : T | ( ( prev : T ) => T ) , ...args : any [ ] ) => void ] {
8+ const isControlled = computed ( ( ) => value . value !== undefined ) ;
9+ const initialValue = value . value ?? defaultValue ;
10+ const internalState = ref ( initialValue ) as Ref < T > ;
11+ const wasControlled = ref ( isControlled . value ) ;
12+
13+ const currentValue = computed ( ( ) =>
14+ isControlled . value ? value . value ! : internalState . value
15+ ) ;
16+
17+ watch ( isControlled , ( newVal , oldVal ) => {
18+ if ( newVal !== oldVal ) {
19+ console . warn (
20+ `WARN: Component changed from ${ wasControlled . value ? 'controlled' : 'uncontrolled' } ` +
21+ `to ${ newVal ? 'controlled' : 'uncontrolled' } `
22+ ) ;
23+ wasControlled . value = newVal ;
24+ }
25+ } ) ;
26+
27+ function setValue ( newValue : T | ( ( prev : T ) => T ) , ...args : any [ ] ) {
28+ if ( typeof newValue === 'function' ) {
29+ console . warn (
30+ 'Function callbacks are not supported. See: https://github.com/adobe/react-spectrum/issues/2320'
31+ ) ;
32+ const prev = currentValue . value ;
33+ const updatedValue = ( newValue as ( prev : T ) => T ) ( prev ) ;
34+
35+ if ( ! isControlled . value ) {
36+ internalState . value = updatedValue ;
37+ }
38+
39+ if ( ! Object . is ( prev , updatedValue ) ) {
40+ onChange ?.( updatedValue as unknown as C , ...args ) ;
41+ }
42+ } else {
43+ const shouldUpdate = ! Object . is ( currentValue . value , newValue ) ;
44+
45+ if ( ! isControlled . value ) {
46+ internalState . value = newValue ;
47+ }
48+
49+ if ( shouldUpdate ) {
50+ onChange ?.( newValue as unknown as C , ...args ) ;
51+ }
52+ }
53+ }
54+
55+ return [ currentValue , setValue ] ;
56+ }
0 commit comments