From bdea8ead41add944db9536d9cb811e49cdf88dda Mon Sep 17 00:00:00 2001 From: Roman Harmyder Date: Sat, 16 Oct 2021 16:43:45 +0300 Subject: [PATCH] feat(UseFocusTrap): component (#820) Co-authored-by: Anthony Fu --- packages/core/types.ts | 2 +- packages/integrations/package.json | 4 ++ .../integrations/useFocusTrap/component.ts | 32 +++++++++ packages/integrations/useFocusTrap/index.md | 20 ++++++ scripts/rollup.config.ts | 68 ++++++++++++++++--- scripts/utils.ts | 13 +++- 6 files changed, 126 insertions(+), 13 deletions(-) create mode 100644 packages/integrations/useFocusTrap/component.ts diff --git a/packages/core/types.ts b/packages/core/types.ts index 95da0202e813..59bf8f7fb324 100644 --- a/packages/core/types.ts +++ b/packages/core/types.ts @@ -7,7 +7,7 @@ export interface Position { export interface RenderableComponent { /** - * The elment that the component should be rendered as + * The element that the component should be rendered as * * @default 'div' */ diff --git a/packages/integrations/package.json b/packages/integrations/package.json index b625f15309a1..f56e2174fc6f 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -36,6 +36,10 @@ "import": "./useFocusTrap.mjs", "require": "./useFocusTrap.cjs" }, + "./useFocusTrap/component": { + "import": "./useFocusTrap/component.mjs", + "require": "./useFocusTrap/component.cjs" + }, "./useFuse": { "import": "./useFuse.mjs", "require": "./useFuse.cjs" diff --git a/packages/integrations/useFocusTrap/component.ts b/packages/integrations/useFocusTrap/component.ts new file mode 100644 index 000000000000..ee327c6a61ef --- /dev/null +++ b/packages/integrations/useFocusTrap/component.ts @@ -0,0 +1,32 @@ +import { h, ref, defineComponent, watch, onUnmounted } from 'vue-demi' +import { createFocusTrap } from 'focus-trap' +import type { FocusTrap } from 'focus-trap' +import { unrefElement } from '@vueuse/core' +import { RenderableComponent } from '@vueuse/core/types' + +export const UseFocusTrap = defineComponent({ + name: 'UseFocusTrap', + props: ['as'] as unknown as undefined, + setup(props, { slots }) { + let trap: undefined | FocusTrap + const target = ref() + + const activate = () => trap && trap.activate() + const deactivate = () => trap && trap.deactivate() + watch( + () => unrefElement(target), + (el) => { + if (!el) + return + trap = createFocusTrap(el, {}) + activate() + }, { flush: 'post' }) + + // Cleanup on unmount + onUnmounted(() => deactivate()) + return () => { + if (slots.default) + return h(props.as || 'div', { ref: target }, slots.default()) + } + }, +}) diff --git a/packages/integrations/useFocusTrap/index.md b/packages/integrations/useFocusTrap/index.md index acf6d1815e7b..08c0cae28d09 100644 --- a/packages/integrations/useFocusTrap/index.md +++ b/packages/integrations/useFocusTrap/index.md @@ -50,3 +50,23 @@ const { hasFocus, activate, deactivate } = useFocusTrap(target, { immediate: tru ``` + +## Using Component + +This function can't properly activate focus on elements with conditional rendering. In this case, you can use the `UseFocusTrap` component. Focus Trap will be activated automatically on mounting this component and deactivated on unmount. + +```html + + + + +``` diff --git a/scripts/rollup.config.ts b/scripts/rollup.config.ts index 677e393623e2..354594ccb7ff 100644 --- a/scripts/rollup.config.ts +++ b/scripts/rollup.config.ts @@ -5,6 +5,7 @@ import dts from 'rollup-plugin-dts' import type { OutputOptions, Plugin, RollupOptions } from 'rollup' import fg from 'fast-glob' import { packages } from '../meta/packages' +import { functions } from '../meta/function-indexes' const VUE_DEMI_IIFE = fs.readFileSync(require.resolve('vue-demi/lib/index.iife.js'), 'utf-8') const configs: RollupOptions[] = [] @@ -16,6 +17,19 @@ const injectVueDemi: Plugin = { }, } +const buildPlugins = [ + esbuild(), +] + +const dtsPlugin = [ + dts(), +] + +const externals = [ + 'vue-demi', + '@vueuse/shared', +] + const esbuildMinifer = (options: ESBuildOptions) => { const { renderChunk } = esbuild(options) @@ -40,7 +54,11 @@ for (const { globals, name, external, submodules, iife } of packages) { functionNames.push(...fg.sync('*/index.ts', { cwd: resolve(`packages/${name}`) }).map(i => i.split('/')[0])) for (const fn of functionNames) { - const input = fn === 'index' ? `packages/${name}/index.ts` : `packages/${name}/${fn}/index.ts` + const input = fn === 'index' + ? `packages/${name}/index.ts` + : `packages/${name}/${fn}/index.ts` + + const info = functions.find(i => i.name === fn) const output: OutputOptions[] = [ { @@ -84,12 +102,9 @@ for (const { globals, name, external, submodules, iife } of packages) { configs.push({ input, output, - plugins: [ - esbuild(), - ], + plugins: buildPlugins, external: [ - 'vue-demi', - '@vueuse/shared', + ...externals, ...(external || []), ], }) @@ -100,15 +115,46 @@ for (const { globals, name, external, submodules, iife } of packages) { file: `packages/${name}/dist/${fn}.d.ts`, format: 'es', }, - plugins: [ - dts(), - ], + plugins: dtsPlugin, external: [ - 'vue-demi', - '@vueuse/shared', + ...externals, ...(external || []), ], }) + + if (info?.component) { + configs.push({ + input: `packages/${name}/${fn}/component.ts`, + output: [ + { + file: `packages/${name}/dist/${fn}/component.cjs`, + format: 'cjs', + }, + { + file: `packages/${name}/dist/${fn}/component.mjs`, + format: 'es', + }, + ], + plugins: buildPlugins, + external: [ + ...externals, + ...(external || []), + ], + }) + + configs.push({ + input: `packages/${name}/${fn}/component.ts`, + output: { + file: `packages/${name}/dist/${fn}/component.d.ts`, + format: 'es', + }, + plugins: dtsPlugin, + external: [ + ...externals, + ...(external || []), + ], + }) + } } } diff --git a/scripts/utils.ts b/scripts/utils.ts index 84cb1799670a..2951059d96a3 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -183,7 +183,12 @@ export async function updateImport({ packages, functions }: PackageIndexes) { imports = functions .sort((a, b) => a.name.localeCompare(b.name)) .flatMap((fn) => { - const arr = [] + const arr: string[] = [] + + // don't include integration components + if (fn.package === 'integrations') + return arr + if (fn.component) arr.push(`export * from '../${fn.package}/${fn.name}/component'`) if (fn.directive) @@ -371,6 +376,12 @@ export async function updatePackageJSON(indexes: PackageIndexes) { import: `./${i.name}.mjs`, require: `./${i.name}.cjs`, } + if (i.component) { + packageJSON.exports[`./${i.name}/component`] = { + import: `./${i.name}/component.mjs`, + require: `./${i.name}/component.cjs`, + } + } }) }