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`] = `""`;
+exports[`Popover > header props work 1`] = `""`;
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