From 4e9c3010125b4a5c465c2b2662341d6a002401bb Mon Sep 17 00:00:00 2001 From: neverland Date: Sat, 12 Nov 2022 13:14:58 +0800 Subject: [PATCH] feat(Popover): support uncontrolled mode (#11244) --- .../vant/src/composables/use-sync-prop-ref.ts | 22 ++++++++++++ packages/vant/src/popover/Popover.tsx | 24 ++++++++----- packages/vant/src/popover/README.md | 35 +++++++++++++++++++ packages/vant/src/popover/README.zh-CN.md | 35 +++++++++++++++++++ packages/vant/src/popover/demo/index.vue | 17 +++++++++ .../test/__snapshots__/demo.spec.ts.snap | 13 +++++++ packages/vant/src/popover/test/index.spec.tsx | 4 +-- 7 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 packages/vant/src/composables/use-sync-prop-ref.ts diff --git a/packages/vant/src/composables/use-sync-prop-ref.ts b/packages/vant/src/composables/use-sync-prop-ref.ts new file mode 100644 index 000000000..f0f42e8e9 --- /dev/null +++ b/packages/vant/src/composables/use-sync-prop-ref.ts @@ -0,0 +1,22 @@ +import { Ref, ref, watch } from 'vue'; + +export const useSyncPropRef = ( + getProp: () => T, + setProp: (value: T) => void +) => { + const propRef = ref(getProp()) as Ref; + + watch(getProp, (value) => { + if (value !== propRef.value) { + propRef.value = value; + } + }); + + watch(propRef, (value) => { + if (value !== getProp()) { + setProp(value); + } + }); + + return propRef; +}; diff --git a/packages/vant/src/popover/Popover.tsx b/packages/vant/src/popover/Popover.tsx index d27cdfbb4..6301e6a58 100644 --- a/packages/vant/src/popover/Popover.tsx +++ b/packages/vant/src/popover/Popover.tsx @@ -29,6 +29,7 @@ import { // Composables import { useClickAway } from '@vant/use'; +import { useSyncPropRef } from '../composables/use-sync-prop-ref'; // Components import { Icon } from '../icon'; @@ -45,7 +46,6 @@ import { const [name, bem] = createNamespace('popover'); const popupProps = [ - 'show', 'overlay', 'duration', 'teleport', @@ -95,6 +95,11 @@ export default defineComponent({ const wrapperRef = ref(); const popoverRef = ref(); + const show = useSyncPropRef( + () => props.show, + (value) => emit('update:show', value) + ); + const getPopoverOptions = () => ({ placement: props.placement, modifiers: [ @@ -126,7 +131,7 @@ export default defineComponent({ const updateLocation = () => { nextTick(() => { - if (!props.show) { + if (!show.value) { return; } @@ -138,11 +143,13 @@ export default defineComponent({ }); }; - const updateShow = (value: boolean) => emit('update:show', value); + const updateShow = (value: boolean) => { + show.value = value; + }; const onClickWrapper = () => { if (props.trigger === 'click') { - updateShow(!props.show); + show.value = !show.value; } }; @@ -154,17 +161,17 @@ export default defineComponent({ emit('select', action, index); if (props.closeOnClickAction) { - updateShow(false); + show.value = false; } }; const onClickAway = () => { if ( - props.show && + show.value && props.closeOnClickOutside && (!props.overlay || props.closeOnClickOverlay) ) { - updateShow(false); + show.value = false; } }; @@ -215,7 +222,7 @@ export default defineComponent({ } }); - watch(() => [props.show, props.offset, props.placement], updateLocation); + watch(() => [show.value, props.offset, props.placement], updateLocation); useClickAway([wrapperRef, popupRef], onClickAway, { eventName: 'touchstart', @@ -228,6 +235,7 @@ export default defineComponent({ + + +``` + +```js +import { ref } from 'vue'; +import { showToast } from 'vant'; + +export default { + setup() { + const actions = [ + { text: '选项一' }, + { text: '选项二' }, + { text: '选项三' }, + ]; + const onSelect = (action) => showToast(action.text); + return { + actions, + onSelect, + }; + }, +}; +``` + ## API ### Props diff --git a/packages/vant/src/popover/README.zh-CN.md b/packages/vant/src/popover/README.zh-CN.md index bdcc0809b..06507d340 100644 --- a/packages/vant/src/popover/README.zh-CN.md +++ b/packages/vant/src/popover/README.zh-CN.md @@ -214,6 +214,41 @@ export default { }; ``` +### 非受控模式 + +你可以把 Popover 当做受控组件或非受控组件使用: + +- 当绑定 `v-model:show` 时,Popover 为受控组件,此时组件的显示完全由 `v-model:show` 的值决定。 +- 当未绑定 `v-model:show` 时,Popover 为非受控组件,此时你可以通过 `show` 属性传入一个默认值,组件值的显示由组件自身控制。 + +```html + + + +``` + +```js +import { ref } from 'vue'; +import { showToast } from 'vant'; + +export default { + setup() { + const actions = [ + { text: '选项一' }, + { text: '选项二' }, + { text: '选项三' }, + ]; + const onSelect = (action) => showToast(action.text); + return { + actions, + onSelect, + }; + }, +}; +``` + ## API ### Props diff --git a/packages/vant/src/popover/demo/index.vue b/packages/vant/src/popover/demo/index.vue index 0d46830ae..37e6595ec 100644 --- a/packages/vant/src/popover/demo/index.vue +++ b/packages/vant/src/popover/demo/index.vue @@ -29,6 +29,7 @@ const t = useTranslate({ darkTheme: '深色风格', lightTheme: '浅色风格', showPopover: '点击弹出气泡', + uncontrolled: '非受控模式', actionOptions: '选项配置', customContent: '自定义内容', disableAction: '禁用选项', @@ -52,6 +53,7 @@ const t = useTranslate({ darkTheme: 'Dark Theme', lightTheme: 'Light Theme', showPopover: 'Show Popover', + uncontrolled: 'Uncontrolled', actionOptions: 'Action Options', customContent: 'Custom Content', disableAction: 'Disable Action', @@ -120,6 +122,7 @@ const onSelect = (action: { text: string }) => showToast(action.text); + showToast(action.text); + + + + + +