Skip to content

Commit 5ce5339

Browse files
feat: useControllableState
1 parent 85ac373 commit 5ce5339

File tree

5 files changed

+96
-11
lines changed

5 files changed

+96
-11
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { isDef } from '@vueuse/core'
2+
import { type UnwrapRef, computed, nextTick, shallowRef, watch, type Ref } from 'vue'
3+
4+
5+
/**
6+
* Shorthand for v-model binding, props + emit -> ref
7+
*
8+
* @see https://vueuse.org/useVModel
9+
* @param props
10+
* @param key (default 'value' in Vue 2 and 'modelValue' in Vue 3)
11+
* @param emit
12+
*/
13+
export function useControllableState<P extends object, K extends keyof P, Name extends string, V = Exclude<P[K], undefined>>(
14+
props: P,
15+
emit: (name: Name, ...args: any[]) => void,
16+
key: K,
17+
defaultValue?: V | undefined,
18+
eventName?: string,
19+
): Ref<V> {
20+
const event: Name = (eventName || `update:${key.toString()}`) as Name
21+
22+
const getValue = (): V => (isDef(props[key])
23+
? props[key]
24+
: defaultValue) as V
25+
26+
if (isDef(props[key])) {
27+
return computed<V>({
28+
get() {
29+
return getValue()
30+
},
31+
set(value) {
32+
emit(event, value)
33+
},
34+
})
35+
36+
}
37+
else {
38+
const proxy = shallowRef<V>(getValue())
39+
let isUpdating = false
40+
41+
watch(
42+
() => props[key!],
43+
(v) => {
44+
if (!isUpdating) {
45+
isUpdating = true
46+
; (proxy as any).value = v as UnwrapRef<P[K]>
47+
nextTick(() => isUpdating = false)
48+
}
49+
},
50+
)
51+
52+
watch(
53+
proxy,
54+
(v) => {
55+
if (!isUpdating && (v !== props[key!]))
56+
emit(event, v)
57+
},
58+
)
59+
60+
return proxy
61+
}
62+
}

packages/vue-primitives/src/toggle-group/ToggleGroup.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import { Primitive } from '../primitive/index.ts'
3-
import { useControllableState } from '../utils/useControllableState.ts'
3+
import { useControllableState } from '../hooks/useControllableState.ts'
44
import { composeEventHandlers } from '../utils/composeEventHandlers.ts'
55
import type { ToggleEmits, ToggleProps } from './ToggleGroup.ts'
66
@@ -15,10 +15,7 @@ const props = withDefaults(defineProps<ToggleProps>(), {
1515
1616
const emit = defineEmits<ToggleEmits>()
1717
18-
const pressed = useControllableState(props, 'pressed', emit, {
19-
defaultValue: props.defaultPressed,
20-
passive: (props.pressed === undefined) as false,
21-
})
18+
const pressed = useControllableState(props, emit, 'pressed', props.defaultPressed)
2219
2320
const onClick = composeEventHandlers((e: Event) => {
2421
emit('click', e)

packages/vue-primitives/src/toggle/Toggle.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import type { ToggleEmits, ToggleProps } from './Toggle.ts'
33
import { Primitive } from '~/primitive/index.ts'
4-
import { useControllableState } from '~/utils/useControllableState.ts'
4+
import { useControllableState } from '~/hooks/useControllableState.ts'
55
import { composeEventHandlers } from '~/utils/composeEventHandlers.ts'
66
77
defineOptions({
@@ -15,10 +15,7 @@ const props = withDefaults(defineProps<ToggleProps>(), {
1515
1616
const emit = defineEmits<ToggleEmits>()
1717
18-
const pressed = useControllableState(props, 'pressed', emit, {
19-
defaultValue: props.defaultPressed,
20-
passive: (props.pressed === undefined) as false,
21-
})
18+
const pressed = useControllableState(props, emit, 'pressed', props.defaultPressed)
2219
2320
const onClick = composeEventHandlers((e: Event) => {
2421
emit('click', e)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { type InjectionKey, inject, provide } from 'vue'
2+
3+
/**
4+
* Create global state that can be injected into components.
5+
*
6+
* @see https://vueuse.org/createInjectionState
7+
*
8+
*/
9+
export function createContext<T>(
10+
contextName: string,
11+
defaultValue?: T,
12+
): readonly [useProvidingState: (state: T) => void, useContext: () => T] {
13+
const key: string | InjectionKey<T> = Symbol(contextName)
14+
15+
const useProvideContext = (state: T) => {
16+
provide(key, state)
17+
}
18+
19+
const useContext = (consumerName?: string) => {
20+
const state = inject(key, defaultValue)
21+
22+
if (!state) {
23+
throw new Error(`\`${consumerName}\` must be used within \`${contextName}\``)
24+
}
25+
26+
return state
27+
}
28+
29+
return [useProvideContext, useContext]
30+
}

packages/vue-primitives/src/utils/useControllableState.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)