@opentiny/vue-hooks 3.21.0 → 3.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +3 -0
  2. package/README.zh-CN.md +3 -0
  3. package/dist/index.d.ts +22 -0
  4. package/dist/index.js +24 -0
  5. package/{src → dist/src}/use-floating.d.ts +7 -2
  6. package/dist/src/use-floating.js +141 -0
  7. package/{src → dist/src}/use-lazy-show.d.ts +6 -3
  8. package/dist/src/use-lazy-show.js +10 -0
  9. package/dist/src/useEventListener.d.ts +15 -0
  10. package/dist/src/useEventListener.js +31 -0
  11. package/dist/src/useInstanceSlots.d.ts +6 -0
  12. package/dist/src/useInstanceSlots.js +14 -0
  13. package/dist/src/useRect.d.ts +1 -0
  14. package/dist/src/useRect.js +18 -0
  15. package/dist/src/useRelation.d.ts +31 -0
  16. package/dist/src/useRelation.js +54 -0
  17. package/dist/src/useTouch.d.ts +15 -0
  18. package/dist/src/useTouch.js +32 -0
  19. package/dist/src/useUserAgent.d.ts +3 -0
  20. package/dist/src/useUserAgent.js +10 -0
  21. package/dist/src/useWindowSize.d.ts +4 -0
  22. package/dist/src/useWindowSize.js +14 -0
  23. package/{index.d.ts → dist/src/vue-emitter.d.ts} +5 -3
  24. package/dist/src/vue-popper.js +85 -0
  25. package/dist/src/vue-popup.d.ts +18 -0
  26. package/dist/src/vue-popup.js +69 -0
  27. package/index.ts +23 -0
  28. package/package.json +15 -9
  29. package/src/use-floating.ts +409 -0
  30. package/src/use-lazy-show.ts +20 -0
  31. package/src/useEventListener.ts +65 -0
  32. package/src/useInstanceSlots.ts +29 -0
  33. package/src/useRect.ts +25 -0
  34. package/src/useRelation.ts +130 -0
  35. package/src/useTouch.ts +74 -0
  36. package/src/useUserAgent.ts +18 -0
  37. package/src/useWindowSize.ts +25 -0
  38. package/src/vue-emitter.ts +49 -0
  39. package/src/vue-popper.ts +277 -0
  40. package/src/vue-popup.ts +196 -0
  41. package/tsconfig.json +25 -0
  42. package/tsconfig.node.json +10 -0
  43. package/vite.config.ts +23 -0
  44. package/index.js +0 -2258
