From 23cabb68f2574d7af81eebcf29e6f8bea7fa4792 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Wed, 6 Nov 2024 15:23:44 +0100 Subject: [PATCH] feat: Add `spawnDialog` function This allows to spawn a Vue dialog without the need of mounting the component. So it is possible to spawn a dialog directly from any JS code outside of components (e.g. callback functions). This method was originally taken from `@nextcloud/dialogs`, but I think it should belong to this Vue components library. Co-authored-by: Ferdinand Thiessen Co-authored-by: Grigorii K. Shartsev Signed-off-by: Ferdinand Thiessen --- docs/functions/spawnDialog.md | 107 +++++++++++++++++++++++++++ src/functions/dialog/index.ts | 50 +++++++++++++ src/functions/{index.js => index.ts} | 1 + src/index.js | 2 +- styleguide.config.js | 4 + styleguide/global.requires.js | 2 + 6 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 docs/functions/spawnDialog.md create mode 100644 src/functions/dialog/index.ts rename src/functions/{index.js => index.ts} (90%) diff --git a/docs/functions/spawnDialog.md b/docs/functions/spawnDialog.md new file mode 100644 index 0000000000..4af36206f4 --- /dev/null +++ b/docs/functions/spawnDialog.md @@ -0,0 +1,107 @@ + +```ts static +import { + spawnDialog, +} from '@nextcloud/vue/dist/Functions/dialog.js' +``` + +## Definitions + +```ts static +/** + * Helper to spawn a Vue dialog without having to mount it from a component + * + * @param dialog The dialog component to spawn - the component must emit the 'close' event whenever it is closed + * @param props Properties to pass to the dialog + * @param props.container Optionally pass a query selector for the dialog container element + * @param onClose Callback when the dialog is closed (parameters of the 'close' event of the dialog) + */ +function spawnDialog( + dialog: Component | AsyncComponent, + props: Record, + onClose: (...rest: unknown[]) => void = () => {}, +): Vue; +``` + +## Usage + +The main use case is to be able to spawn a dialog from code, without the need of including the dialog component in the template. +So a Vue dialog can be spawned in any context and not just from Vue components but also from callback functions or other API. + +```vue + + + +``` \ No newline at end of file diff --git a/src/functions/dialog/index.ts b/src/functions/dialog/index.ts new file mode 100644 index 0000000000..2d63c4e73e --- /dev/null +++ b/src/functions/dialog/index.ts @@ -0,0 +1,50 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import type { AsyncComponent, Component } from 'vue' + +import Vue, { toRaw } from 'vue' + +interface DialogProps { + [index: string]: unknown + container?: string +} + +/** + * Helper to spawn a Vue dialog without having to mount it from a component + * + * @param dialog The dialog component to spawn + * @param props Properties to pass to the dialog + * @param props.container Optionally pass a query selector for the dialog container element + * @param onClose Callback when the dialog is closed + */ +export function spawnDialog( + dialog: Component | AsyncComponent, + props?: DialogProps, + onClose: (...rest: unknown[]) => void = () => {}, +): Vue { + const el = document.createElement('div') + + const container: HTMLElement = typeof props?.container === 'string' + ? (document.querySelector(props.container) || document.body) + : document.body + container.appendChild(el) + + const vm = new Vue({ + el, + name: 'VueDialogHelper', + render: (h) => + h(dialog, { + props, + on: { + close: (...rest: unknown[]) => { + onClose(...rest.map(v => toRaw(v))) + vm.$destroy() + el.remove() + }, + }, + }), + }) + return vm +} diff --git a/src/functions/index.js b/src/functions/index.ts similarity index 90% rename from src/functions/index.js rename to src/functions/index.ts index 807f98ef08..bc0b822256 100644 --- a/src/functions/index.js +++ b/src/functions/index.ts @@ -4,6 +4,7 @@ */ export * from './a11y/index.ts' +export * from './dialog/index.ts' export * from './emoji/index.ts' export * from './reference/index.js' export * from './isDarkTheme/index.ts' diff --git a/src/index.js b/src/index.js index 4a1f7c90ce..de5d928418 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ export * from './components/index.js' export * from './composables/index.js' -export * from './functions/index.js' +export * from './functions/index.ts' export * from './directives/index.js' export * from './mixins/index.js' diff --git a/styleguide.config.js b/styleguide.config.js index 99b1dc3f3d..4890c36934 100644 --- a/styleguide.config.js +++ b/styleguide.config.js @@ -145,6 +145,10 @@ module.exports = async () => { name: 'registerReference', content: 'docs/functions/registerReference.md', }, + { + name: 'spawnDialog', + content: 'docs/functions/spawnDialog.md', + }, ], }, { diff --git a/styleguide/global.requires.js b/styleguide/global.requires.js index 741bc116fe..7f1bd65f08 100644 --- a/styleguide/global.requires.js +++ b/styleguide/global.requires.js @@ -12,6 +12,7 @@ import Tooltip from './../src/directives/Tooltip/index.js' import Focus from './../src/directives/Focus/index.js' import Linkify from './../src/directives/Linkify/index.js' import { useIsDarkTheme } from '../src/composables/index.js' +import { spawnDialog } from '../src/functions/dialog/index.ts' import axios from '@nextcloud/axios' @@ -167,6 +168,7 @@ window.emojiAddRecent = emojiAddRecent window.getCurrentSkinTone = getCurrentSkinTone window.setCurrentSkinTone = setCurrentSkinTone window.usernameToColor = usernameToColor +window.spawnDialog = spawnDialog // Exported composables window.useIsDarkTheme = useIsDarkTheme