diff --git a/packages/.vitepress/theme/styles/demo.css b/packages/.vitepress/theme/styles/demo.css index 6509241f97a..38b882dcfe2 100644 --- a/packages/.vitepress/theme/styles/demo.css +++ b/packages/.vitepress/theme/styles/demo.css @@ -5,6 +5,7 @@ position: relative; margin-bottom: 10px; border-radius: 8px; + z-index: 50; transition: background-color 0.5s; } @@ -182,6 +183,11 @@ border-style: dashed; padding: 1rem; } + + dialog { + z-index: 1000000; + background: var(--vp-c-bg); + } } diff --git a/packages/.vitepress/theme/styles/vars.css b/packages/.vitepress/theme/styles/vars.css index 6c46307a8ad..b05b8ca325e 100644 --- a/packages/.vitepress/theme/styles/vars.css +++ b/packages/.vitepress/theme/styles/vars.css @@ -13,7 +13,10 @@ --vp-c-disabled-bg: rgba(125,125,125,0.2); /* fix contrast on gray cards: used by --vp-c-text-2 */ + --vp-c-text-light-2: rgba(56 56 56 / 70%); --vp-c-text-dark-2: rgba(56 56 56 / 70%); + + --vp-custom-block-tip-bg: transparent; } .dark { diff --git a/packages/core/createReusableTemplate/index.md b/packages/core/createReusableTemplate/index.md index d6b3b622a53..b1374c4f3c5 100644 --- a/packages/core/createReusableTemplate/index.md +++ b/packages/core/createReusableTemplate/index.md @@ -7,6 +7,7 @@ outline: deep Define and reuse template inside the component scope. + ## Motivation It's common to have the need to reuse some part of the template. For example: @@ -179,7 +180,7 @@ const [DefineTemplate, ReuseTemplate] = createReusableTemplate() ``` -::: info +::: warning Passing slots does not work in Vue 2. ::: @@ -195,4 +196,3 @@ Alternative Approaches: - [Vue Macros - `namedTemplate`](https://vue-macros.sxzz.moe/features/named-template.html) - [`unplugin-@vueuse/core`](https://github.com/liulinboyi/unplugin-@vueuse/core) - diff --git a/packages/core/createReusableTemplate/index.ts b/packages/core/createReusableTemplate/index.ts index ad17616e252..93d9acf4d69 100644 --- a/packages/core/createReusableTemplate/index.ts +++ b/packages/core/createReusableTemplate/index.ts @@ -1,12 +1,11 @@ import type { DefineComponent, Slot } from 'vue-demi' -import { defineComponent } from 'vue-demi' -import { __onlyVue27Plus, makeDestructurable } from '@vueuse/shared' +import { defineComponent, isVue3, version } from 'vue-demi' +import { makeDestructurable } from '@vueuse/shared' export type DefineTemplateComponent< Bindings extends object, Slots extends Record, - Props = {}, -> = DefineComponent & { +> = DefineComponent<{}> & { new(): { $slots: { default(_: Bindings & { $slots: Slots }): any } } } @@ -17,6 +16,17 @@ export type ReuseTemplateComponent< new(): { $slots: Slots } } +export type ReusableTemplatePair< + Bindings extends object, + Slots extends Record, +> = [ + DefineTemplateComponent, + ReuseTemplateComponent, +] & { + define: DefineTemplateComponent + reuse: ReuseTemplateComponent +} + /** * This function creates `define` and `reuse` components in pair, * It also allow to pass a generic to bind with type. @@ -26,8 +36,14 @@ export type ReuseTemplateComponent< export function createReusableTemplate< Bindings extends object, Slots extends Record = Record, ->(name?: string) { - __onlyVue27Plus() +>(): ReusableTemplatePair { + // compatibility: Vue 2.7 or above + if (!isVue3 && !version.startsWith('2.7.')) { + if (process.env.NODE_ENV !== 'production') + throw new Error('[VueUse] createReusableTemplate only works in Vue 2.7 or above.') + // @ts-expect-error incompatible + return + } let render: Slot | undefined @@ -44,7 +60,7 @@ export function createReusableTemplate< setup(_, { attrs, slots }) { return () => { if (!render && process.env.NODE_ENV !== 'production') - throw new Error(`[VueUse] Failed to find the definition of template${name ? ` "${name}"` : ''}`) + throw new Error('[VueUse] Failed to find the definition of reusable template') return render?.({ ...attrs, $slots: slots }) } }, @@ -52,6 +68,6 @@ export function createReusableTemplate< return makeDestructurable( { define, reuse }, - [define, reuse] as const, - ) + [define, reuse], + ) as any } diff --git a/packages/core/createTemplatePromise/demo.vue b/packages/core/createTemplatePromise/demo.vue new file mode 100644 index 00000000000..88b380904f9 --- /dev/null +++ b/packages/core/createTemplatePromise/demo.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/packages/core/createTemplatePromise/index.md b/packages/core/createTemplatePromise/index.md new file mode 100644 index 00000000000..2bc553e9d07 --- /dev/null +++ b/packages/core/createTemplatePromise/index.md @@ -0,0 +1,175 @@ +--- +category: Component +outline: deep +--- + +# createTemplatePromise + +Template as Promise. Useful for constructing custom Dialogs, Modals, Toasts, etc. + +## Usage + +```html + + + +``` + +::: tip +This function is migrated from [vue-template-promise](https://github.com/antfu/vue-template-promise) +::: + +## Features + +- **Programmatic** - call your UI as a promise +- **Template** - use Vue template to render, not a new DSL +- **TypeScript** - full type safety via generic type +- **Renderless** - you take full control of the UI +- **Transition** - use support Vue transition + +## Usage + +`createTemplatePromise` returns a **Vue Component** that you can directly use in your template with ` + + + + +``` + +Learn more about [Vue Transition](https://v3.vuejs.org/guide/transitions-overview.html). + +## Motivation + +The common approach to call a dialog or a model programmatically would be like this: + +```ts +const dialog = useDialog() +const result = await dialog.open({ + title: 'Hello', + content: 'World', +}) +``` + +This would work by sending these information to the top-level component and let it render the dialog. However, it limits the flexibility you could express in the UI. For example, you could want the title to be red, or have extra buttons, etc. You would end up with a lot of options like: + +```ts +const result = await dialog.open({ + title: 'Hello', + titleClass: 'text-red', + content: 'World', + contentClass: 'text-blue text-sm', + buttons: [ + { text: 'OK', class: 'bg-red', onClick: () => {} }, + { text: 'Cancel', class: 'bg-blue', onClick: () => {} }, + ], + // ... +}) +``` + +Even this is not flexible enough. If you want more, you might end up with manual render function. + +```ts +const result = await dialog.open({ + title: 'Hello', + contentSlot: () => h(MyComponent, { content }), +}) +``` + +This is like reinventing a new DSL in the script to express the UI template. + +So this function allows **expressing the UI in templates instead of scripts**, where it is supposed to be, while still being able to be manipulated programmatically. diff --git a/packages/core/createTemplatePromise/index.ts b/packages/core/createTemplatePromise/index.ts new file mode 100644 index 00000000000..9d19d74f8d3 --- /dev/null +++ b/packages/core/createTemplatePromise/index.ts @@ -0,0 +1,125 @@ +import type { DefineComponent, Ref, TransitionGroupProps } from 'vue-demi' +import { Fragment, TransitionGroup, defineComponent, h, isVue3, ref, shallowReactive } from 'vue-demi' + +export interface TemplatePromiseProps { + /** + * The promise instance. + */ + promise: Promise | undefined + /** + * Resolve the promise. + */ + resolve: (v: Return | Promise) => void + /** + * Reject the promise. + */ + reject: (v: any) => void + /** + * Arguments passed to TemplatePromise.start() + */ + args: Args + /** + * Indicates if the promise is resolving. + * When passing another promise to `resolve`, this will be set to `true` until the promise is resolved. + */ + isResolving: boolean + /** + * Options passed to createTemplatePromise() + */ + options: TemplatePromiseOptions + /** + * Unique key for list rendering. + */ + key: number +} + +export interface TemplatePromiseOptions { + /** + * Determines if the promise can be called only once at a time. + * + * @default false + */ + singleton?: boolean + + /** + * Transition props for the promise. + */ + transition?: TransitionGroupProps +} + +export type TemplatePromise = DefineComponent<{}> & { + new(): { + $slots: { + default(_: TemplatePromiseProps): any + } + } +} & { + start: (...args: Args) => Promise +} + +/** + * Creates a template promise component. + * + * @see https://vueuse.org/createTemplatePromise + */ +export function createTemplatePromise( + options: TemplatePromiseOptions = {}, +): TemplatePromise { + // compatibility: Vue 3 or above + if (!isVue3) { + if (process.env.NODE_ENV !== 'production') + throw new Error('[VueUse] createTemplatePromise only works in Vue 3 or above.') + // @ts-expect-error incompatible + return + } + + let index = 0 + const instances = ref([]) as Ref[]> + + function create(...args: Args) { + const props = shallowReactive({ + key: index++, + args, + promise: undefined, + resolve: () => {}, + reject: () => {}, + isResolving: false, + options, + }) as TemplatePromiseProps + + instances.value.push(props) + + props.promise = new Promise((_resolve, _reject) => { + props.resolve = (v) => { + props.isResolving = true + return _resolve(v) + } + props.reject = _reject + }) + .finally(() => { + props.promise = undefined + const index = instances.value.indexOf(props) + if (index !== -1) + instances.value.splice(index, 1) + }) + + return props.promise + } + + function start(...args: Args) { + if (options.singleton && instances.value.length > 0) + return instances.value[0].promise + return create(...args) + } + + const component = defineComponent((_, { slots }) => { + const renderList = () => instances.value.map(props => h(Fragment, { key: props.key }, slots.default?.(props))) + if (options.transition) + return () => h(TransitionGroup, options.transition, renderList) + return renderList + }) + + component.start = start + + return component as any +} diff --git a/packages/core/index.ts b/packages/core/index.ts index c0e59335e85..a33b7ac0045 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -1,6 +1,7 @@ export * from './computedAsync' export * from './computedInject' export * from './createReusableTemplate' +export * from './createTemplatePromise' export * from './createUnrefFn' export * from './onClickOutside' export * from './onKeyStroke' diff --git a/packages/core/useTemplateRefsList/index.md b/packages/core/useTemplateRefsList/index.md index 444a41b913d..345411b9953 100644 --- a/packages/core/useTemplateRefsList/index.md +++ b/packages/core/useTemplateRefsList/index.md @@ -6,7 +6,9 @@ category: Component Shorthand for binding refs to template elements and components inside `v-for`. -> This function only works for Vue 3.x. +::: warning +This function only works for Vue 3 +::: ## Usage diff --git a/packages/router/README.md b/packages/router/README.md index 725415c4298..bac620b5212 100644 --- a/packages/router/README.md +++ b/packages/router/README.md @@ -2,7 +2,11 @@ [![NPM version](https://img.shields.io/npm/v/@vueuse/router?color=a1b858)](https://www.npmjs.com/package/@vueuse/router) -> This is an add-on of [VueUse](https://github.com/vueuse/vueuse), providing utilities for vue-router (Vue 3 Only). +> This is an add-on of [VueUse](https://github.com/vueuse/vueuse), providing utilities for vue-router (Vue 3 only). + +::: warning +This package only works for Vue 3 +::: ## Install diff --git a/packages/shared/computedWithControl/index.md b/packages/shared/computedWithControl/index.md index 30b05340594..a6a0eb5335f 100644 --- a/packages/shared/computedWithControl/index.md +++ b/packages/shared/computedWithControl/index.md @@ -37,8 +37,6 @@ console.log(computedRef.value) // 1 ### Manual Triggering -> This only works in Vue 3 - You can also manually trigger the update of the computed by: ```ts @@ -49,3 +47,7 @@ const computedRef = computedWithControl( computedRef.trigger() ``` + +::: warning +Manual triggering only works for Vue 3 +::: diff --git a/packages/shared/extendRef/index.md b/packages/shared/extendRef/index.md index 8b55442e076..8001bd8fa89 100644 --- a/packages/shared/extendRef/index.md +++ b/packages/shared/extendRef/index.md @@ -6,9 +6,11 @@ category: Reactivity Add extra attributes to Ref. -## Usage +::: warning +This function only works for Vue 2.7 or above. +::: -> This function is **NOT compatible with Vue 2.6 or below**. +## Usage > Please note the extra attribute will not be accessible in Vue's template. diff --git a/packages/shared/extendRef/index.ts b/packages/shared/extendRef/index.ts index de179392ecc..308c8d7abe7 100644 --- a/packages/shared/extendRef/index.ts +++ b/packages/shared/extendRef/index.ts @@ -29,9 +29,9 @@ export function extendRef, Extend extends object, Options ext // implementation export function extendRef, Extend extends object>(ref: R, extend: Extend, { enumerable = false, unwrap = true }: ExtendRefOptions = {}) { // compatibility: Vue 2.7 or above - if (!isVue3 || !version.startsWith('2.7.')) { + if (!isVue3 && !version.startsWith('2.7.')) { if (process.env.NODE_ENV !== 'production') - throw new Error('[VueUse] extendRef is only works on Vue 2.7 or above.') + throw new Error('[VueUse] extendRef only works in Vue 2.7 or above.') return } diff --git a/packages/shared/refWithControl/index.md b/packages/shared/refWithControl/index.md index 1eafee9c10e..d5d67130779 100644 --- a/packages/shared/refWithControl/index.md +++ b/packages/shared/refWithControl/index.md @@ -6,7 +6,11 @@ related: computedWithControl # refWithControl -Fine-grained controls over ref and its reactivity. (Vue 3 Only) +Fine-grained controls over ref and its reactivity. + +::: warning +This function only works for Vue 3 +::: ## Usage diff --git a/playgrounds/vite-vue2.7/App.vue b/playgrounds/vite-vue2.7/App.vue index 28c61e0b24c..4b8b4a3af8c 100644 --- a/playgrounds/vite-vue2.7/App.vue +++ b/playgrounds/vite-vue2.7/App.vue @@ -1,11 +1,12 @@ diff --git a/playgrounds/vite-vue2.7/components/createTemplatePromise.vue b/playgrounds/vite-vue2.7/components/createTemplatePromise.vue new file mode 100644 index 00000000000..f02cc7a3a5c --- /dev/null +++ b/playgrounds/vite-vue2.7/components/createTemplatePromise.vue @@ -0,0 +1,11 @@ + + + diff --git a/playgrounds/vite-vue2.7/components/useMouse.vue b/playgrounds/vite-vue2.7/components/useMouse.vue new file mode 100644 index 00000000000..28c61e0b24c --- /dev/null +++ b/playgrounds/vite-vue2.7/components/useMouse.vue @@ -0,0 +1,11 @@ + + +