diff --git a/packages/cdk/package.json b/packages/cdk/package.json index a12ed71b3..1be7dcf0b 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -34,7 +34,7 @@ "jsdelivr": "./index.full.js", "scripts": {}, "dependencies": { - "@popperjs/core": "^2.9.0", + "@floating-ui/dom": "^1.0.2", "lodash-es": "^4.17.0" }, "devDependencies": { diff --git a/packages/cdk/popper/__tests__/__snapshots__/popper.spec.ts.snap b/packages/cdk/popper/__tests__/__snapshots__/popper.spec.ts.snap index eea05c34d..c19a2f6d7 100644 --- a/packages/cdk/popper/__tests__/__snapshots__/popper.spec.ts.snap +++ b/packages/cdk/popper/__tests__/__snapshots__/popper.spec.ts.snap @@ -12,70 +12,70 @@ exports[`usePopper > options > autoAdjust work 2`] = ` exports[`usePopper > options > offset work 1`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > offset work 2`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 1`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 2`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 3`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 4`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 5`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 6`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 7`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 8`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 9`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 10`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 11`] = ` "
Trigger
-
Popper
" +
Popper
" `; exports[`usePopper > options > placement work 12`] = ` "
Trigger
-
Popper
" +
Popper
" `; diff --git a/packages/cdk/popper/docs/Api.zh.md b/packages/cdk/popper/docs/Api.zh.md index 66b28b0c9..ac1fef058 100644 --- a/packages/cdk/popper/docs/Api.zh.md +++ b/packages/cdk/popper/docs/Api.zh.md @@ -1,6 +1,6 @@ ## API -`@idux/cdk/popper` 基于 `@popperjs/core` 对浮层的创建进行了封装. +`@idux/cdk/popper` 基于 `@floating-ui/dom` 对浮层的创建进行了封装. ### usePopper @@ -20,8 +20,7 @@ export function usePopper(options?: PopperOptions): PopperInstance | `trigger` | 浮层的触发方式 | `PopperTrigger` | `hover` | - | - | | `visible` | 是否显示浮层 | `boolean` | `false` | - | - | | `strategy` | 浮层的定位策略 | `'absolute' \| 'fixed'` | `absolute` | - | - | -| `modifiers` | 自定义浮层的 `modifier` | `Partial[]` | `[]` | - | 参见[popper.js](https://popper.js.org/docs/v2/modifiers/) | -| `onFirstUpdate` | 浮层创建后的回调 | `(state: Partial) => void` | - | - | 参见[popper.js](https://popper.js.org/docs/v2/lifecycle/#hook-into-the-lifecycle) | +| `middleware` | 自定义浮层的 `middleware` | `Middleware[]` | `[]` | - | 参见[floating-ui](https://floating-ui.com/docs/middleware) | ```ts export declare type PopperPlacement = 'topStart' | 'top' | 'topEnd' | 'rightStart' | 'right' | 'rightEnd' | 'bottomStart' | 'bottom' | 'bottomEnd' | 'leftStart' | 'left' | 'leftEnd' @@ -37,7 +36,6 @@ export type PopperTrigger = 'click' | 'hover' | 'focus' | 'contextmenu' | 'manua | `show` | 显示浮层 | `(delay?: number): void` | - | - | `delay` 是延迟显示的时间 | | `hide` | 隐藏浮层 | `(delay?: number): void` | - | - | `delay` 是延迟隐藏的时间 | | `update` | 更新浮层 | `(options: Partial): void` | - | - | - | -| `forceUpdate` | 强制更新浮层 | `(): void` | - | - | - | | `destroy` | 销毁浮层 | `(): void` | - | - | - | | `visibility` | 浮层显示状态 | `ComputedRef` | -| - | - | | `placement` | 浮层位置 | `ComputedRef` | - | - | - | diff --git a/packages/cdk/popper/src/composables/useDelay.ts b/packages/cdk/popper/src/composables/useDelay.ts new file mode 100644 index 000000000..c9f5b2371 --- /dev/null +++ b/packages/cdk/popper/src/composables/useDelay.ts @@ -0,0 +1,24 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { PopperOptions } from '../types' + +import { type ComputedRef, computed } from 'vue' + +import { defaultDelay } from './useOptions' + +export function useDelay(options: Required): ComputedRef<{ show: number; hide: number }> { + const convertDelay = (delay: number | [number | null, number | null]) => { + if (Array.isArray(delay)) { + const [show, hide] = delay + return { show: show ?? defaultDelay, hide: hide ?? defaultDelay } + } + return { show: delay, hide: delay } + } + + return computed(() => convertDelay(options.delay)) +} diff --git a/packages/cdk/popper/src/composables/useInstance.ts b/packages/cdk/popper/src/composables/useInstance.ts new file mode 100644 index 000000000..5be788c30 --- /dev/null +++ b/packages/cdk/popper/src/composables/useInstance.ts @@ -0,0 +1,85 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { PopperElement } from '../types' + +import { type Ref, watch } from 'vue' + +import { type ComputePositionConfig, type ComputePositionReturn, autoUpdate, computePosition } from '@floating-ui/dom' + +import { convertElement } from '@idux/cdk/utils' + +export interface Instance { + update: () => Promise + destroy: () => void +} + +export function useInstance( + triggerRef: Ref, + popperRef: Ref, + options: Ref, +): Instance { + const updatePopperPosition = (state: ComputePositionReturn) => { + const popperEl = convertElement(popperRef.value) + const { x, y, strategy } = state + + if (popperEl) { + Object.assign(popperEl.style, { + position: strategy, + left: `${x}px`, + top: `${y}px`, + }) + } + } + + const update = async () => { + const triggerEl = convertElement(triggerRef.value) + const popperEl = convertElement(popperRef.value) + + if (!triggerEl || !popperEl) { + return + } + + const state = await computePosition(triggerEl, popperEl, options.value) + state && updatePopperPosition(state) + } + + let cleanUpHandler: (() => void) | null = null + const initialize = () => { + const triggerEl = convertElement(triggerRef.value) + const popperEl = convertElement(popperRef.value) + if (!triggerEl || !popperEl) { + return + } + + cleanUpHandler?.() + + Object.assign(popperEl.style, { + position: 'absolute', + left: 0, + top: 0, + }) + + cleanUpHandler = autoUpdate(triggerEl, popperEl, () => { + update() + }) + } + + const watchStopHandlers = [ + watch([triggerRef, popperRef], initialize, { immediate: true }), + watch(options, update, { immediate: true }), + ] + const destroy = () => { + watchStopHandlers.forEach(handler => handler()) + cleanUpHandler?.() + } + + return { + update, + destroy, + } +} diff --git a/packages/cdk/popper/src/composables/useOptions.ts b/packages/cdk/popper/src/composables/useOptions.ts new file mode 100644 index 000000000..3643d997a --- /dev/null +++ b/packages/cdk/popper/src/composables/useOptions.ts @@ -0,0 +1,62 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { PopperOptions } from '../types' + +import { type ComputedRef, computed, reactive, watch } from 'vue' + +import { isEqual } from 'lodash-es' + +export const defaultDelay = 0 + +export function usePopperOptions(options: PopperOptions): { + popperOptions: Required + updateOptions: (options: PopperOptions) => void +} { + const popperOptions = reactive>({ + allowEnter: options.allowEnter ?? true, + autoAdjust: options.autoAdjust ?? true, + delay: options.delay ?? defaultDelay, + disabled: options.disabled ?? false, + offset: options.offset ?? [0, 0], + placement: options.placement ?? 'top', + trigger: options.trigger ?? 'hover', + visible: options.visible ?? false, + strategy: options.strategy ?? 'absolute', + middlewares: options.middlewares ?? [], + }) + const updateOptions = (options: PopperOptions) => { + Object.entries(options).forEach(([key, value]) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (value !== undefined && !isEqual(value, (popperOptions as any)[key])) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(popperOptions as any)[key] = value + } + }) + } + + watch(options, _options => { + updateOptions(_options) + }) + + return { + popperOptions, + updateOptions, + } +} + +export type BaseOptions = Pick< + Required, + 'placement' | 'strategy' | 'middlewares' | 'offset' | 'autoAdjust' +> + +export function useBaseOptions(options: Required): ComputedRef { + return computed(() => { + const { placement, strategy, middlewares, offset, autoAdjust } = options + return { placement, strategy, middlewares, offset, autoAdjust } + }) +} diff --git a/packages/cdk/popper/src/composables/usePlacement.ts b/packages/cdk/popper/src/composables/usePlacement.ts new file mode 100644 index 000000000..e1241ad82 --- /dev/null +++ b/packages/cdk/popper/src/composables/usePlacement.ts @@ -0,0 +1,27 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { PopperOptions, PopperPlacement } from '../types' + +import { type ComputedRef, computed, ref, watch } from 'vue' + +export function usePlacement(options: Required): { + placement: ComputedRef + updatePlacement: (value: PopperPlacement) => void +} { + const _placement = ref(options.placement) + + const updatePlacement = (value: PopperPlacement) => { + _placement.value = value + } + + watch(() => options.placement, updatePlacement) + + const placement = computed(() => _placement.value) + + return { placement, updatePlacement } +} diff --git a/packages/cdk/popper/src/composables/usePopperEvents.ts b/packages/cdk/popper/src/composables/usePopperEvents.ts new file mode 100644 index 000000000..2dabf1670 --- /dev/null +++ b/packages/cdk/popper/src/composables/usePopperEvents.ts @@ -0,0 +1,32 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { PopperEvents, PopperOptions } from '../types' + +import { type ComputedRef, computed } from 'vue' + +import { NoopObject } from '@idux/cdk/utils' + +export function usePopperEvents( + baseOptions: Required, + eventOptions: { show(): void; hide(): void }, +): ComputedRef { + const { show, hide } = eventOptions + + const onMouseenter = () => show() + const onMouseleave = () => hide() + + const eventsMap = { + click: NoopObject, + focus: NoopObject, + hover: { onMouseenter, onMouseleave }, + contextmenu: NoopObject, + manual: NoopObject, + } + + return computed(() => (baseOptions.allowEnter ? eventsMap[baseOptions.trigger] : NoopObject)) +} diff --git a/packages/cdk/popper/src/composables/useTimer.ts b/packages/cdk/popper/src/composables/useTimer.ts new file mode 100644 index 000000000..3c8cb52b7 --- /dev/null +++ b/packages/cdk/popper/src/composables/useTimer.ts @@ -0,0 +1,26 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +export function useTimer(): { setTimer: (action: () => void, delay: number) => void; clearTimer: () => void } { + let timer: number | null = null + + const setTimer = (action: () => void, delay: number) => { + if (timer) { + clearTimeout(timer) + } + timer = setTimeout(action, delay) + } + + const clearTimer = () => { + if (timer) { + clearTimeout(timer) + timer = null + } + } + + return { setTimer, clearTimer } +} diff --git a/packages/cdk/popper/src/composables/useTriggerEvents.ts b/packages/cdk/popper/src/composables/useTriggerEvents.ts new file mode 100644 index 000000000..52d2836f3 --- /dev/null +++ b/packages/cdk/popper/src/composables/useTriggerEvents.ts @@ -0,0 +1,46 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { PopperOptions, PopperTriggerEvents } from '../types' + +import { type ComputedRef, computed } from 'vue' + +export function useTriggerEvents( + baseOptions: Required, + eventOptions: { visibility: ComputedRef; show(): void; hide(): void }, +): ComputedRef { + const { visibility, show, hide } = eventOptions + + const onMouseenter = () => show() + const onMouseleave = () => hide() + const onFocus = () => show() + const onBlur = () => hide() + + const onClick = () => { + const { trigger } = baseOptions + if (trigger === 'click') { + visibility.value ? hide() : show() + } else if (trigger === 'contextmenu') { + visibility.value && hide() + } + } + + const onContextmenu = (evt: Event) => { + evt.preventDefault() + show() + } + + const eventsMap = { + hover: { onMouseenter, onMouseleave }, + focus: { onFocus, onBlur }, + click: { onClick }, + contextmenu: { onClick, onContextmenu }, + manual: {}, + } + + return computed(() => eventsMap[baseOptions.trigger]) +} diff --git a/packages/cdk/popper/src/composables/useVisibility.ts b/packages/cdk/popper/src/composables/useVisibility.ts new file mode 100644 index 000000000..05e8be5d9 --- /dev/null +++ b/packages/cdk/popper/src/composables/useVisibility.ts @@ -0,0 +1,15 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { PopperOptions } from '../types' +import type { ComputedRef } from 'vue' + +import { computed } from 'vue' + +export function useVisibility(options: Required): ComputedRef { + return computed(() => !options.disabled && options.visible) +} diff --git a/packages/cdk/popper/src/convertOptions.ts b/packages/cdk/popper/src/convertOptions.ts new file mode 100644 index 000000000..dcea0574e --- /dev/null +++ b/packages/cdk/popper/src/convertOptions.ts @@ -0,0 +1,49 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { BaseOptions } from './composables/useOptions' +import type { PopperPlacement } from './types' + +import { kebabCase } from 'lodash-es' + +import { + type ComputePositionConfig, + type Middleware, + type Placement, + flip, + offset as offsetMiddleware, +} from '@floating-ui/dom' + +import { arrow } from './middlewares/arrow' +import { referenceHidden } from './middlewares/refenceHidden' +import { updatePlacement } from './middlewares/updatePlacement' + +export interface ExtraOptions { + arrowElement: HTMLElement | undefined + updatePlacement: (value: PopperPlacement) => void +} + +export function convertOptions(baseOptions: BaseOptions, extraOptions: ExtraOptions): ComputePositionConfig { + const { placement, strategy, middlewares, offset, autoAdjust } = baseOptions + const { arrowElement, updatePlacement: _updatePlacement } = extraOptions + + return { + placement: kebabCase(placement) as Placement, + strategy, + middleware: [ + !!arrowElement && arrow(arrowElement), + offsetMiddleware({ + mainAxis: offset[1], + crossAxis: offset[0], + }), + autoAdjust && flip({ padding: 4 }), + ...middlewares, + referenceHidden(), + updatePlacement(_updatePlacement), + ].filter(Boolean) as Middleware[], + } +} diff --git a/packages/cdk/popper/src/hooks.ts b/packages/cdk/popper/src/hooks.ts deleted file mode 100644 index 08e18a239..000000000 --- a/packages/cdk/popper/src/hooks.ts +++ /dev/null @@ -1,166 +0,0 @@ -/** - * @license - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE - */ - -import type { PopperEvents, PopperOptions, PopperPlacement, PopperTriggerEvents } from './types' -import type { ComputedRef } from 'vue' - -import { computed, reactive, ref, watch } from 'vue' - -import { NoopFunction, NoopObject } from '@idux/cdk/utils' - -const defaultDelay = 0 - -export function useReactiveOptions(options: PopperOptions): Required { - const { - allowEnter = true, - autoAdjust = true, - delay = defaultDelay, - disabled = false, - offset = [0, 0], - placement = 'top', - trigger = 'hover', - visible = false, - strategy = 'absolute', - modifiers = [], - onFirstUpdate = NoopFunction, - } = options - - return reactive({ - allowEnter, - autoAdjust, - delay, - disabled, - offset, - placement, - trigger, - visible, - strategy, - modifiers, - onFirstUpdate, - }) -} - -export type BaseOptions = Pick< - Required, - 'placement' | 'strategy' | 'onFirstUpdate' | 'modifiers' | 'offset' | 'autoAdjust' -> - -export function useBaseOptions(state: Required): ComputedRef { - return computed(() => { - const { placement, strategy, onFirstUpdate, modifiers, offset, autoAdjust } = state - return { placement, strategy, onFirstUpdate, modifiers, offset, autoAdjust } - }) -} - -export function useVisibility(state: Required): ComputedRef { - return computed(() => !state.disabled && state.visible) -} - -export function usePlacement(state: Required): { - placement: ComputedRef - updatePlacement: (value: PopperPlacement) => void -} { - const _placement = ref(state.placement) - - const updatePlacement = (value: PopperPlacement) => { - _placement.value = value - } - - watch(() => state.placement, updatePlacement) - - const placement = computed(() => _placement.value) - - return { placement, updatePlacement } -} - -export function useDelay(state: Required): ComputedRef<{ show: number; hide: number }> { - const convertDelay = (delay: number | [number | null, number | null]) => { - if (Array.isArray(delay)) { - const [show, hide] = delay - return { show: show ?? defaultDelay, hide: hide ?? defaultDelay } - } - return { show: delay, hide: delay } - } - - return computed(() => convertDelay(state.delay)) -} - -export function useTimer(): { setTimer: (action: () => void, delay: number) => void; clearTimer: () => void } { - let timer: number | null = null - - const setTimer = (action: () => void, delay: number) => { - if (timer) { - clearTimeout(timer) - } - timer = setTimeout(action, delay) - } - - const clearTimer = () => { - if (timer) { - clearTimeout(timer) - timer = null - } - } - - return { setTimer, clearTimer } -} - -export function useTriggerEvents( - baseOptions: Required, - eventOptions: { visibility: ComputedRef; show(): void; hide(): void }, -): ComputedRef { - const { visibility, show, hide } = eventOptions - - const onMouseenter = () => show() - const onMouseleave = () => hide() - const onFocus = () => show() - const onBlur = () => hide() - - const onClick = () => { - const { trigger } = baseOptions - if (trigger === 'click') { - visibility.value ? hide() : show() - } else if (trigger === 'contextmenu') { - visibility.value && hide() - } - } - - const onContextmenu = (evt: Event) => { - evt.preventDefault() - show() - } - - const eventsMap = { - hover: { onMouseenter, onMouseleave }, - focus: { onFocus, onBlur }, - click: { onClick }, - contextmenu: { onClick, onContextmenu }, - manual: {}, - } - - return computed(() => eventsMap[baseOptions.trigger]) -} - -export function usePopperEvents( - baseOptions: Required, - eventOptions: { show(): void; hide(): void }, -): ComputedRef { - const { show, hide } = eventOptions - - const onMouseenter = () => show() - const onMouseleave = () => hide() - - const eventsMap = { - click: NoopObject, - focus: NoopObject, - hover: { onMouseenter, onMouseleave }, - contextmenu: NoopObject, - manual: NoopObject, - } - - return computed(() => (baseOptions.allowEnter ? eventsMap[baseOptions.trigger] : NoopObject)) -} diff --git a/packages/cdk/popper/src/middlewares/arrow.ts b/packages/cdk/popper/src/middlewares/arrow.ts new file mode 100644 index 000000000..f0780f91f --- /dev/null +++ b/packages/cdk/popper/src/middlewares/arrow.ts @@ -0,0 +1,30 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { type Middleware, arrow as _arrow } from '@floating-ui/dom' + +export function arrow(arrowElement: HTMLElement): Middleware { + const { fn: arrowFn, ...rest } = _arrow({ element: arrowElement, padding: 4 }) + + return { + ...rest, + name: 'IDUX_arrow', + async fn(middlewareArguments) { + const res = (await arrowFn(middlewareArguments)) as { data: { x: number; y: number; centerOffset: number } } + const { + data: { x, y }, + } = res + + Object.assign(arrowElement.style, { + left: x != null ? `${x}px` : '', + top: y != null ? `${y}px` : '', + }) + + return res + }, + } +} diff --git a/packages/cdk/popper/src/middlewares/refenceHidden.ts b/packages/cdk/popper/src/middlewares/refenceHidden.ts new file mode 100644 index 000000000..d6a7c9e7f --- /dev/null +++ b/packages/cdk/popper/src/middlewares/refenceHidden.ts @@ -0,0 +1,35 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { type Middleware, hide } from '@floating-ui/dom' + +export function referenceHidden(): Middleware { + const { fn: hideFn, ...rest } = hide() + + return { + ...rest, + name: 'IDUX_referenceHidden', + async fn(middlewareArguments) { + const { + elements: { floating }, + } = middlewareArguments + + const res = (await hideFn(middlewareArguments)) as { data: { referenceHidden: boolean } } + const { + data: { referenceHidden }, + } = res + + if (referenceHidden) { + floating.setAttribute('data-popper-reference-hidden', '') + } else { + floating.removeAttribute('data-popper-reference-hidden') + } + + return res + }, + } +} diff --git a/packages/cdk/popper/src/middlewares/updatePlacement.ts b/packages/cdk/popper/src/middlewares/updatePlacement.ts new file mode 100644 index 000000000..3842dcbb1 --- /dev/null +++ b/packages/cdk/popper/src/middlewares/updatePlacement.ts @@ -0,0 +1,27 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { PopperPlacement } from '../types' +import type { Middleware } from '@floating-ui/dom' + +import { camelCase } from 'lodash-es' + +export function updatePlacement(updatePlacement: (value: PopperPlacement) => void): Middleware { + return { + name: 'IDUX_updatePlacement', + fn(middlewareArguments) { + const { + placement, + elements: { floating }, + } = middlewareArguments + updatePlacement(camelCase(placement) as PopperPlacement) + + floating.setAttribute('data-popper-placement', placement) + return middlewareArguments + }, + } +} diff --git a/packages/cdk/popper/src/types.ts b/packages/cdk/popper/src/types.ts index 92ab057b7..50c25ede1 100644 --- a/packages/cdk/popper/src/types.ts +++ b/packages/cdk/popper/src/types.ts @@ -5,10 +5,10 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import type { Modifier, PositioningStrategy, State } from '@popperjs/core' +import type { Middleware, Strategy } from '@floating-ui/dom' import type { ComponentPublicInstance, ComputedRef, Ref } from 'vue' -export type PopperPositioningStrategy = PositioningStrategy +export type PopperStrategy = Strategy export type PopperTrigger = 'click' | 'hover' | 'focus' | 'contextmenu' | 'manual' export type PopperElement = ComponentPublicInstance | HTMLElement export type PopperPlacement = @@ -87,10 +87,8 @@ export interface PopperOptions { */ visible?: boolean - strategy?: PopperPositioningStrategy - // eslint-disable-next-line @typescript-eslint/no-explicit-any - modifiers?: Array>> - onFirstUpdate?: (state: Partial) => void + strategy?: PopperStrategy + middlewares?: Array } export interface PopperInstance { @@ -113,10 +111,6 @@ export interface PopperInstance): void - /** - * Force update popper - */ - forceUpdate(): void /** * Destroy the popper. * The life cycle of the popper will enter beforeDestroy. diff --git a/packages/cdk/popper/src/usePopper.ts b/packages/cdk/popper/src/usePopper.ts index 16bb12173..6af497d3a 100644 --- a/packages/cdk/popper/src/usePopper.ts +++ b/packages/cdk/popper/src/usePopper.ts @@ -5,50 +5,50 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import { type WatchStopHandle, ref, watch } from 'vue' +import type { PopperElement, PopperInstance, PopperOptions } from './types' -import { type Instance, createPopper } from '@popperjs/core' -import { isEqual } from 'lodash-es' +import { computed, ref, watch } from 'vue' import { convertElement } from '@idux/cdk/utils' -import { - useBaseOptions, - useDelay, - usePlacement, - usePopperEvents, - useReactiveOptions, - useTimer, - useTriggerEvents, - useVisibility, -} from './hooks' -import { type PopperElement, type PopperInstance, type PopperOptions } from './types' -import { convertOptions } from './utils' +import { useDelay } from './composables/useDelay' +import { type Instance, useInstance } from './composables/useInstance' +import { useBaseOptions, usePopperOptions } from './composables/useOptions' +import { usePlacement } from './composables/usePlacement' +import { usePopperEvents } from './composables/usePopperEvents' +import { useTimer } from './composables/useTimer' +import { useTriggerEvents } from './composables/useTriggerEvents' +import { useVisibility } from './composables/useVisibility' +import { convertOptions } from './convertOptions' export function usePopper( options: PopperOptions = {}, ): PopperInstance { - let popperInstance: Instance | null = null + let popperInstance: Instance | null const triggerRef = ref() const popperRef = ref() const arrowRef = ref() - const state = useReactiveOptions(options) - const baseOptions = useBaseOptions(state) - const visibility = useVisibility(state) - const { placement, updatePlacement } = usePlacement(state) - const delay = useDelay(state) + const { popperOptions, updateOptions } = usePopperOptions(options) + const baseOptions = useBaseOptions(popperOptions) + const convertedOptions = computed(() => + convertOptions(baseOptions.value, { arrowElement: convertElement(arrowRef), updatePlacement }), + ) + + const visibility = useVisibility(popperOptions) + const { placement, updatePlacement } = usePlacement(popperOptions) + const delay = useDelay(popperOptions) const { setTimer, clearTimer } = useTimer() - const triggerEvents = useTriggerEvents(state, { visibility, show, hide }) - const popperEvents = usePopperEvents(state, { show, hide }) + const triggerEvents = useTriggerEvents(popperOptions, { visibility, show, hide }) + const popperEvents = usePopperEvents(popperOptions, { show, hide }) function toggle(visible: boolean, delay: number): void { clearTimer() const action = () => { - state.visible = visible + popperOptions.visible = visible } if (delay > 0) { setTimer(action, delay) @@ -67,21 +67,16 @@ export function usePopper): void { if (options) { - Object.entries(options).forEach(([key, value]) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (value !== undefined && !isEqual(value, (state as any)[key])) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(state as any)[key] = value - } - }) + updateOptions(options) return } popperInstance?.update() } - function forceUpdate(): void { - popperInstance?.forceUpdate() + function initialize() { + destroy() + popperInstance = useInstance(triggerRef, popperRef, convertedOptions) } function destroy(): void { @@ -93,29 +88,6 @@ export function usePopper { - const triggerElement = convertElement(trigger) - const popperElement = convertElement(popper) - if (!triggerElement || !popperElement) { - return - } - destroy() - const options = convertOptions(baseOptions.value, { arrowElement: convertElement(arrowRef), updatePlacement }) - popperInstance = createPopper(triggerElement, popperElement, options) - }, - { immediate: true }, - ) - } - watch(visibility, value => { if (value) { clearTimer() @@ -123,16 +95,6 @@ export function usePopper { - popperInstance?.setOptions( - convertOptions(currBaseOptions, { arrowElement: convertElement(arrowElement), updatePlacement }), - ) - }, - { flush: 'post' }, - ) - return { visibility, placement, @@ -145,7 +107,6 @@ export function usePopper void -} - -export function convertOptions(baseOptions: BaseOptions, extraOptions: ExtraOptions): Options { - const { placement, strategy, onFirstUpdate, modifiers, offset, autoAdjust } = baseOptions - const { arrowElement, updatePlacement } = extraOptions - - return { - placement: kebabCase(placement) as Placement, - strategy, - onFirstUpdate, - modifiers: [ - { name: 'hide', enabled: true }, - { name: 'offset', options: { offset } }, - { name: 'flip', enabled: autoAdjust, options: { padding: 4 } }, - { name: 'arrow', enabled: !!arrowElement, options: { element: arrowElement, padding: 4 } }, - { - name: 'IDUX_updatePlacement', - enabled: true, - phase: 'beforeWrite', - requires: ['computeStyles'], - fn: ({ state }) => updatePlacement(camelCase(state.placement) as PopperPlacement), - }, - ...modifiers, - ], - } -} diff --git a/packages/components/_private/overlay/src/Overlay.tsx b/packages/components/_private/overlay/src/Overlay.tsx index fc14b88a3..ced358869 100644 --- a/packages/components/_private/overlay/src/Overlay.tsx +++ b/packages/components/_private/overlay/src/Overlay.tsx @@ -51,7 +51,6 @@ export default defineComponent({ placement, initialize, update, - forceUpdate, show, hide, destroy, @@ -79,7 +78,6 @@ export default defineComponent({ expose({ updatePopper: update, - forceUpdatePopper: forceUpdate, }) const handleClickOutside = (evt: Event) => { diff --git a/packages/components/_private/overlay/src/types.ts b/packages/components/_private/overlay/src/types.ts index 7295bbf99..7bd318c41 100644 --- a/packages/components/_private/overlay/src/types.ts +++ b/packages/components/_private/overlay/src/types.ts @@ -62,7 +62,6 @@ export const overlayProps = { export interface OverlayBindings { updatePopper: (options?: Partial) => void - forceUpdatePopper: () => void } export type OverlayProps = ExtractInnerPropTypes diff --git a/packages/components/_private/overlay/style/index.less b/packages/components/_private/overlay/style/index.less index 1cc6a98c5..3b3d65ae9 100644 --- a/packages/components/_private/overlay/style/index.less +++ b/packages/components/_private/overlay/style/index.less @@ -1,6 +1,7 @@ .@{overlay-prefix} { &-arrow { + position: absolute; width: @overlay-arrow-size * 2; height: @overlay-arrow-size * 2; @@ -14,10 +15,9 @@ &[data-popper-placement^='top'] { .@{overlay-prefix}-arrow { - bottom: 0; + bottom: -@overlay-arrow-size * 2; &::before { - bottom: -@overlay-arrow-size; left: 0; border-width: @overlay-arrow-size @overlay-arrow-size 0; border-top-color: initial; @@ -28,10 +28,9 @@ &[data-popper-placement^='bottom'] { .@{overlay-prefix}-arrow { - top: 0; + top: -@overlay-arrow-size; &::before { - top: -@overlay-arrow-size; left: 0; border-width: 0 @overlay-arrow-size @overlay-arrow-size; border-bottom-color: initial; @@ -42,12 +41,11 @@ &[data-popper-placement^='left'] { .@{overlay-prefix}-arrow { - right: 0; + right: -@overlay-arrow-size * 2; &::before { border-width: @overlay-arrow-size 0 @overlay-arrow-size @overlay-arrow-size; border-left-color: initial; - right: -@overlay-arrow-size; transform-origin: center left; } } @@ -55,10 +53,9 @@ &[data-popper-placement^='right'] { .@{overlay-prefix}-arrow { - left: 0; + left: -@overlay-arrow-size; &::before { - left: -@overlay-arrow-size; border-width: @overlay-arrow-size @overlay-arrow-size @overlay-arrow-size 0; border-right-color: initial; transform-origin: center right; diff --git a/packages/components/popover/__tests__/__snapshots__/popover.spec.ts.snap b/packages/components/popover/__tests__/__snapshots__/popover.spec.ts.snap index 08d352e32..8f2631060 100644 --- a/packages/components/popover/__tests__/__snapshots__/popover.spec.ts.snap +++ b/packages/components/popover/__tests__/__snapshots__/popover.spec.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1 -exports[`Popover > header props work 1`] = `"
TitlesubTitle
"`; +exports[`Popover > header props work 1`] = `"
TitlesubTitle
"`; exports[`Popover > render work 1`] = ` "
trigger
diff --git a/packages/pro/config/src/defaultConfig.ts b/packages/pro/config/src/defaultConfig.ts index d83f721fd..25910ac01 100644 --- a/packages/pro/config/src/defaultConfig.ts +++ b/packages/pro/config/src/defaultConfig.ts @@ -47,6 +47,7 @@ export const defaultConfig: ProGlobalConfig = { resize: 'none', size: 'md', showCount: false, + trim: false, }, search: { clearable: true, diff --git a/packages/pro/config/src/types.ts b/packages/pro/config/src/types.ts index 172e85150..595156656 100644 --- a/packages/pro/config/src/types.ts +++ b/packages/pro/config/src/types.ts @@ -96,4 +96,5 @@ export interface ProTextareaConfig { resize: TextareaResize size: FormSize showCount: boolean + trim: boolean } diff --git a/packages/pro/textarea/demo/Form.vue b/packages/pro/textarea/demo/Form.vue index b2b6d6e3c..f6a1a46ca 100644 --- a/packages/pro/textarea/demo/Form.vue +++ b/packages/pro/textarea/demo/Form.vue @@ -64,9 +64,4 @@ const formGroup = useFormGroup({ ruleName: ['', [required]], IP: ['', [IPValidator]], }) -// const onChange = (value: string | undefined, oldValue: string | undefined) => { -// if (value?.split('\n').length !== oldValue?.split('\n').length) { -// errors.value = getIpValidationResults(oldValue) -// } -// } diff --git a/scripts/gulp/build/rollup.ts b/scripts/gulp/build/rollup.ts index 9aca3e23f..552276290 100644 --- a/scripts/gulp/build/rollup.ts +++ b/scripts/gulp/build/rollup.ts @@ -22,7 +22,7 @@ interface Options { minify?: boolean } -const externalDeps = ['vue', '@vue', '@idux', '@popperjs/core', 'date-fns', 'lodash-es', 'ajv'] +const externalDeps = ['vue', '@vue', '@idux', '@floating-ui/dom', 'date-fns', 'lodash-es', 'ajv'] export const getRollupSingleOptions = (options: Options): RollupOptions => { const { targetDirname, distDirname, compName = '', minify = false } = options