@@ -0,0 +1,130 @@
1
+ import { noop } from '@opentiny/utils'
2
+ import { onMountedOrActivated as createHook } from './useEventListener'
3
+
4
+ /**
5
+ * 处理组件嵌套的组合式 API
6
+ * relationKey 关系树上的父子组件使用同一个关系名称
7
+ * relationContainer 子组件顺序由关系容器确定,由根组件提供,可以不使用,子组件顺序就是组件创建顺序
8
+ * onChange 子组件顺序改变后的回调处理,由根组件提供,可以不使用
9
+ * childrenKey 在组件关系树上的所有实例中定义的子组件引用名称,默认是 instanceChildren
10
+ * delivery 根组件向下分发的内容
11
+ */
12
+ export const useRelation =
13
+ ({
14
+ computed,
15
+ getCurrentInstance,
16
+ inject,
17
+ markRaw,
18
+ nextTick,
19
+ onMounted,
20
+ onActivated,
21
+ onUnmounted,
22
+ provide,
23
+ reactive,
24
+ toRef
25
+ }) =>
26
+ ({ relationKey, relationContainer, onChange, childrenKey, delivery } = {}) => {
27
+ if (!relationKey) {
28
+ throw new Error('[TINY Error]<relationKey> must exist.')
29
+ }
30
+
31
+ const instance = getCurrentInstance()
32
+ const state = reactive({ children: [], indexInParent: -1 })
33
+ const injectValue = inject(relationKey, null)
34
+ // 收集所有的子组件刷新回调
35
+ let callbacks = []
36
+
37
+ if (injectValue) {
38
+ const { link, unlink, callbacks: injectCbs, childrenKey: injectKey, delivery: injectDelivery } = injectValue
39
+
40
+ callbacks = injectCbs
41
+ childrenKey = childrenKey || injectKey || 'instanceChildren'
42
+ delivery = injectDelivery
43
+
44
+ state.indexInParent = link(instance)
45
+
46
+ onUnmounted(() => unlink(instance))
47
+ } else {
48
+ childrenKey = childrenKey || 'instanceChildren'
49
+
50
+ const onMountedOrActivated = createHook({ onMounted, onActivated, nextTick })
51
+ const changeHandler = onChange ? () => nextTick(onChange) : noop
52
+
53
+ let relationMO
54
+
55
+ nextTick(() => {
56
+ // 在 mounted 之后,如果表示子组件关系的 dom 元素存在,就创建 MutationObserver 观察它的子树改变
57
+ const targetNode = typeof relationContainer === 'function' ? relationContainer() : relationContainer
58
+
59
+ if (targetNode) {
60
+ relationMO = new MutationObserver((mutationList, observer) => {
61
+ const flattenNodes = []
62
+ // 对关系容器 dom 子树进行平铺处理
63
+ flattenChildNodes(targetNode.childNodes, flattenNodes)
64
+ // 使用平铺的 dom 子树更新子组件顺序
65
+ callbacks.forEach((callback) => callback(flattenNodes, mutationList, observer))
66
+ // 执行后续组件 change 处理
67
+ changeHandler()
68
+ })
69
+
70
+ relationMO.observe(targetNode, { attributes: true, childList: true, subtree: true })
71
+ }
72
+ })
73
+
74
+ onMountedOrActivated(() => changeHandler())
75
+
76
+ onUnmounted(() => {
77
+ if (relationMO) {
78
+ relationMO.disconnect()
79
+ relationMO = null
80
+ }
81
+
82
+ callbacks = null
83
+ })
84
+ }
85
+
86
+ const link = (child) => {
87
+ const childPublic = child.proxy
88
+
89
+ state.children.push(markRaw(childPublic))
90
+
91
+ return computed(() => state.children.indexOf(childPublic))
92
+ }
93
+
94
+ const unlink = (child) => {
95
+ const index = state.children.indexOf(child.proxy)
96
+
97
+ if (index > -1) {
98
+ state.children.splice(index, 1)
99
+ }
100
+ }
101
+
102
+ // 刷新子组件顺序
103
+ callbacks.push((flattenNodes) => sortPublicInstances(state.children, flattenNodes))
104
+
105
+ provide(relationKey, { link, unlink, callbacks, childrenKey, delivery })
106
+
107
+ // 在 Public Instance 上定义子组件数组,并且在组件卸载时移除
108
+ Object.defineProperty(instance.proxy, childrenKey, { configurable: true, get: () => state.children })
109
+
110
+ onUnmounted(() => delete instance.proxy[childrenKey])
111
+
112
+ // 返回子组件数组 ref、在父级中的位置索引 ref 和接收到的分发内容
113
+ return { children: toRef(state, 'children'), index: toRef(state, 'indexInParent'), delivery }
114
+ }
115
+
116
+ const flattenChildNodes = (childNodes, result) => {
117
+ if (childNodes.length) {
118
+ childNodes.forEach((childNode) => {
119
+ result.push(childNode)
120
+
121
+ if (childNode.childNodes) {
122
+ flattenChildNodes(childNode.childNodes, result)
123
+ }
124
+ })
125
+ }
126
+ }
127
+
128
+ const sortPublicInstances = (instances, flattenNodes) => {
129
+ instances.sort((a, b) => flattenNodes.indexOf(a.$el) - flattenNodes.indexOf(b.$el))
130
+ }
@@ -0,0 +1,74 @@
1
+ const TAP_OFFSET = 5
2
+
3
+ const getDirection = (x, y) => {
4
+ if (x > y) return 'horizontal'
5
+ if (y > x) return 'vertical'
6
+ return ''
7
+ }
8
+
9
+ const touchEvent = (event) => event.touches[0]
10
+
11
+ export const useTouch = (ref) => () => {
12
+ const startX = ref(0)
13
+ const startY = ref(0)
14
+ const deltaX = ref(0)
15
+ const deltaY = ref(0)
16
+ const offsetX = ref(0)
17
+ const offsetY = ref(0)
18
+ const direction = ref('')
19
+ const isTap = ref(true)
20
+
21
+ const isVertical = () => direction.value === 'vertical'
22
+ const isHorizontal = () => direction.value === 'horizontal'
23
+
24
+ const reset = () => {
25
+ deltaX.value = 0
26
+ deltaY.value = 0
27
+ offsetX.value = 0
28
+ offsetY.value = 0
29
+ direction.value = ''
30
+ isTap.value = true
31
+ }
32
+
33
+ const start = (event) => {
34
+ reset()
35
+ const touch = touchEvent(event)
36
+ startX.value = touch.clientX
37
+ startY.value = touch.clientY
38
+ }
39
+
40
+ const move = (event) => {
41
+ const touch = touchEvent(event)
42
+ // safari back will set clientX to negative number
43
+ deltaX.value = (touch.clientX < 0 ? 0 : touch.clientX) - startX.value
44
+ deltaY.value = touch.clientY - startY.value
45
+ offsetX.value = Math.abs(deltaX.value)
46
+ offsetY.value = Math.abs(deltaY.value)
47
+
48
+ // lock direction when distance is greater than a certain value
49
+ const LOCK_DIRECTION_DISTANCE = 10
50
+ if (!direction.value || (offsetX.value < LOCK_DIRECTION_DISTANCE && offsetY.value < LOCK_DIRECTION_DISTANCE)) {
51
+ direction.value = getDirection(offsetX.value, offsetY.value)
52
+ }
53
+
54
+ if (isTap.value && (offsetX.value > TAP_OFFSET || offsetY.value > TAP_OFFSET)) {
55
+ isTap.value = false
56
+ }
57
+ }
58
+
59
+ return {
60
+ move,
61
+ start,
62
+ reset,
63
+ isVertical,
64
+ isHorizontal,
65
+ startX,
66
+ startY,
67
+ deltaX,
68
+ deltaY,
69
+ offsetX,
70
+ offsetY,
71
+ direction,
72
+ isTap
73
+ }
74
+ }
@@ -0,0 +1,18 @@
1
+ import { isServer } from '@opentiny/utils'
2
+
3
+ function getIsIOS() {
4
+ if (isServer) return false
5
+ return (
6
+ window.navigator &&
7
+ window.navigator.userAgent &&
8
+ (/iP(?:ad|hone|od)/.test(window.navigator.userAgent) ||
9
+ // The new iPad Pro Gen3 does not identify itself as iPad, but as Macintosh.
10
+ // https://github.com/vueuse/vueuse/issues/3577
11
+ (window.navigator.maxTouchPoints > 2 && /iPad|Macintosh/.test(window.navigator.userAgent)))
12
+ )
13
+ }
14
+
15
+ export const useUserAgent = () => {
16
+ const isIOS = getIsIOS()
17
+ return { isIOS }
18
+ }
@@ -0,0 +1,25 @@
1
+ import { on, isServer } from '@opentiny/utils'
2
+
3
+ let width
4
+ let height
5
+
6
+ export const useWindowSize = (ref) => () => {
7
+ if (!width) {
8
+ width = ref(0)
9
+ height = ref(0)
10
+
11
+ if (!isServer) {
12
+ const update = () => {
13
+ width.value = window.innerWidth
14
+ height.value = window.innerHeight
15
+ }
16
+
17
+ update()
18
+
19
+ on(window, 'resize', update, { passive: true })
20
+ on(window, 'orientationchange', update, { passive: true })
21
+ }
22
+ }
23
+
24
+ return { width, height }
25
+ }
@@ -0,0 +1,49 @@
1
+ /* eslint-disable prefer-spread */
2
+ /**
3
+ * Copyright (c) 2022 - present TinyVue Authors.
4
+ * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license.
7
+ *
8
+ * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
9
+ * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
10
+ * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
11
+ *
12
+ */
13
+
14
+ // 全局未引用 ,待移除
15
+ export default (vm) => {
16
+ const broadcast = (vm, componentName, eventName, params) => {
17
+ vm.$children.forEach((child) => {
18
+ const name = child.$options.componentName
19
+
20
+ if (name === componentName) {
21
+ child.$emit(eventName, params)
22
+ } else {
23
+ broadcast(child, componentName, eventName, params)
24
+ }
25
+ })
26
+ }
27
+
28
+ return {
29
+ dispatch(componentName, eventName, params) {
30
+ let parent = vm.$parent || vm.$root
31
+ let name = parent.$options.componentName
32
+
33
+ while (parent && !parent.$attrs.novalid && (!name || name !== componentName)) {
34
+ parent = parent.$parent
35
+
36
+ if (parent) {
37
+ name = parent.$options.componentName
38
+ }
39
+ }
40
+
41
+ if (parent) {
42
+ parent.$emit.apply(parent, [eventName].concat(params))
43
+ }
44
+ },
45
+ broadcast(componentName, eventName, params) {
46
+ broadcast(vm, componentName, eventName, params)
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Copyright (c) 2022 - present TinyVue Authors.
3
+ * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license.
6
+ *
7
+ * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
8
+ * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
9
+ * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
10
+ *
11
+ */
12
+
13
+ import { PopupManager, PopperJS, on, off, isDisplayNone } from '@opentiny/utils'
14
+
15
+ // todo
16
+ import type { ISharedRenderlessFunctionParams } from 'types/shared.type'
17
+
18
+ import { isServer } from '@opentiny/utils'
19
+
20
+ export interface IPopperState {
21
+ popperJS: Popper
22
+ appended: boolean
23
+ popperElm: HTMLElement
24
+ showPopper: boolean
25
+ referenceElm: HTMLElement
26
+ currentPlacement: string
27
+ }
28
+
29
+ type IPopperInputParams = ISharedRenderlessFunctionParams<never> & {
30
+ api: { open: Function; close: Function }
31
+ state: IPopperState
32
+ props: any
33
+ }
34
+
35
+ /** 给 popper 的click添加stop, 阻止冒泡 */
36
+ const stop = (e: Event) => e.stopPropagation()
37
+
38
+ // 由于多个组件传入reference元素的方式不同,所以这里从多处查找。
39
+ const getReference = ({ state, props, vm, slots }: Pick<IPopperInputParams, 'state' | 'props' | 'vm' | 'slots'>) => {
40
+ let reference =
41
+ state.referenceElm || props.reference || (vm.$refs.reference && vm.$refs.reference.$el) || vm.$refs.reference
42
+
43
+ if (!reference && slots.reference && slots.reference()[0]) {
44
+ state.referenceElm = slots.reference()[0].elm || slots.reference()[0].el
45
+ reference = state.referenceElm
46
+ }
47
+
48
+ return reference
49
+ }
50
+
51
+ const getReferMaxZIndex = (reference) => {
52
+ if (!reference || !reference.nodeType) return
53
+
54
+ let getZIndex = (dom) => parseInt(window.getComputedStyle(dom).zIndex, 10) || 0
55
+ let max = getZIndex(reference)
56
+ let z
57
+
58
+ do {
59
+ reference = reference.parentNode
60
+
61
+ // 处理遇到shadowRoot的情况
62
+ if (reference && reference instanceof ShadowRoot && reference.host) {
63
+ reference = reference.host
64
+ }
65
+
66
+ if (reference) {
67
+ z = getZIndex(reference)
68
+ } else {
69
+ break
70
+ }
71
+
72
+ max = z > max ? z : max
73
+ } while (reference !== document.body)
74
+
75
+ return max + 1 + ''
76
+ }
77
+ // 历史原因,暂时先命名为userPopper, 以后统一替换
78
+ export const userPopper = (options: IPopperInputParams) => {
79
+ const {
80
+ parent,
81
+ emit,
82
+ nextTick,
83
+ onBeforeUnmount,
84
+ onDeactivated,
85
+ props,
86
+ watch,
87
+ reactive,
88
+ vm,
89
+ slots,
90
+ toRefs,
91
+ popperVmRef
92
+ } = options
93
+ const state = reactive<IPopperState>({
94
+ popperJS: null as any,
95
+ appended: false, // arrow 是否添加
96
+ popperElm: null as any,
97
+ showPopper: props.manual ? Boolean(props.modelValue) : false,
98
+ referenceElm: null as any,
99
+ currentPlacement: ''
100
+ })
101
+
102
+ /** 创建箭头函数 */
103
+ const appendArrow = (el: HTMLElement) => {
104
+ if (state.appended) {
105
+ return
106
+ }
107
+
108
+ state.appended = true
109
+ const div = document.createElement('div')
110
+
111
+ div.setAttribute('x-arrow', '')
112
+ div.className = 'popper__arrow'
113
+ el.appendChild(div)
114
+ }
115
+
116
+ // 如果触发源是隐藏的,其弹出层也设置为隐藏。组件可以通过 props.popperOptions.followReferenceHide = true/false来控制
117
+ const followHide = (popperInstance: PopperJS) => {
118
+ const { followReferenceHide = true } = props?.popperOptions || {}
119
+ const { _popper: popper, _reference: reference } = popperInstance
120
+
121
+ if (followReferenceHide && isDisplayNone(reference)) {
122
+ popper.style.display = 'none'
123
+ }
124
+ }
125
+
126
+ const nextZIndex = (reference) => {
127
+ return props.zIndex === 'relative' ? getReferMaxZIndex(reference) : PopupManager.nextZIndex()
128
+ }
129
+
130
+ const createPopper = (dom) => {
131
+ if (isServer) {
132
+ return
133
+ }
134
+
135
+ state.currentPlacement = state.currentPlacement || props.placement
136
+
137
+ if (!/^(top|bottom|left|right)(-start|-end)?$/g.test(state.currentPlacement)) {
138
+ return
139
+ }
140
+
141
+ const options = props.popperOptions || { gpuAcceleration: false }
142
+ state.popperElm = state.popperElm || props.popper || vm.$refs.popper || popperVmRef.popper || dom
143
+ const popper = state.popperElm
144
+ let reference = getReference({ state, props, vm, slots })
145
+
146
+ if (!popper || !reference || reference.nodeType !== Node.ELEMENT_NODE) {
147
+ return
148
+ }
149
+
150
+ if (props.visibleArrow) {
151
+ appendArrow(popper)
152
+ }
153
+
154
+ // 使用的组件比较多,所以 appendToBody popperAppendToBody 这2个属性都要监听
155
+ if (props.appendToBody || props.popperAppendToBody) {
156
+ document.body.appendChild(state.popperElm)
157
+ } else {
158
+ // 只有tooltip 传入parent了
159
+ parent && parent.$el && parent.$el.appendChild(state.popperElm)
160
+ options.forceAbsolute = true
161
+ }
162
+
163
+ options.placement = state.currentPlacement
164
+ options.offset = props.offset || 0
165
+ options.arrowOffset = props.arrowOffset || 0
166
+ options.adjustArrow = props.adjustArrow || false
167
+ options.appendToBody = props.appendToBody || props.popperAppendToBody
168
+
169
+ // 创建一个popperJS, 内部会立即调用一次update() 并 applyStyle等操作
170
+ state.popperJS = new PopperJS(reference, popper, options)
171
+ // 1、所有使用vue-popper的都有该事件;2、有的组件会多次触发 created
172
+ emit('created', state)
173
+
174
+ if (typeof options.onUpdate === 'function') {
175
+ state.popperJS.onUpdate(options.onUpdate)
176
+ }
177
+
178
+ state.popperJS._popper.style.zIndex = nextZIndex(state.popperJS._reference)
179
+ followHide(state.popperJS)
180
+ on(state.popperElm, 'click', stop)
181
+ }
182
+
183
+ /** 第一次 updatePopper 的时候,才真正执行创建
184
+ * popperElmOrTrue===true的场景仅在select组件动态更新面版时,不更新zIndex
185
+ */
186
+ const updatePopper = (popperElmOrTrue?: HTMLElement) => {
187
+ if (popperElmOrTrue && popperElmOrTrue !== true) {
188
+ state.popperElm = popperElmOrTrue
189
+ }
190
+
191
+ const popperJS = state.popperJS
192
+ if (popperJS) {
193
+ // Tiny 新增,在动态切换renference时,需要实时获取最新的触发源
194
+ popperJS._reference = getReference({ state, props, vm, slots })
195
+ popperJS.update()
196
+
197
+ // 每次递增 z-index
198
+ if (popperJS._popper && popperElmOrTrue !== true) {
199
+ popperJS._popper.style.zIndex = nextZIndex(popperJS._reference)
200
+ followHide(state.popperJS)
201
+ }
202
+ } else {
203
+ createPopper(popperElmOrTrue && popperElmOrTrue !== true ? popperElmOrTrue : undefined)
204
+ }
205
+ }
206
+
207
+ /** 调用state.popperJS.destroy()。 默认不会移除popper dom
208
+ * doDestroy() 默认执行的条件是: state.popperJS 有值且 state.showPopper = false.
209
+ * 当state.showPopper 为true时, 需要 doDestroy(true)!
210
+ */
211
+ const doDestroy = (forceDestroy?: boolean) => {
212
+ if (!state.popperJS || (state.showPopper && !forceDestroy)) {
213
+ return
214
+ }
215
+ state.popperJS.destroy() // 并未移除popper的dom
216
+ state.popperJS = null as any
217
+ }
218
+
219
+ /** remove时,执行真的移除popper dom操作。 */
220
+ const destroyPopper = (remove: 'remove' | boolean) => {
221
+ if (remove) {
222
+ // 当popper中嵌套popper时,内层popper被移除后不会重新创建,因此onDeactivated不将内层popper移除
223
+ if (state.popperElm && state.popperElm.parentNode === document.body) {
224
+ off(state.popperElm, 'click', stop)
225
+ state.popperElm.remove()
226
+ }
227
+ }
228
+ }
229
+
230
+ // 注意: 一直以来,state.showPopper 为false时,并未调用doDestroy. 像popover只是依赖这个值来 给reference元素 v-show一下
231
+ watch(
232
+ () => state.showPopper,
233
+ (val) => {
234
+ if (props.disabled) {
235
+ return
236
+ }
237
+ if (val) {
238
+ nextTick(updatePopper)
239
+ }
240
+ props.trigger === 'manual' && emit('update:modelValue', val)
241
+ }
242
+ )
243
+
244
+ watch(
245
+ () => props.placement,
246
+ (val?: string) => {
247
+ state.currentPlacement = val
248
+ state.popperJS?.setOptions({ placement: val })
249
+
250
+ if (props.disabled) {
251
+ return
252
+ }
253
+ if (val) {
254
+ nextTick(updatePopper)
255
+ }
256
+ props.trigger === 'manual' && emit('update:modelValue', val)
257
+ }
258
+ )
259
+
260
+ onBeforeUnmount(() => {
261
+ nextTick(() => {
262
+ doDestroy(true)
263
+ if (props.appendToBody || props.popperAppendToBody) {
264
+ destroyPopper('remove')
265
+ }
266
+ })
267
+ })
268
+
269
+ onDeactivated(() => {
270
+ doDestroy(true)
271
+ if (props.appendToBody || props.popperAppendToBody) {
272
+ destroyPopper('remove')
273
+ }
274
+ })
275
+
276
+ return { updatePopper, destroyPopper, doDestroy, ...toRefs(state) }
277
+ }