papayaui 0.2.17 → 0.2.19

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.
@@ -57,6 +57,7 @@
57
57
  :field-names="_fieldNames"
58
58
  :search-text="searchText"
59
59
  :empty-text="emptyText"
60
+ :max-level="maxLevel"
60
61
  :lazy-search="lazySearch"
61
62
  :is-selected="isSelected"
62
63
  :safe-area-inset-bottom="safeAreaInsetBottom && !localState.hasConfirm"
@@ -149,6 +149,7 @@ export const cascaderSearchViewProps = {
149
149
  required: true,
150
150
  },
151
151
  safeAreaInsetBottom: Boolean,
152
+ maxLevel: cascaderProps.maxLevel,
152
153
  } as const
153
154
 
154
155
  export interface SearchNode extends TreeNode<CascaderOption> {
@@ -31,7 +31,7 @@ const ns = useNamespace('cascader-search-view')
31
31
  const props = defineProps(cascaderSearchViewProps)
32
32
  const emit = defineEmits(cascaderSearchViewEmits)
33
33
 
34
- const { options, fieldNames, searchText, lazySearch } = toRefs(props)
34
+ const { options, fieldNames, searchText, lazySearch, maxLevel } = toRefs(props)
35
35
 
36
36
  const searchData = ref<SearchNode[]>([])
37
37
  const loading = ref<boolean>(false)
@@ -42,7 +42,7 @@ watch(searchText, (newVal) => {
42
42
 
43
43
  const getFlattenTreeData = (data: CascaderOption[], remote?: boolean, filterStr?: string) => {
44
44
  const result: SearchNode[] = []
45
- const loop = (list: CascaderOption[], prefixLabel = '', parentPath = '') => {
45
+ const loop = (list: CascaderOption[], prefixLabel = '', parentPath = '', level = 0) => {
46
46
  list.forEach((item, index) => {
47
47
  const newItem = item as any
48
48
  const node: SearchNode = {
@@ -51,8 +51,8 @@ const getFlattenTreeData = (data: CascaderOption[], remote?: boolean, filterStr?
51
51
  __label: `${prefixLabel}${prefixLabel ? '/' : ''}${newItem[fieldNames.value.label]}`,
52
52
  __remoteSearch: !!remote,
53
53
  }
54
- if (newItem[fieldNames.value.children]?.length) {
55
- loop(newItem[fieldNames.value.children], node.__label, node.__path)
54
+ if (newItem[fieldNames.value.children]?.length && level < maxLevel.value - 1) {
55
+ loop(newItem[fieldNames.value.children], node.__label, node.__path, level + 1)
56
56
  } else {
57
57
  ;(filterStr ? node.__label.includes(filterStr) : true) && result.push(node)
58
58
  }
@@ -31,8 +31,10 @@
31
31
  })
32
32
  "
33
33
  >
34
- <slot v-if="$slots.default" />
35
- <text v-else :selectable="selectable" :user-select="selectable">{{ value }}</text>
34
+ <text v-if="!$slots.default" :selectable="selectable" :user-select="selectable">
35
+ {{ value }}
36
+ </text>
37
+ <slot v-else />
36
38
 
37
39
  <view v-if="errorMessage" :class="ns.e('error-message')">
38
40
  {{ errorMessage }}
@@ -1,5 +1,7 @@
1
1
  @import '../../styles/vars.scss';
2
2
  .#{$prefix}-collapse {
3
+ position: relative;
4
+
3
5
  &--border::after {
4
6
  position: absolute;
5
7
  box-sizing: border-box;
@@ -5,42 +5,45 @@
5
5
  </template>
6
6
 
7
7
  <script setup lang="ts">
8
- import { provide, ref, watch, type Ref } from 'vue'
8
+ import { computed, ref, watch } from 'vue'
9
9
  import useNamespace from '../../core/useNamespace'
10
- import type { CollapseItemInstance } from '../collapse-item/collapse-item.vue'
11
- import type { CollapseItemValue } from '../collapse-item/props'
10
+ import type { CollapseItemInstance, CollapseItemValue } from '../collapse-item/props'
12
11
  import { collapseEmits, collapseProps } from './props'
13
12
 
14
- export interface CollapseProvideData {
15
- modelValue: Ref<CollapseItemValue[]>
16
- setChildren: (node: CollapseItemInstance) => void
17
- onChildExpand: (name: CollapseItemValue, expanded: boolean) => void
18
- }
19
-
20
13
  const ns = useNamespace('collapse')
21
14
 
22
15
  const props = defineProps(collapseProps)
23
16
  const emit = defineEmits(collapseEmits)
24
17
 
25
18
  const children = ref<CollapseItemInstance[]>([])
26
- const expandedValues = ref<CollapseItemValue[]>([])
19
+ const expandedValue = ref<CollapseItemValue | CollapseItemValue[]>('')
20
+
21
+ const _modelValue = computed({
22
+ get() {
23
+ return props.modelValue ?? expandedValue.value
24
+ },
25
+ set(val) {
26
+ expandedValue.value = val
27
+ emit('update:modelValue', val)
28
+ },
29
+ })
27
30
 
28
- const setChildren = (node: CollapseItemInstance) => {
29
- node.index.value = children.value.length
30
- children.value.push(node as any)
31
+ const updateExpanded = () => {
32
+ children.value.forEach((child) => {
33
+ child.updateExpanded()
34
+ })
31
35
  }
32
36
 
33
- const onChildExpand = (name: CollapseItemValue, expanded: boolean) => {
34
- const values = props.accordion
35
- ? name
36
- : children.value.flatMap((node) => {
37
- const nodeId = node.name ?? node.index
38
- const nodeExpanded = nodeId === name ? expanded : node.expanded
39
- return nodeExpanded ? nodeId : []
37
+ const onChange = (name: CollapseItemValue, expanded: boolean) => {
38
+ const newModelValue = props.accordion
39
+ ? expanded
40
+ ? name
41
+ : ''
42
+ : children.value.flatMap((child) => {
43
+ const nodeExpanded = child.name === name ? expanded : child.expanded
44
+ return nodeExpanded ? child.name : []
40
45
  })
41
- const newModelValue = values
42
- expandedValues.value = Array.isArray(newModelValue) ? newModelValue : [newModelValue]
43
- emit('update:modelValue', newModelValue)
46
+ _modelValue.value = newModelValue
44
47
  emit('change', newModelValue)
45
48
 
46
49
  if (expanded) {
@@ -50,39 +53,20 @@ const onChildExpand = (name: CollapseItemValue, expanded: boolean) => {
50
53
  }
51
54
  }
52
55
 
53
- /**
54
- * 切换所有面板展开状态,传 true 为全部展开,false 为全部收起,不传参为全部切换
55
- */
56
- const toggleAll = (options: boolean | { expanded: boolean; skipDisabled: boolean }) => {
57
- children.value.forEach((node) => {
58
- if (typeof options === 'boolean') {
59
- node?.toggle(options)
60
- } else {
61
- // 跳过禁用的复选框
62
- if (options.skipDisabled && node.disabled) return
63
- node?.toggle(options.expanded)
64
- }
65
- })
66
- }
67
-
68
56
  watch(
69
- () => props.modelValue,
70
- (value) => {
71
- expandedValues.value = Array.isArray(value) ? value : value ? [value] : []
57
+ _modelValue,
58
+ () => {
59
+ updateExpanded()
72
60
  },
73
61
  {
74
62
  immediate: true,
75
63
  },
76
64
  )
77
65
 
78
- provide<CollapseProvideData>(`${ns.b()}-provide`, {
79
- modelValue: expandedValues,
80
- setChildren,
81
- onChildExpand,
82
- })
83
-
84
66
  defineExpose({
85
- toggleAll,
67
+ children,
68
+ modelValue: _modelValue,
69
+ onChange,
86
70
  })
87
71
  </script>
88
72
 
@@ -1,6 +1,6 @@
1
- import type { ExtractPropTypes, PropType } from 'vue'
1
+ import type { ExtractPropTypes, PropType, Ref } from 'vue'
2
2
  import { isUndefined } from '../../utils'
3
- import type { CollapseItemValue } from '../collapse-item/props'
3
+ import type { CollapseItemInstance, CollapseItemValue } from '../collapse-item/props'
4
4
 
5
5
  export const collapseProps = {
6
6
  /**
@@ -29,3 +29,9 @@ export const collapseEmits = {
29
29
 
30
30
  export type CollapseProps = ExtractPropTypes<typeof collapseProps>
31
31
  export type CollapseEmits = typeof collapseEmits
32
+
33
+ export type CollapseExpose = {
34
+ children: Ref<CollapseItemInstance[]>
35
+ modelValue: Ref<CollapseItemValue | CollapseItemValue[] | undefined>
36
+ onChange: (name: CollapseItemValue, expanded: boolean) => void
37
+ }
@@ -33,7 +33,7 @@
33
33
  transform: scaleY(0.5);
34
34
  }
35
35
 
36
- &--expanded &__title {
36
+ &--expanded > &__title {
37
37
  @include _setVar(cell-icon-right-rotate, -90deg);
38
38
  }
39
39
 
@@ -2,7 +2,7 @@
2
2
  <view
3
3
  :class="[
4
4
  ns.b(),
5
- ns.is('border', _index !== 0 && props.border),
5
+ ns.is('border', index !== 0 && props.border),
6
6
  ns.is('expanded', expanded),
7
7
  ns.is('disabled', disabled),
8
8
  ns.is('readonly', readonly),
@@ -14,8 +14,8 @@
14
14
  :title="title"
15
15
  :icon="icon"
16
16
  :value="value"
17
- :is-link="isLink"
18
- :border="props.border"
17
+ :is-link="isLink && !disabled"
18
+ :border="border"
19
19
  @click.stop="onClick"
20
20
  >
21
21
  <template #title>
@@ -36,94 +36,100 @@
36
36
  </template>
37
37
 
38
38
  <script setup lang="ts">
39
- import { computed, watch, type CSSProperties, type Ref } from 'vue'
40
- import { getCurrentInstance, inject, onMounted, ref, toRefs } from 'vue'
41
- import useNamespace, { defaultNamespace } from '../../core/useNamespace'
39
+ import { computed, getCurrentInstance, onMounted, ref, type CSSProperties } from 'vue'
40
+ import useNamespace from '../../core/useNamespace'
42
41
  import { useRect } from '../../hooks'
43
- import { noop } from '../../utils'
42
+ import { getParentInstance } from '../../utils/component'
44
43
  import Cell from '../cell/cell.vue'
45
- import type { CollapseProvideData } from '../collapse/collapse.vue'
46
- import type { CollapseItemProps } from './props'
47
- import { collapseItemProps } from './props'
48
-
49
- export interface CollapseItemInstance {
50
- index: Ref<number>
51
- /** 唯一标识符 */
52
- name?: Ref<CollapseItemProps['name']>
53
- /** 打开状态 */
54
- expanded: Ref<boolean>
55
- /** 禁用状态 */
56
- disabled: Ref<boolean>
57
- /** 切换打开状态 */
58
- toggle: (show?: boolean) => void
59
- }
44
+ import { CollapseExpose, CollapseProps } from '../collapse'
45
+ import { collapseItemProps, CollapseItemValue } from './props'
60
46
 
61
47
  const ns = useNamespace('collapse-item')
62
48
 
63
- const instance = getCurrentInstance()
64
-
65
49
  const props = defineProps(collapseItemProps)
66
50
 
67
- const { name, disabled } = toRefs(props)
68
- const _index = ref<number>(0)
69
-
70
- const collapseProvide = inject<CollapseProvideData>(`${defaultNamespace}-collapse-provide`, {
71
- modelValue: ref([]),
72
- setChildren: noop,
73
- onChildExpand: noop,
74
- })
51
+ const instance = getCurrentInstance()
52
+ const parent = getParentInstance<CollapseProps, CollapseExpose>(instance, 'collapse')
75
53
 
76
- const itemId = computed(() => name?.value ?? _index.value)
77
- const expanded = computed(() => collapseProvide.modelValue.value.includes(itemId.value))
54
+ const index = ref<number | undefined>()
55
+ const name = computed(() => props.name ?? index.value ?? 0)
56
+ const expanded = ref(false)
78
57
  const wrapperAnimation = ref<CSSProperties>()
58
+ let animating = false
79
59
 
80
60
  const getRect = () => {
81
61
  if (!instance) return Promise.resolve(null)
82
62
  return useRect(instance, `.${ns.e('content')}`)
83
63
  }
84
64
 
85
- const toggle = async (show = !expanded.value) => {
65
+ const updateExpanded = () => {
66
+ if (!parent || animating) return
67
+
68
+ const parentVal = parent.exposed.modelValue.value
69
+ const newExpanded = parent.props.accordion
70
+ ? parentVal === name.value
71
+ : (parentVal as CollapseItemValue[]).includes(name.value)
72
+ if (newExpanded !== expanded.value) {
73
+ setContentAnimate(newExpanded)
74
+ }
75
+ expanded.value = newExpanded
76
+ }
77
+
78
+ const setContentAnimate = async (show: boolean, duration = 300) => {
86
79
  const rect = await getRect()
87
80
  if (!rect) return
88
- const height = show ? rect.height : 0
89
81
 
82
+ animating = true
90
83
  const animation = uni.createAnimation({
84
+ duration: 0,
91
85
  timingFunction: 'ease-in-out',
92
86
  })
93
- animation.height(height).step({
94
- duration: 300,
95
- })
87
+ if (show) {
88
+ animation.height(rect.height).step({
89
+ duration,
90
+ })
91
+ animation.height('auto').step()
92
+ } else {
93
+ animation.height(rect.height).step()
94
+ // TODO: 这里第二步动画需要延迟执行,否则执行后无效果
95
+ setTimeout(() => {
96
+ animation.height(0).step({
97
+ duration,
98
+ })
99
+ wrapperAnimation.value = animation.export()
100
+ }, 16)
101
+ }
96
102
  wrapperAnimation.value = animation.export()
103
+ setTimeout(() => {
104
+ animating = false
105
+ }, duration)
97
106
  }
98
107
 
99
108
  const onClick = () => {
100
109
  if (props.disabled || props.readonly) return
101
- collapseProvide.onChildExpand(itemId.value, !expanded.value)
102
- toggle()
110
+ parent?.exposed.onChange(name.value, !expanded.value)
103
111
  }
104
112
 
105
- onMounted(() => {
106
- // 将自身组件实例添加到父组件
107
- if (instance) {
108
- collapseProvide.setChildren({
109
- index: _index,
113
+ const init = () => {
114
+ if (parent && parent.exposed) {
115
+ // 操作父组件的children对象,按照添加顺序记录子组件的index
116
+ index.value = parent.exposed.children.value.length
117
+ parent.exposed.children.value.push({
110
118
  name,
111
119
  expanded,
112
- disabled,
113
- toggle,
120
+ updateExpanded,
114
121
  })
115
- toggle(expanded.value)
116
122
  }
117
- })
123
+ }
118
124
 
119
- watch(expanded, (newVal, oldVal) => {
120
- if (newVal !== oldVal) {
121
- toggle(newVal)
122
- }
125
+ onMounted(() => {
126
+ init()
127
+ updateExpanded()
123
128
  })
124
129
 
125
130
  defineExpose({
126
- toggle,
131
+ updateExpanded,
132
+ setContentAnimate,
127
133
  })
128
134
  </script>
129
135
 
@@ -1,4 +1,4 @@
1
- import type { ExtractPropTypes, PropType } from 'vue'
1
+ import type { ExtractPropTypes, PropType, Ref } from 'vue'
2
2
 
3
3
  export const collapseItemProps = {
4
4
  /**
@@ -50,3 +50,12 @@ export const collapseItemProps = {
50
50
  export type CollapseItemValue = string | number
51
51
 
52
52
  export type CollapseItemProps = ExtractPropTypes<typeof collapseItemProps>
53
+
54
+ export interface CollapseItemInstance {
55
+ /** 唯一标识符 */
56
+ name: Ref<NonNullable<CollapseItemProps['name']>>
57
+ /** 打开状态 */
58
+ expanded: Ref<boolean>
59
+ /** 切换打开状态 */
60
+ updateExpanded: () => void
61
+ }
@@ -2,11 +2,21 @@
2
2
  <DocDemoBlock title="基础用法" card>
3
3
  <Demo1 />
4
4
  </DocDemoBlock>
5
+
6
+ <DocDemoBlock title="选项配置" card>
7
+ <Demo2 />
8
+ </DocDemoBlock>
9
+
10
+ <DocDemoBlock title="自定义内容" card>
11
+ <Demo3 />
12
+ </DocDemoBlock>
5
13
  </template>
6
14
 
7
15
  <script lang="ts" setup>
8
16
  import DocDemoBlock from '../../doc/doc-demo-block.vue'
9
17
  import Demo1 from '../../demos/popover/demo-1.vue'
18
+ import Demo2 from '../../demos/popover/demo-2.vue'
19
+ import Demo3 from '../../demos/popover/demo-3.vue'
10
20
  </script>
11
21
 
12
22
  <style lang="scss" scoped></style>
@@ -1,10 +1,10 @@
1
1
  @import '../../styles/vars.scss';
2
2
  .#{$prefix}-popover {
3
- $arrowSize: 20rpx;
4
- $darkColor: #323233;
3
+ $arrowSize: _var(popover-arrow-size, 6px);
4
+ $darkColor: #4c4c4c;
5
+ $lightColor: #fff;
5
6
 
6
- position: relative;
7
- &-overlay {
7
+ &__overlay {
8
8
  position: fixed;
9
9
  top: 0;
10
10
  left: 0;
@@ -12,18 +12,23 @@
12
12
  height: 100vh;
13
13
  background-color: transparent;
14
14
  }
15
- &-wrapper {
15
+
16
+ &__wrapper {
17
+ display: inline-block;
18
+ }
19
+
20
+ &__content {
16
21
  position: fixed;
17
22
  left: 0;
18
23
  top: 0;
19
24
  margin: 0;
20
25
  padding: 0;
21
- border-radius: 8rpx;
26
+ border-radius: _var(popover-radius, 8px);
22
27
  opacity: 0.2;
23
28
  box-shadow: 0px 0px 5px #dcdee0;
24
29
  }
25
30
 
26
- &-arrow {
31
+ &__arrow {
27
32
  position: absolute;
28
33
  width: $arrowSize;
29
34
  height: $arrowSize;
@@ -34,29 +39,84 @@
34
39
  height: $arrowSize;
35
40
  z-index: -1;
36
41
  content: ' ';
42
+ border-width: 1px;
43
+ border-style: solid;
44
+ border-bottom-color: transparent !important;
45
+ border-right-color: transparent !important;
37
46
  transform: rotate(45deg);
38
47
  box-sizing: border-box;
39
48
  }
40
49
  }
41
50
 
42
- &-wrapper.light {
43
- color: $darkColor;
44
- background: #fff;
51
+ &__action {
52
+ position: relative;
53
+ display: flex;
54
+ align-items: center;
55
+ box-sizing: border-box;
56
+ width: _var(popover-action-width, 128px);
57
+ height: _var(popover-action-height, 44px);
58
+ padding: 0 _var(popover-action-padding, 16px);
59
+ font-size: _var(popover-action-font-size, 14px);
60
+ line-height: _var(popover-action-line-height, 20px);
61
+ cursor: pointer;
62
+ @include _setVar(color-border, #ebedf0);
63
+
64
+ &-icon {
65
+ margin-right: 8px;
66
+ font-size: _var(popover-action-icon-size, 20px);
67
+ }
68
+
69
+ &-text {
70
+ display: flex;
71
+ flex: 1;
72
+ align-items: center;
73
+ justify-content: center;
74
+ height: 100%;
75
+ }
45
76
  }
46
- &-wrapper.light &-arrow {
47
- &::before {
48
- border: 1px solid #fff;
49
- background: #fff;
50
- border-bottom-color: transparent !important;
51
- border-right-color: transparent !important;
77
+
78
+ @each $theme in light, dark {
79
+ $color: $darkColor;
80
+ $backgroundColor: $lightColor;
81
+ @if $theme == dark {
82
+ $color: $lightColor;
83
+ $backgroundColor: $darkColor;
84
+ }
85
+
86
+ &--#{$theme} {
87
+ color: _var(popover-#{$theme}-text-color, $color);
88
+ background: _var(popover-#{$theme}-background, $backgroundColor);
89
+ }
90
+ &--#{$theme} &__arrow {
91
+ &::before {
92
+ border-color: _var(popover-#{$theme}-background, $backgroundColor);
93
+ background: _var(popover-#{$theme}-background, $backgroundColor);
94
+ }
52
95
  }
53
96
  }
54
97
 
55
- &-wrapper.bottom &-arrow {
56
- $top: calc($arrowSize / 2);
98
+ &--bottom &__arrow {
99
+ $top: calc($arrowSize / -2);
57
100
  left: 50%;
58
- top: -$top;
59
- margin-left: -$top;
101
+ top: $top;
102
+ margin-left: $top;
60
103
  border-top-left-radius: 2px;
61
104
  }
105
+
106
+ &--with-icon &__action-text {
107
+ justify-content: flex-start;
108
+ }
109
+
110
+ &--light &--action-active {
111
+ background-color: #f2f3f5;
112
+ }
113
+ &--dark &--action-active {
114
+ background-color: #444;
115
+ }
116
+
117
+ &--disabled {
118
+ color: _var(color-disabled);
119
+ background-color: transparent !important;
120
+ cursor: not-allowed;
121
+ }
62
122
  }
@@ -1,46 +1,58 @@
1
1
  <template>
2
- <view :class="[ns.b(), ns.is('show', show)]">
3
- <view v-show="show" :class="ns.b('overlay')" @tap="onVisibleChange(false)"></view>
4
- <view @tap.stop="onVisibleChange()">
5
- <slot></slot>
6
- </view>
7
- <view
8
- v-show="show"
9
- class="light bottom"
10
- :class="ns.b('wrapper')"
11
- :style="{
12
- width: getUnitValue(width),
13
- opacity: tooltipRect.height && tooltipContentWidth ? '1' : '0',
14
- top: tooltipRect.top + tooltipRect.height + 12 + 'px',
15
- left: (contentLeft < 0 ? 0 : contentLeft) + 'px',
16
- zIndex,
17
- }"
18
- >
19
- <view v-if="text" class="text-28 leading-40" style="max-width: 420rpx">
20
- {{ text }}
2
+ <view v-show="show" :class="ns.e('overlay')" @tap="onClickOverlay"></view>
3
+ <view :class="ns.e('wrapper')" @tap.stop="onVisibleChange()">
4
+ <slot name="reference" />
5
+ </view>
6
+ <view v-show="show" :class="[ns.e('content'), ns.m(theme), ns.m('bottom')]" :style="contentStyle">
7
+ <template v-if="!$slots.default">
8
+ <view
9
+ v-for="(action, index) in actions"
10
+ :key="index"
11
+ :class="[
12
+ ns.e('action'),
13
+ ns.is('with-icon', !!action.icon),
14
+ ns.is('disabled', action.disabled),
15
+ ]"
16
+ :style="ns.style({ color: action.color })"
17
+ :hover-class="ns.m('action-active')"
18
+ @tap.stop="onSelect(action, index)"
19
+ >
20
+ <IconComponent
21
+ v-if="action.icon"
22
+ :class="ns.e('action-icon')"
23
+ :class-prefix="iconPrefix"
24
+ :name="action.icon"
25
+ />
26
+ <view :class="[ns.e('action-text'), { 'border-bottom': index < actions.length - 1 }]">
27
+ {{ action.text }}
28
+ </view>
21
29
  </view>
22
- <slot name="popover-content"></slot>
23
- <view :class="ns.b('arrow')" :style="{ transform: `translateX(${arrowLeft}px)` }"></view>
24
- </view>
30
+ </template>
31
+ <slot v-else />
32
+
33
+ <view :class="ns.e('arrow')" :style="{ transform: `translateX(${arrowLeft}px)` }"></view>
25
34
  </view>
26
35
  </template>
27
36
 
28
37
  <script lang="ts" setup>
29
- import { getUnitValue } from '../../utils'
30
38
  import { computed, getCurrentInstance, nextTick, ref, watch } from 'vue'
31
- import useRect from '../../hooks/useRect'
32
39
  import useNamespace from '../../core/useNamespace'
33
- import { popoverProps } from './props'
40
+ import useRect from '../../hooks/useRect'
41
+ import { getUnitValue } from '../../utils'
42
+ import IconComponent from '../icon/icon.vue'
43
+ import type { PopoverAction } from './props'
44
+ import { popoverEmits, popoverProps } from './props'
34
45
 
35
46
  const ns = useNamespace('popover')
36
47
 
37
- defineProps(popoverProps)
48
+ const props = defineProps(popoverProps)
49
+ const emit = defineEmits(popoverEmits)
38
50
 
39
51
  const show = ref<boolean>(false)
40
52
 
41
53
  const instance = getCurrentInstance()
42
54
 
43
- const tooltipRect = ref<Required<UniApp.NodeInfo>>({
55
+ const wrapperRect = ref<Required<UniApp.NodeInfo>>({
44
56
  left: 0,
45
57
  right: 0,
46
58
  top: 0,
@@ -48,31 +60,41 @@ const tooltipRect = ref<Required<UniApp.NodeInfo>>({
48
60
  width: 0,
49
61
  height: 0,
50
62
  } as Required<UniApp.NodeInfo>)
51
- const tooltipContentWidth = ref<number>(0)
63
+ const contentWidth = ref<number>(0)
52
64
 
53
- const contentLeft = computed<number>(() => {
54
- return (
55
- (tooltipRect.value.left || 0) -
56
- tooltipContentWidth.value / 2 +
57
- (tooltipRect.value.width || 0) / 2
58
- )
65
+ const contentOffsetLeft = computed<number>(() => {
66
+ const { left = 0, width = 0 } = wrapperRect.value
67
+ const offsetLeft = left + width / 2 - contentWidth.value / 2
68
+ return offsetLeft
59
69
  })
60
70
 
61
71
  const arrowLeft = computed(() => {
62
- return contentLeft.value < 0 ? contentLeft.value : 0
72
+ return contentOffsetLeft.value < 0 ? contentOffsetLeft.value : 0
73
+ })
74
+
75
+ const contentStyle = computed(() => {
76
+ return {
77
+ opacity: wrapperRect.value.height && contentWidth ? '1' : '0',
78
+ top: getUnitValue(wrapperRect.value.top + wrapperRect.value.height + props.offset[1], 'px'),
79
+ left: getUnitValue(
80
+ (contentOffsetLeft.value < 0 ? 0 : contentOffsetLeft.value) + props.offset[0],
81
+ 'px',
82
+ ),
83
+ zIndex: props.zIndex,
84
+ }
63
85
  })
64
86
 
65
87
  watch(show, (newVal) => {
66
88
  if (instance && newVal) {
67
89
  nextTick(() => {
68
- useRect(instance, `.${ns.b()}`).then((res) => {
90
+ useRect(instance, `.${ns.e('wrapper')}`).then((res) => {
69
91
  if (res) {
70
- tooltipRect.value = res
92
+ wrapperRect.value = res
71
93
  }
72
94
  })
73
- useRect(instance, `.${ns.b('wrapper')}`).then((res) => {
95
+ useRect(instance, `.${ns.e('content')}`).then((res) => {
74
96
  if (res) {
75
- tooltipContentWidth.value = res.width || 0
97
+ contentWidth.value = res.width || 0
76
98
  }
77
99
  })
78
100
  })
@@ -82,6 +104,21 @@ watch(show, (newVal) => {
82
104
  const onVisibleChange = (visible = !show.value) => {
83
105
  show.value = visible
84
106
  }
107
+
108
+ const onClickOverlay = () => {
109
+ emit('click-overlay')
110
+ if (props.closeOnClickOverlay) {
111
+ onVisibleChange(false)
112
+ }
113
+ }
114
+
115
+ const onSelect = (action: PopoverAction, index: number) => {
116
+ if (action.disabled) return
117
+ emit('select', action, index)
118
+ if (props.closeOnClickAction) {
119
+ onVisibleChange(false)
120
+ }
121
+ }
85
122
  </script>
86
123
 
87
124
  <style lang="scss">
@@ -1,19 +1,28 @@
1
- import type { ExtractPropTypes } from 'vue'
1
+ import type { ExtractPropTypes, PropType } from 'vue'
2
+ import { isUndefined } from '../../utils'
3
+ import { iconProps } from '../icon'
4
+
5
+ export type PopoverTheme = 'light' | 'dark'
6
+ export type PopoverPlacement = 'bottom'
7
+
8
+ export interface PopoverAction {
9
+ /** 选项文字 */
10
+ text: string
11
+ /** 文字左侧的图标,支持传入图标名称或图片链接,等同于 Icon 组件的 name 属性 */
12
+ icon?: string
13
+ /** 选项文字颜色 */
14
+ color?: string
15
+ /** 是否为禁用状态 */
16
+ disabled?: boolean
17
+ }
2
18
 
3
19
  export const popoverProps = {
4
20
  /**
5
- * 内容
21
+ * 选项列表
6
22
  */
7
- text: {
8
- type: String,
9
- default: '',
10
- },
11
- /**
12
- * 浮窗宽度
13
- */
14
- width: {
15
- type: [String, Number],
16
- default: '300',
23
+ actions: {
24
+ type: Array as PropType<PopoverAction[]>,
25
+ default: () => [],
17
26
  },
18
27
  /**
19
28
  * 浮窗层级
@@ -22,6 +31,51 @@ export const popoverProps = {
22
31
  type: Number,
23
32
  default: 9,
24
33
  },
34
+ /**
35
+ * 主题
36
+ */
37
+ theme: {
38
+ type: String as PropType<PopoverTheme>,
39
+ default: 'light',
40
+ },
41
+ /**
42
+ * 弹出位置
43
+ */
44
+ placement: {
45
+ type: String as PropType<PopoverPlacement>,
46
+ default: 'bottom',
47
+ },
48
+ /**
49
+ * 出现位置的偏移量
50
+ */
51
+ offset: {
52
+ type: Object as PropType<[number, number]>,
53
+ default: () => [0, 8],
54
+ },
55
+ /**
56
+ * 是否在点击选项后关闭
57
+ */
58
+ closeOnClickAction: {
59
+ type: Boolean,
60
+ default: true,
61
+ },
62
+ /**
63
+ * 是否在点击遮罩层后关闭菜单
64
+ */
65
+ closeOnClickOverlay: {
66
+ type: Boolean,
67
+ default: true,
68
+ },
69
+ /**
70
+ * 图标类名前缀,等同于 Icon 组件的 class-prefix 属性
71
+ */
72
+ iconPrefix: iconProps.classPrefix,
73
+ }
74
+
75
+ export const popoverEmits = {
76
+ select: (action: PopoverAction, _index: number) => !isUndefined(action),
77
+ 'click-overlay': () => true,
25
78
  }
26
79
 
27
80
  export type PopoverProps = ExtractPropTypes<typeof popoverProps>
81
+ export type PopoverEmits = typeof popoverEmits
@@ -90,13 +90,10 @@ const onChooseFile = () => {
90
90
  })
91
91
  }
92
92
 
93
- const beforeValid = <T extends object>(
94
- files: T,
95
- fun?: (files: T) => boolean | Promise<unknown>,
96
- ) => {
93
+ const beforeValid = <T extends object>(files: T, fn?: (files: T) => boolean | Promise<unknown>) => {
97
94
  return new Promise<void>((resolve, reject) => {
98
- if (typeof fun === 'function') {
99
- const res = fun(files)
95
+ if (typeof fn === 'function') {
96
+ const res = fn(files)
100
97
  if (res === false) {
101
98
  return reject()
102
99
  }
@@ -1,11 +1,19 @@
1
1
  <template>
2
- <pa-popover class="inline-flex">
3
- <pa-button>基础用法</pa-button>
4
- <template #popover-content>
5
- <view class="p-20">
6
- <text class="text-32">标题</text>
7
- <text class="text-28 block">这是一段内容,这是一段内容,这是一段内容,这是一段内容。</text>
8
- </view>
2
+ <pa-popover :actions="actions">
3
+ <template #reference>
4
+ <pa-button>浅色风格</pa-button>
5
+ </template>
6
+ </pa-popover>
7
+
8
+ <pa-popover theme="dark" :actions="actions">
9
+ <template #reference>
10
+ <pa-button class="ml-30">深色风格</pa-button>
9
11
  </template>
10
12
  </pa-popover>
11
13
  </template>
14
+
15
+ <script setup lang="ts">
16
+ import { ref } from 'vue'
17
+
18
+ const actions = ref([{ text: '选项一' }, { text: '选项二' }, { text: '选项三' }])
19
+ </script>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <pa-popover :actions="actions1">
3
+ <template #reference>
4
+ <pa-button>展示图标</pa-button>
5
+ </template>
6
+ </pa-popover>
7
+
8
+ <pa-popover theme="dark" :actions="actions2">
9
+ <template #reference>
10
+ <pa-button class="ml-30">禁用选项</pa-button>
11
+ </template>
12
+ </pa-popover>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { ref } from 'vue'
17
+
18
+ const actions1 = ref([
19
+ { text: '选项一', icon: 'plus' },
20
+ { text: '选项二', icon: 'scan' },
21
+ { text: '选项三', icon: 'refresh' },
22
+ ])
23
+
24
+ const actions2 = ref([{ text: '选项一', disabled: true }, { text: '选项二' }, { text: '选项三' }])
25
+ </script>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <pa-popover>
3
+ <template #reference>
4
+ <pa-button>基础用法</pa-button>
5
+ </template>
6
+ <view class="p-20" style="width: 150px">
7
+ <text class="text-32">标题</text>
8
+ <text class="text-28 block">这是一段内容,这是一段内容,这是一段内容,这是一段内容。</text>
9
+ </view>
10
+ </pa-popover>
11
+ </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "papayaui",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "适用于uniapp的ui扩展库",
5
5
  "packageManager": "pnpm@8.6.0",
6
6
  "dependencies": {
package/styles/color.scss CHANGED
@@ -20,6 +20,7 @@ $color-text-black-3: #969799;
20
20
 
21
21
  $color-active: $color-gray;
22
22
  $color-disabled: #c8c9cc;
23
+ $color-border: #e4e7ed;
23
24
 
24
25
  $colors: (
25
26
  primary: $color-primary,
@@ -35,4 +36,5 @@ $colors: (
35
36
  red: $color-danger,
36
37
  active: $color-active,
37
38
  disabled: $color-disabled,
39
+ border: $color-border,
38
40
  );
@@ -22,13 +22,12 @@
22
22
  top: 0;
23
23
  pointer-events: none;
24
24
  box-sizing: border-box;
25
- -webkit-transform-origin: 0 0;
26
25
  transform-origin: 0 0;
27
26
  // 多加0.1%,能解决有时候边框缺失的问题
28
27
  width: 199.8%;
29
28
  height: 199.7%;
30
- transform: scale(0.5, 0.5);
31
- border: 0 solid #e4e7ed;
29
+ transform: scale(0.5);
30
+ border: 0 solid _var(color-border);
32
31
  z-index: 2;
33
32
  }
34
33
 
package/.DS_Store DELETED
Binary file
Binary file
@@ -1,14 +0,0 @@
1
- import { defaultNamespace } from '../../core'
2
-
3
- const Dialog = () => {
4
- const node = uni
5
- .createSelectorQuery()
6
- .select(`#${defaultNamespace}-dialog`)
7
- .node((result) => {
8
- console.log('result', result)
9
- })
10
- .exec()
11
- console.log(node)
12
- }
13
-
14
- export default Dialog
package/fonts/.DS_Store DELETED
Binary file
package/images/.DS_Store DELETED
Binary file