Skip to content

Commit 79c7db4

Browse files
fix: checkbox (#113)
* fix: use-composable * feat: utils toValue * feat: useUnrefToRef * feat: useRef * fix: useREf * [autofix.ci] apply automated fixes
1 parent b5caab9 commit 79c7db4

File tree

13 files changed

+215
-123
lines changed

13 files changed

+215
-123
lines changed

packages/components/checkbox/src/checkbox.ts

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { createProvideScope } from '@oku-ui/provide'
22
import type { PropType, Ref } from 'vue'
3-
import { Transition, computed, defineComponent, h, onMounted, ref, watchEffect } from 'vue'
3+
import { Transition, defineComponent, h, onMounted, ref, toRefs, watch, watchEffect } from 'vue'
44

55
import { composeEventHandlers } from '@oku-ui/utils'
6-
import { useControllableRef, usePrevious, useSize } from '@oku-ui/use-composable'
6+
import { useControllableRef, usePrevious, useRef, useSize } from '@oku-ui/use-composable'
77
import { Primitive } from '@oku-ui/primitive'
88

9-
// import { useComposedRefs } from '@oku-ui/compose-refs'
109
import type { ElementType, MergeProps, PrimitiveProps, RefElement } from '@oku-ui/primitive'
1110

1211
import type { Scope } from '@oku-ui/provide'
@@ -121,26 +120,30 @@ const Checkbox = defineComponent({
121120
props: {
122121
checked: {
123122
type: [Boolean, 'indeterminate'] as PropType<boolean | 'indeterminate'>,
124-
default: false,
123+
default: undefined,
125124
},
126125
defaultChecked: {
127126
type: [Boolean, 'indeterminate'] as PropType<boolean | 'indeterminate'>,
128-
default: false,
127+
default: undefined,
128+
},
129+
required: {
130+
type: Boolean,
131+
default: undefined,
129132
},
130-
required: Boolean,
131133
onCheckedChange: Function as PropType<(checked: CheckedState) => void>,
132134
scopeCheckbox: {
133135
type: Object as unknown as PropType<Scope>,
134136
required: false,
137+
default: undefined,
135138
},
136139
},
137140
setup(props, { attrs, slots, expose }) {
138-
const { checked: checkedProp, scopeCheckbox, defaultChecked, onCheckedChange, required } = props
139-
const innerRef = ref()
140-
const _innerRef = computed(() => innerRef.value?.$el)
141+
const { checked: checkedProp, scopeCheckbox, defaultChecked, onCheckedChange, required } = toRefs(props)
142+
143+
const { _ref: buttonRef, refEl: buttonRefEl } = useRef<HTMLButtonElement>()
141144

142145
expose({
143-
innerRef: _innerRef,
146+
innerRef: buttonRefEl,
144147
})
145148

146149
const {
@@ -150,60 +153,55 @@ const Checkbox = defineComponent({
150153
...checkboxProps
151154
} = attrs as CheckboxElement
152155

153-
const _button = computed<HTMLButtonElement>(() => _innerRef.value)
154-
// const button = ref<HTMLButtonElement>()
155-
// TODO: Change the useComposedRefs structure here if necessary (https://github.com/radix-ui/primitives/blob/c3f2189034e690e9fb564d484733144fdcbc02d7/packages/react/checkbox/src/Checkbox.tsx#L56)
156-
// const composedRefs = useComposedRefs(innerRef, button)
157-
158156
const hasConsumerStoppedPropagationRef = ref(false)
159157

160-
const isFormControl = _button.value ? Boolean(_button.value.closest('form')) : true
161-
const [checked, setChecked] = useControllableRef({
162-
prop: checkedProp,
163-
defaultProp: defaultChecked,
164-
onChange: onCheckedChange,
158+
const isFormControl = buttonRefEl.value ? Boolean(buttonRefEl.value.closest('form')) : true
159+
const { state } = useControllableRef({
160+
prop: checkedProp.value,
161+
defaultProp: defaultChecked.value,
162+
onChange: onCheckedChange.value,
165163
})
166164

167165
const initialCheckedStateRef = ref()
168166

169167
onMounted(() => {
170-
initialCheckedStateRef.value = checked.value
168+
initialCheckedStateRef.value = state.value
171169
})
172170

173-
watchEffect(() => {
174-
const form = _button.value?.form
171+
watch([buttonRefEl, state], () => {
172+
const form = buttonRefEl.value?.form
175173
if (form) {
176-
const reset = () => setChecked(initialCheckedStateRef.value)
174+
const reset = () => (state.value = initialCheckedStateRef.value)
177175
form.addEventListener('reset', reset)
178176
return () => form.removeEventListener('reset', reset)
179177
}
180178
})
181179

182180
CheckboxProvider({
183-
scope: scopeCheckbox as Scope,
184-
state: checked as Ref<CheckedState>,
181+
scope: scopeCheckbox.value as Scope,
182+
state: state as Ref<CheckedState>,
185183
disabled: disabled as boolean,
186184
})
187185

188186
const originalReturn = () =>
189187
[h(Primitive.button, {
190188
'type': 'button',
191189
'role': 'checkbox',
192-
'aria-checked': isIndeterminate(checked.value as any) ? 'mixed' : checked.value as any,
193-
'aria-required': required,
194-
'data-state': getState(checked.value as any),
190+
'aria-checked': isIndeterminate(state.value as any) ? 'mixed' : state.value as any,
191+
'aria-required': required.value,
192+
'data-state': getState(state.value as any),
195193
'data-disabled': disabled ? '' : undefined,
196194
'disabled': disabled,
197195
'value': value,
198196
...checkboxProps,
199-
'ref': innerRef,
197+
'ref': buttonRef,
200198
'onKeyDown': composeEventHandlers(checkboxProps.onKeydown, (event) => {
201199
// According to WAI ARIA, Checkboxes don't activate on enter keypress
202200
if (event.key === 'Enter')
203201
event.preventDefault()
204202
}),
205203
'onClick': composeEventHandlers(checkboxProps.onClick, (event) => {
206-
setChecked(prevChecked => (isIndeterminate(prevChecked) ? true : !prevChecked))
204+
state.value = isIndeterminate(state.value as any) ? true : !(state.value as any)
207205
if (isFormControl) {
208206
// hasConsumerStoppedPropagationRef.value.current = event.isPropagationStopped()
209207
// if checkbox is in a form, stop propagation from the button so that we only propagate
@@ -220,11 +218,11 @@ const Checkbox = defineComponent({
220218
isFormControl && h(
221219
BubbleInput,
222220
{
223-
control: _button.value,
221+
control: buttonRefEl.value,
224222
bubbles: !hasConsumerStoppedPropagationRef.value,
225223
name,
226224
value,
227-
checked: checked.value,
225+
checked: state.value,
228226
required,
229227
disabled,
230228
// We transform because the input is absolutely positioned but we have
@@ -285,7 +283,7 @@ const CheckboxIndicator = defineComponent({
285283
])
286284

287285
return originalReturn as unknown as {
288-
innerRef: Ref<CheckboxIndicatorElement>
286+
innerRef: Ref<HTMLButtonElement>
289287
}
290288
},
291289
})

packages/components/checkbox/src/stories/CheckboxDemo.vue

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<!-- eslint-disable no-console -->
22
<script setup lang="ts">
3-
import type { CheckboxProps } from '@oku-ui/checkbox'
3+
import type { CheckboxProps, CheckboxRef } from '@oku-ui/checkbox'
44
import { OkuCheckbox, OkuCheckboxIndicator } from '@oku-ui/checkbox'
55
import { OkuLabel } from '@oku-ui/label'
6+
import { onMounted, ref } from 'vue'
67
78
export interface ICheckBoxProps extends CheckboxProps {
89
template?: '#1' | '#2' | '#3'
@@ -12,6 +13,12 @@ export interface ICheckBoxProps extends CheckboxProps {
1213
withDefaults(defineProps<ICheckBoxProps>(), {
1314
1415
})
16+
17+
const refdd = ref<CheckboxRef>()
18+
19+
onMounted(() => {
20+
console.log(refdd.value?.innerRef, 'tt')
21+
})
1522
</script>
1623

1724
<template>
@@ -24,6 +31,7 @@ withDefaults(defineProps<ICheckBoxProps>(), {
2431
</h1>
2532
<OkuCheckbox
2633
id="checkbox"
34+
ref="refdd"
2735
class="w-6 h-6 flex bg-gray-300 rounded-md text-red-500 checked:text-red-600"
2836
>
2937
<OkuCheckboxIndicator class="w-6 h-6 flex items-center justify-center text-blue-500">

packages/core/use-composable/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
"peerDependencies": {
3535
"vue": "^3.3.1"
3636
},
37+
"dependencies": {
38+
"@oku-ui/utils": "workspace:^"
39+
},
3740
"devDependencies": {
3841
"@types/resize-observer-browser": "^0.1.7",
3942
"tsconfig": "workspace:^"

packages/core/use-composable/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ export { useControllableRef } from './useControllableRef'
22
export { useCallbackRef } from './useCallbackRef'
33
export { useSize } from './useSize'
44
export { usePrevious } from './usePrevious'
5-
export { useComposedRefs, composeRefs } from './useComposedRefs'
5+
export { useRef } from './useRef'
6+
export { unrefElement } from './unrefElement'
7+
export type { MaybeComputedElementRef } from './unrefElement'
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { ComponentPublicInstance } from 'vue'
2+
import type { MaybeRef, MaybeRefOrGetter } from '@oku-ui/utils'
3+
import { toValue } from '@oku-ui/utils'
4+
5+
export type VueInstance = ComponentPublicInstance
6+
export type MaybeElementRef<T extends MaybeElement = MaybeElement> = MaybeRef<T>
7+
export type MaybeComputedElementRef<T extends MaybeElement = MaybeElement> = MaybeRefOrGetter<T>
8+
export type MaybeElement = HTMLElement | SVGElement | VueInstance | undefined | null
9+
10+
export type UnRefElementReturn<T extends MaybeElement = MaybeElement> = T extends VueInstance ? Exclude<MaybeElement, VueInstance> : T | undefined
11+
12+
/**
13+
* Get the dom element of a ref of element or Vue component instance
14+
*
15+
* @param elRef
16+
*/
17+
export function unrefElement<T extends MaybeElement>(elRef: MaybeComputedElementRef<T>): UnRefElementReturn<T> {
18+
const plain = toValue(elRef)
19+
return (plain as VueInstance)?.$el ?? plain
20+
}

packages/core/use-composable/src/useCallbackRef.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1+
import type { Ref, UnwrapRef } from 'vue'
12
import { computed, ref, watchEffect } from 'vue'
2-
import type { Ref } from 'vue'
33

44
/**
5-
* A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a
5+
* A custom function that converts a callback to a ref to avoid triggering re-renders when passed as a
66
* prop or avoid re-executing effects when passed as a dependency
77
*/
88
function useCallbackRef<T extends (...args: any[]) => any>(callback: T | undefined): T {
9-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
10-
// @ts-expect-error
11-
const callbackRef: Ref<T | undefined> = ref(callback)
9+
const callbackRef: Ref<UnwrapRef<T> | undefined | T> = ref(callback)
1210

1311
watchEffect(() => {
1412
callbackRef.value = callback

packages/core/use-composable/src/useComposedRefs.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 25 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Ref } from 'vue'
2-
import { computed, ref, watch, watchEffect } from 'vue'
2+
import { ref, watch, watchEffect } from 'vue'
33
import { useCallbackRef } from './useCallbackRef'
44

55
type UseControllableRefParams<T> = {
@@ -8,78 +8,55 @@ type UseControllableRefParams<T> = {
88
onChange?: (ref: T) => void
99
}
1010

11-
type SetRefFn<T> = (prevRef?: T) => T
12-
1311
function useControllableRef<T>({
1412
prop,
1513
defaultProp,
1614
onChange = () => {},
1715
}: UseControllableRefParams<T>) {
18-
const uncontrolledProp = useUncontrolledRef({ defaultProp, onChange })
16+
const uncontrolledRef = useUncontrolledRef({
17+
defaultProp,
18+
onChange,
19+
})
20+
const handleChange = useCallbackRef(onChange)
1921
const isControlled = prop !== undefined
20-
const state = computed(() => (isControlled ? prop : uncontrolledProp.value))
21-
const handleChange = computed(() => onChange)
22-
23-
const setValue = (callback: (nextValue: T | undefined) => void | T): any => {
24-
const refCallback = ref(callback)
25-
const computedCallback = computed(() => refCallback.value)
2622

27-
watchEffect(() => {
28-
refCallback.value = callback
29-
})
23+
const state = ref(isControlled ? prop : uncontrolledRef) as Ref<T | undefined>
3024

25+
// TODO: How to add handleChange watch. handleChange add watch auto run when prop change :/ not good
26+
watch([state, uncontrolledRef, prop], () => {
3127
if (isControlled) {
32-
const setter = computedCallback.value as SetRefFn<T>
33-
const value = typeof computedCallback.value === 'function' ? setter(prop) : computedCallback.value
28+
const value = typeof state.value === 'function' ? state.value() : state.value
3429
if (value !== prop)
35-
handleChange.value(value as T)
30+
handleChange(prop)
3631
}
3732
else {
38-
const setter = callback as SetRefFn<T>
39-
uncontrolledProp.value = typeof callback === 'function' ? setter(uncontrolledProp.value) : callback
33+
uncontrolledRef.value = state.value
4034
}
41-
}
35+
}, {
36+
deep: true,
37+
})
4238

43-
return [state, setValue] as const
39+
return {
40+
state,
41+
}
4442
}
4543

4644
function useUncontrolledRef<T>({
4745
defaultProp,
4846
onChange,
4947
}: Omit<UseControllableRefParams<T>, 'prop'>) {
50-
const uncontrolledRef = ref(defaultProp) as Ref<T | undefined>
51-
const prevValueRef = ref(defaultProp) as Ref<T | undefined>
48+
const state = ref(defaultProp) as Ref<T | undefined>
49+
const prevValue = ref(defaultProp) as Ref<T | undefined>
5250
const handleChange = useCallbackRef(onChange)
5351

54-
watch([uncontrolledRef, prevValueRef, handleChange], () => {
55-
if (prevValueRef.value !== uncontrolledRef.value) {
56-
handleChange(uncontrolledRef.value as T)
57-
prevValueRef.value = uncontrolledRef.value
52+
watchEffect(() => {
53+
if (prevValue.value !== state.value) {
54+
handleChange(state.value as T)
55+
prevValue.value = state.value
5856
}
5957
})
6058

61-
return uncontrolledRef
59+
return state
6260
}
6361

6462
export { useControllableRef }
65-
66-
// type Callback<T extends any[]> = (...args: T) => void
67-
68-
// function useCallback<T extends any[]>(callback: Callback<T>, deps: any[]): (...args: T) => void {
69-
// const refCallback = ref(callback)
70-
// const computedCallback = computed(() => refCallback.value)
71-
72-
// const memoizedCallback = (...args: T) => computedCallback.value(...args)
73-
74-
// watchEffect(() => {
75-
// refCallback.value = callback
76-
// })
77-
78-
// if (deps.length > 0) {
79-
// watch(deps, () => {
80-
// refCallback.value = callback
81-
// })
82-
// }
83-
84-
// return memoizedCallback
85-
// }

0 commit comments

Comments
 (0)