sard-uniapp 1.24.1 → 1.24.2

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [1.24.2](https://github.com/sutras/sard-uniapp/compare/v1.24.1...v1.24.2) (2025-09-06)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **count-down:** 修复行内嵌套块的问题 ([fd8112c](https://github.com/sutras/sard-uniapp/commit/fd8112c3885c0069b34f6709c7190a298d680726))
7
+ * **popout:** 修正按钮插槽内容 ([7f29d80](https://github.com/sutras/sard-uniapp/commit/7f29d8059ac1dd342f85a5de54f42e867879ecfc))
8
+ * **tag:** 阻止点击关闭时的事件冒泡 ([4220f31](https://github.com/sutras/sard-uniapp/commit/4220f311f0c5564ae095db6863f415f483f7f22e))
9
+
10
+
11
+ ### Features
12
+
13
+ * **fab:** 添加拖拽功能 ([4d24318](https://github.com/sutras/sard-uniapp/commit/4d243186b4ba22ebe50722c67466eb702c88cbf5))
14
+
15
+
16
+
1
17
  ## [1.24.1](https://github.com/sutras/sard-uniapp/compare/v1.24.0...v1.24.1) (2025-08-31)
2
18
 
3
19
 
@@ -13,6 +13,7 @@ import { type DatetimeRangePickerInputProps } from '../datetime-range-picker-inp
13
13
  import { type DialogProps } from '../dialog';
14
14
  import { type DividerProps } from '../divider';
15
15
  import { type DropdownProps } from '../dropdown';
16
+ import { type FabProps } from '../fab';
16
17
  import { type FloatingBubbleProps } from '../floating-bubble';
17
18
  import { type FormProps } from '../form';
18
19
  import { type GridProps } from '../grid';
@@ -202,8 +203,13 @@ export declare const defaultConfig: {
202
203
  overlayClosable: boolean;
203
204
  hideName: boolean;
204
205
  duration: number;
206
+ draggable: boolean;
207
+ axis: FabProps["axis"];
208
+ gapX: number;
209
+ gapY: number;
205
210
  };
206
211
  floatingBubble: {
212
+ draggable: boolean;
207
213
  axis: FloatingBubbleProps["axis"];
208
214
  gapX: number;
209
215
  gapY: number;
@@ -152,8 +152,13 @@ export const defaultConfig = {
152
152
  overlayClosable: false,
153
153
  hideName: false,
154
154
  duration: 150,
155
+ draggable: false,
156
+ axis: 'y',
157
+ gapX: 24,
158
+ gapY: 24,
155
159
  },
156
160
  floatingBubble: {
161
+ draggable: true,
157
162
  axis: 'y',
158
163
  gapX: 24,
159
164
  gapY: 24,
@@ -57,12 +57,14 @@ import CountDown from 'sard-uniapp/components/count-down/count-down.vue'
57
57
 
58
58
  ### CountDownProps
59
59
 
60
- | 属性 | 描述 | 类型 | 默认值 |
61
- | ----------- | ---------------------- | ------- | ---------- |
62
- | time | 倒计时总时长,单位毫秒 | number | 0 |
63
- | auto-start | 是否自动开始倒计时 | boolean | true |
64
- | format | 时间格式 | string | 'HH:mm:ss' |
65
- | millisecond | 是否开启毫秒级别渲染 | boolean | false |
60
+ | 属性 | 描述 | 类型 | 默认值 |
61
+ | ----------------------------- | ---------------------- | ---------- | ---------- |
62
+ | root-class <sup>1.24.2+</sup> | 组件根元素类名 | string | - |
63
+ | root-style <sup>1.24.2+</sup> | 组件根元素样式 | StyleValue | - |
64
+ | time | 倒计时总时长,单位毫秒 | number | 0 |
65
+ | auto-start | 是否自动开始倒计时 | boolean | true |
66
+ | format | 时间格式 | string | 'HH:mm:ss' |
67
+ | millisecond | 是否开启毫秒级别渲染 | boolean | false |
66
68
 
67
69
  ### CountDownSlots
68
70
 
@@ -1,4 +1,7 @@
1
+ import { type StyleValue } from 'vue';
1
2
  export interface CountDownProps {
3
+ rootStyle?: StyleValue;
4
+ rootClass?: string;
2
5
  time?: number;
3
6
  autoStart?: boolean;
4
7
  format?: string;
@@ -1,9 +1,9 @@
1
1
  <template>
2
- <text>
2
+ <view :class="countDownClass" :style="countDownStyle">
3
3
  <slot :time="currentTime">
4
4
  {{ formatTime(format, currentTime) }}
5
5
  </slot>
6
- </text>
6
+ </view>
7
7
  </template>
8
8
 
9
9
  <script>
@@ -14,7 +14,10 @@ import {
14
14
  formatTime,
15
15
  defaultCountDownProps
16
16
  } from "./common";
17
+ import { classNames, stringifyStyle, createBem } from "../../utils";
17
18
  /**
19
+ * @property {string} rootClass 组件根元素类名,默认值:-。
20
+ * @property {StyleValue} rootStyle 组件根元素样式,默认值:-。
18
21
  * @property {number} time 倒计时总时长,单位毫秒,默认值:0。
19
22
  * @property {boolean} autoStart 是否自动开始倒计时,默认值:true。
20
23
  * @property {string} format 时间格式,默认值:'HH:mm:ss'。
@@ -31,6 +34,8 @@ export default _defineComponent({
31
34
  },
32
35
  __name: "count-down",
33
36
  props: _mergeDefaults({
37
+ rootStyle: { type: [Boolean, null, String, Object, Array], required: false, skipCheck: true },
38
+ rootClass: { type: String, required: false },
34
39
  time: { type: Number, required: false },
35
40
  autoStart: { type: Boolean, required: false },
36
41
  format: { type: String, required: false },
@@ -40,6 +45,7 @@ export default _defineComponent({
40
45
  setup(__props, { expose: __expose, emit: __emit }) {
41
46
  const props = __props;
42
47
  const emit = __emit;
48
+ const bem = createBem("count-down");
43
49
  const remainTime = ref(props.time);
44
50
  let endTime = 0;
45
51
  let timer = null;
@@ -102,7 +108,13 @@ export default _defineComponent({
102
108
  onBeforeUnmount(() => {
103
109
  pause();
104
110
  });
105
- const __returned__ = { props, emit, remainTime, get endTime() {
111
+ const countDownClass = computed(() => {
112
+ return classNames(bem.b(), props.rootClass);
113
+ });
114
+ const countDownStyle = computed(() => {
115
+ return stringifyStyle(props.rootStyle);
116
+ });
117
+ const __returned__ = { props, emit, bem, remainTime, get endTime() {
106
118
  return endTime;
107
119
  }, set endTime(v) {
108
120
  endTime = v;
@@ -114,10 +126,14 @@ export default _defineComponent({
114
126
  return paused;
115
127
  }, set paused(v) {
116
128
  paused = v;
117
- }, tick, start, pause, reset, precisionTime, currentTime, get formatTime() {
129
+ }, tick, start, pause, reset, precisionTime, currentTime, countDownClass, countDownStyle, get formatTime() {
118
130
  return formatTime;
119
131
  } };
120
132
  return __returned__;
121
133
  }
122
134
  });
123
135
  </script>
136
+
137
+ <style lang="scss">
138
+ @import './index.scss';
139
+ </style>
@@ -0,0 +1,10 @@
1
+ @use '../style/base' as *;
2
+
3
+ @include bem(count-down) {
4
+ @include b() {
5
+ box-sizing: border-box;
6
+ display: inline-flex;
7
+ align-items: center;
8
+ vertical-align: middle;
9
+ }
10
+ }
@@ -70,22 +70,28 @@ import Fab from 'sard-uniapp/components/fab/fab.vue'
70
70
 
71
71
  ### FabProps
72
72
 
73
- | 属性 | 描述 | 类型 | 默认值 |
74
- | ---------------- | -------------------------------------------- | ---------- | ------ |
75
- | root-class | 组件根元素类名 | string | - |
76
- | root-style | 组件根元素样式 | StyleValue | - |
77
- | top | 设置距离窗口顶部的距离,优先级比 `bottom` 高 | string | - |
78
- | right | 设置距离窗口右边的距离 | string | - |
79
- | bottom | 设置距离窗口底部的距离 | string | - |
80
- | left | 设置距离窗口左边的距离,优先级比 `right` 高 | string | - |
81
- | color | 设置按钮图标的颜色 | string | - |
82
- | background | 设置按钮的背景色 | string | - |
83
- | icon | 设置入口按钮的图标 | string | - |
84
- | icon-family | 设置入口按钮的图标族 | string | - |
85
- | item-list | 设置扩展按钮 | FabItem[] | [] |
86
- | hide-name | 是否隐藏按钮名称 | boolean | false |
87
- | overlay-closable | 点击遮罩是否隐藏扩展按钮 | boolean | false |
88
- | duration | 扩展按钮显隐动画时长,单位 ms | number | 150 |
73
+ | 属性 | 描述 | 类型 | 默认值 |
74
+ | ----------------------------------- | -------------------------------------------- | ------------------------------ | ------ |
75
+ | root-class | 组件根元素类名 | string | - |
76
+ | root-style | 组件根元素样式 | StyleValue | - |
77
+ | top | 设置距离窗口顶部的距离,优先级比 `bottom` 高 | string | - |
78
+ | right | 设置距离窗口右边的距离 | string | - |
79
+ | bottom | 设置距离窗口底部的距离 | string | - |
80
+ | left | 设置距离窗口左边的距离,优先级比 `right` 高 | string | - |
81
+ | color | 设置按钮图标的颜色 | string | - |
82
+ | background | 设置按钮的背景色 | string | - |
83
+ | icon | 设置入口按钮的图标 | string | - |
84
+ | icon-family | 设置入口按钮的图标族 | string | - |
85
+ | item-list | 设置扩展按钮 | FabItem[] | [] |
86
+ | hide-name | 是否隐藏按钮名称 | boolean | false |
87
+ | overlay-closable | 点击遮罩是否隐藏扩展按钮 | boolean | false |
88
+ | duration | 扩展按钮显隐动画时长,单位 ms | number | 150 |
89
+ | draggable <sup>1.24.2+</sup> | 是否可拖拽 | boolean | false |
90
+ | axis <sup>1.24.2+</sup> | 允许拖拽的方向轴 | 'x' \| 'y' \| 'both' \| 'none' | 'y' |
91
+ | magnet <sup>1.24.2+</sup> | 吸附到指定轴最近的一边 | 'x' \| 'y' | - |
92
+ | gap-x <sup>1.24.2+</sup> | 悬浮按钮与窗口左右两边的最小间距,单位为 px | number | 24 |
93
+ | gap-y <sup>1.24.2+</sup> | 悬浮按钮与窗口上下两边的最小间距,单位为 px | number | 24 |
94
+ | offset (v-model) <sup>1.24.2+</sup> | 控制悬浮按钮的位置 | { x: number; y: number } | - |
89
95
 
90
96
  ### FabSlots
91
97
 
@@ -95,10 +101,11 @@ import Fab from 'sard-uniapp/components/fab/fab.vue'
95
101
 
96
102
  ### FabEmits
97
103
 
98
- | 事件 | 描述 | 类型 |
99
- | ------ | ------------------ | -------------------------------------- |
100
- | click | 点击入口按钮时触发 | (event: any) => void |
101
- | select | 点击扩展按钮时触发 | (item: FabItem, index: number) => void |
104
+ | 事件 | 描述 | 类型 |
105
+ | -------------------------------- | ---------------------------- | ------------------------------------------ |
106
+ | click | 点击入口按钮时触发 | (event: any) => void |
107
+ | select | 点击扩展按钮时触发 | (item: FabItem, index: number) => void |
108
+ | update:offset <sup>1.24.2+</sup> | 因用户拖拽导致位置改变时触发 | (offset: { x: number; y: number }) => void |
102
109
 
103
110
  ### FabItem
104
111
 
@@ -14,16 +14,33 @@ export interface FabProps {
14
14
  hideName?: boolean;
15
15
  overlayClosable?: boolean;
16
16
  duration?: number;
17
+ draggable?: boolean;
18
+ axis?: 'x' | 'y' | 'both' | 'none';
19
+ magnet?: 'x' | 'y';
20
+ gapX?: number;
21
+ gapY?: number;
22
+ offset?: {
23
+ x: number;
24
+ y: number;
25
+ };
17
26
  }
18
27
  export declare const defaultFabProps: () => {
19
28
  itemList: () => never[];
20
29
  overlayClosable: boolean;
21
30
  hideName: boolean;
22
31
  duration: number;
32
+ draggable: boolean;
33
+ axis: FabProps["axis"];
34
+ gapX: number;
35
+ gapY: number;
23
36
  };
24
37
  export interface FabEmits {
25
38
  (e: 'click', event: any): void;
26
39
  (e: 'select', item: FabItem, index: number): void;
40
+ (e: 'update:offset', offset: {
41
+ x: number;
42
+ y: number;
43
+ }): void;
27
44
  }
28
45
  export interface FabItem {
29
46
  name?: string;
@@ -2,13 +2,25 @@ import { type FabProps, FabItem } from './common';
2
2
  declare const _default: import("vue").DefineComponent<FabProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
3
3
  click: (event: any) => any;
4
4
  select: (item: FabItem, index: number) => any;
5
+ "update:offset": (offset: {
6
+ x: number;
7
+ y: number;
8
+ }) => any;
5
9
  }, string, import("vue").PublicProps, Readonly<FabProps> & Readonly<{
6
10
  onClick?: ((event: any) => any) | undefined;
7
11
  onSelect?: ((item: FabItem, index: number) => any) | undefined;
12
+ "onUpdate:offset"?: ((offset: {
13
+ x: number;
14
+ y: number;
15
+ }) => any) | undefined;
8
16
  }>, {
17
+ axis: "x" | "y" | "both" | "none";
9
18
  duration: number;
10
19
  overlayClosable: boolean;
20
+ draggable: boolean;
11
21
  itemList: FabItem[];
12
22
  hideName: boolean;
23
+ gapX: number;
24
+ gapY: number;
13
25
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
26
  export default _default;
@@ -6,7 +6,21 @@
6
6
  @click="onOverlayClick"
7
7
  />
8
8
 
9
- <view :class="fabClass" :style="fabStyle">
9
+ <view
10
+ :class="fabClass"
11
+ :style="fabStyle"
12
+ @touchstart="onTouchStart"
13
+ @touchmove.stop.prevent="onTouchMove"
14
+ @touchend="onTouchEnd"
15
+ @touchcancel="onTouchEnd"
16
+ @mousedown="onMouseDown"
17
+ >
18
+ <view :class="itemEntryClass" @click="onItemEntryClick">
19
+ <view :class="bem.e('item-btn')" :style="itemEntryBtnStyle">
20
+ <sar-icon :family="iconFamily || 'sari'" :name="icon || 'plus'" />
21
+ </view>
22
+ </view>
23
+
10
24
  <view
11
25
  :class="contentClass"
12
26
  :style="contentStyle"
@@ -31,11 +45,6 @@
31
45
  </view>
32
46
  </view>
33
47
  </view>
34
- <view :class="itemEntryClass" @click="onItemEntryClick">
35
- <view :class="bem.e('item-btn')" :style="itemEntryBtnStyle">
36
- <sar-icon :family="iconFamily || 'sari'" :name="icon || 'plus'" />
37
- </view>
38
- </view>
39
48
  </view>
40
49
  </template>
41
50
 
@@ -49,6 +58,7 @@ import {
49
58
  import { useTransition, useZIndex } from "../../use";
50
59
  import SarIcon from "../icon/icon.vue";
51
60
  import SarOverlay from "../overlay/overlay.vue";
61
+ import { useFloatingBubble } from "../floating-bubble/useFloatingBubble";
52
62
  /**
53
63
  * @property {string} rootClass 组件根元素类名,默认值:-。
54
64
  * @property {StyleValue} rootStyle 组件根元素样式,默认值:-。
@@ -64,8 +74,15 @@ import SarOverlay from "../overlay/overlay.vue";
64
74
  * @property {boolean} hideName 是否隐藏按钮名称,默认值:false。
65
75
  * @property {boolean} overlayClosable 点击遮罩是否隐藏扩展按钮,默认值:false。
66
76
  * @property {number} duration 扩展按钮显隐动画时长,单位 ms,默认值:150。
77
+ * @property {boolean} draggable 是否可拖拽,默认值:false。
78
+ * @property {'x' | 'y' | 'both' | 'none'} axis 允许拖拽的方向轴,默认值:'y'。
79
+ * @property {'x' | 'y'} magnet 吸附到指定轴最近的一边,默认值:-。
80
+ * @property {number} gapX 悬浮按钮与窗口左右两边的最小间距,单位为 px,默认值:24。
81
+ * @property {number} gapY 悬浮按钮与窗口上下两边的最小间距,单位为 px,默认值:24。
82
+ * @property {{ x: number; y: number }} offset 控制悬浮按钮的位置,默认值:-。
67
83
  * @event {(event: any) => void} click 点击入口按钮时触发
68
84
  * @event {(item: FabItem, index: number) => void} select 点击扩展按钮时触发
85
+ * @event {(offset: { x: number; y: number }) => void} update 因用户拖拽导致位置改变时触发
69
86
  */
70
87
  export default _defineComponent({
71
88
  components: {
@@ -93,9 +110,15 @@ export default _defineComponent({
93
110
  itemList: { type: Array, required: false },
94
111
  hideName: { type: Boolean, required: false },
95
112
  overlayClosable: { type: Boolean, required: false },
96
- duration: { type: Number, required: false }
113
+ duration: { type: Number, required: false },
114
+ draggable: { type: Boolean, required: false },
115
+ axis: { type: String, required: false },
116
+ magnet: { type: String, required: false },
117
+ gapX: { type: Number, required: false },
118
+ gapY: { type: Number, required: false },
119
+ offset: { type: Object, required: false }
97
120
  }, defaultFabProps()),
98
- emits: ["click", "select"],
121
+ emits: ["click", "select", "update:offset"],
99
122
  setup(__props, { expose: __expose, emit: __emit }) {
100
123
  __expose();
101
124
  const props = __props;
@@ -111,6 +134,7 @@ export default _defineComponent({
111
134
  })
112
135
  );
113
136
  const onItemEntryClick = (event) => {
137
+ if (stopBubbling.value) return;
114
138
  if (props.itemList && props.itemList.length > 0) {
115
139
  visible.value = !visible.value;
116
140
  if (visible.value) {
@@ -120,6 +144,7 @@ export default _defineComponent({
120
144
  emit("click", event);
121
145
  };
122
146
  const onItemClick = (item, index) => {
147
+ if (stopBubbling.value) return;
123
148
  visible.value = false;
124
149
  emit("select", item, index);
125
150
  };
@@ -128,32 +153,51 @@ export default _defineComponent({
128
153
  visible.value = false;
129
154
  }
130
155
  };
156
+ const {
157
+ onTouchStart,
158
+ onTouchMove,
159
+ onTouchEnd,
160
+ onMouseDown,
161
+ position,
162
+ initialized,
163
+ animated,
164
+ bubbleId,
165
+ stopBubbling,
166
+ windowWidth,
167
+ windowHeight
168
+ } = useFloatingBubble(props, emit, {
169
+ disabled: visible
170
+ });
171
+ const isTop = computed(() => {
172
+ return props.draggable ? position.value.y > windowHeight / 2 ? false : true : !isNullish(props.top);
173
+ });
174
+ const isLeft = computed(() => {
175
+ return props.draggable ? position.value.x > windowWidth / 2 ? false : true : !isNullish(props.left);
176
+ });
131
177
  const fabClass = computed(() => {
132
178
  return classNames(
133
179
  bem.b(),
134
- bem.m(isNullish(props.top) ? "bottom" : "top"),
135
- bem.m(isNullish(props.left) ? "right" : "left"),
180
+ bem.m(isTop.value ? "top" : "bottom"),
181
+ bem.m(isLeft.value ? "left" : "right"),
136
182
  bem.m("visible", visible.value),
137
- props.rootClass
183
+ bem.m("animated", animated.value),
184
+ bem.m("initialized", initialized.value),
185
+ bem.m("draggable", props.draggable),
186
+ props.rootClass,
187
+ bubbleId
138
188
  );
139
189
  });
140
190
  const fabStyle = computed(() => {
141
191
  return stringifyStyle(props.rootStyle, {
142
192
  zIndex: visible.value ? zIndex.value : null,
143
- top: props.top,
144
- left: props.left,
145
- right: !isNullish(props.left) ? "auto" : props.right,
146
- bottom: !isNullish(props.top) ? "auto" : props.bottom
147
- });
148
- });
149
- const contentClass = computed(() => {
150
- return classNames(bem.e("content"), transitionClass.value);
151
- });
152
- const contentStyle = computed(() => {
153
- return stringifyStyle({
154
- display: realVisible.value ? "flex" : "none",
155
- transitionDuration: props.duration + "ms",
156
- transformOrigin: `${isNullish(props.top) ? "bottom" : "top"} ${isNullish(props.left) ? "right" : "left"}`
193
+ ...props.draggable ? {
194
+ transform: `translate3d(${position.value.x}px, ${position.value.y}px, 0)`
195
+ } : {
196
+ top: props.top,
197
+ left: props.left,
198
+ right: isLeft.value ? "auto" : props.right,
199
+ bottom: isTop.value ? "auto" : props.bottom
200
+ }
157
201
  });
158
202
  });
159
203
  const itemEntryClass = computed(() => {
@@ -165,10 +209,20 @@ export default _defineComponent({
165
209
  background: props.background
166
210
  });
167
211
  });
212
+ const contentClass = computed(() => {
213
+ return classNames(bem.e("content"), transitionClass.value);
214
+ });
215
+ const contentStyle = computed(() => {
216
+ return stringifyStyle({
217
+ display: realVisible.value ? "flex" : "none",
218
+ transitionDuration: props.duration + "ms",
219
+ transformOrigin: `${isTop.value ? "top" : "bottom"} ${isLeft.value ? "left" : "right"}`
220
+ });
221
+ });
168
222
  const itemClass = computed(() => {
169
223
  return classNames(bem.e("item"));
170
224
  });
171
- const __returned__ = { props, emit, bem, visible, zIndex, increaseZIndex, realVisible, transitionClass, onTransitionEnd, onItemEntryClick, onItemClick, onOverlayClick, fabClass, fabStyle, contentClass, contentStyle, itemEntryClass, itemEntryBtnStyle, itemClass, get stringifyStyle() {
225
+ const __returned__ = { props, emit, bem, visible, zIndex, increaseZIndex, realVisible, transitionClass, onTransitionEnd, onItemEntryClick, onItemClick, onOverlayClick, onTouchStart, onTouchMove, onTouchEnd, onMouseDown, position, initialized, animated, bubbleId, stopBubbling, windowWidth, windowHeight, isTop, isLeft, fabClass, fabStyle, itemEntryClass, itemEntryBtnStyle, contentClass, contentStyle, itemClass, get stringifyStyle() {
172
226
  return stringifyStyle;
173
227
  }, SarIcon, SarOverlay };
174
228
  return __returned__;
@@ -7,14 +7,21 @@
7
7
  right: var(--sar-fab-right);
8
8
  bottom: var(--sar-fab-bottom);
9
9
  z-index: var(--sar-fab-z-index);
10
- gap: var(--sar-fab-item-gap);
11
10
  }
12
11
 
13
12
  @include e(content) {
14
13
  @include universal;
14
+ position: absolute;
15
15
  gap: var(--sar-fab-item-gap);
16
16
  }
17
17
 
18
+ @include m(draggable) {
19
+ top: 0;
20
+ left: 0;
21
+ right: auto;
22
+ bottom: auto;
23
+ }
24
+
18
25
  @include m(zoom-enter-from, zoom-leave-to) {
19
26
  opacity: 0;
20
27
  transform: scale(0.4);
@@ -26,7 +33,8 @@
26
33
  }
27
34
 
28
35
  @include m(zoom-enter-active, zoom-leave-active) {
29
- transition: transform var(--sar-fab-duration) ease-out,
36
+ transition:
37
+ transform var(--sar-fab-duration) ease-out,
30
38
  opacity var(--sar-fab-duration) ease-out;
31
39
  }
32
40
 
@@ -52,10 +60,12 @@
52
60
  @include universal;
53
61
  font-size: var(--sar-fab-item-name-font-size);
54
62
  color: var(--sar-fab-item-name-color);
63
+ white-space: nowrap;
55
64
  }
56
65
 
57
66
  @include e(item-btn) {
58
67
  @include universal;
68
+ flex: none;
59
69
  justify-content: center;
60
70
  align-items: center;
61
71
  width: var(--sar-fab-item-btn-size);
@@ -68,11 +78,21 @@
68
78
  }
69
79
 
70
80
  @include m(top) {
71
- flex-direction: column-reverse;
81
+ @include e(content) {
82
+ top: calc(100% + var(--sar-fab-item-gap));
83
+ }
84
+ }
85
+
86
+ @include m(bottom) {
87
+ @include e(content) {
88
+ bottom: calc(100% + var(--sar-fab-item-gap));
89
+ }
72
90
  }
73
91
 
74
92
  @include m(left) {
75
- align-items: flex-start;
93
+ @include e(content) {
94
+ left: 0;
95
+ }
76
96
 
77
97
  @include e(item) {
78
98
  flex-direction: row-reverse;
@@ -80,7 +100,9 @@
80
100
  }
81
101
 
82
102
  @include m(right) {
83
- align-items: flex-end;
103
+ @include e(content) {
104
+ right: 0;
105
+ }
84
106
  }
85
107
 
86
108
  @include m(visible) {
@@ -92,4 +114,12 @@
92
114
  }
93
115
  }
94
116
  }
117
+
118
+ @include m(initialized) {
119
+ opacity: 1;
120
+ }
121
+
122
+ @include m(animated) {
123
+ transition: transform var(--sar-fab-duration);
124
+ }
95
125
  }
@@ -40,15 +40,16 @@ import FloatingBubble from 'sard-uniapp/components/floating-bubble/floating-bubb
40
40
 
41
41
  ### FloatingBubbleProps
42
42
 
43
- | 属性 | 描述 | 类型 | 默认值 |
44
- | ---------------- | --------------------------------------- | ------------------------------ | ------ |
45
- | root-class | 组件根元素类名 | string | - |
46
- | root-style | 组件根元素样式 | StyleValue | - |
47
- | axis | 允许拖拽的方向轴 | 'x' \| 'y' \| 'both' \| 'none' | 'y' |
48
- | magnet | 吸附到指定轴最近的一边 | 'x' \| 'y' | - |
49
- | gap-x | 气泡与窗口左右两边的最小间距,单位为 px | number | 24 |
50
- | gap-y | 气泡与窗口上下两边的最小间距,单位为 px | number | 24 |
51
- | offset (v-model) | 控制气泡的位置 | { x: number; y: number } | - |
43
+ | 属性 | 描述 | 类型 | 默认值 |
44
+ | ---------------------------- | --------------------------------------- | ------------------------------ | ------ |
45
+ | root-class | 组件根元素类名 | string | - |
46
+ | root-style | 组件根元素样式 | StyleValue | - |
47
+ | axis | 允许拖拽的方向轴 | 'x' \| 'y' \| 'both' \| 'none' | 'y' |
48
+ | magnet | 吸附到指定轴最近的一边 | 'x' \| 'y' | - |
49
+ | gap-x | 气泡与窗口左右两边的最小间距,单位为 px | number | 24 |
50
+ | gap-y | 气泡与窗口上下两边的最小间距,单位为 px | number | 24 |
51
+ | offset (v-model) | 控制气泡的位置 | { x: number; y: number } | - |
52
+ | draggable <sup>1.24.2+</sup> | 是否可拖拽 | boolean | true |
52
53
 
53
54
  ### FloatingBubbleSlots
54
55
 
@@ -2,6 +2,7 @@ import { type StyleValue } from 'vue';
2
2
  export interface FloatingBubbleProps {
3
3
  rootStyle?: StyleValue;
4
4
  rootClass?: string;
5
+ draggable?: boolean;
5
6
  axis?: 'x' | 'y' | 'both' | 'none';
6
7
  magnet?: 'x' | 'y';
7
8
  gapX?: number;
@@ -12,6 +13,7 @@ export interface FloatingBubbleProps {
12
13
  };
13
14
  }
14
15
  export declare const defaultFloatingBubbleProps: {
16
+ draggable: boolean;
15
17
  axis: FloatingBubbleProps["axis"];
16
18
  gapX: number;
17
19
  gapY: number;
@@ -14,6 +14,7 @@ declare const __VLS_component: import("vue").DefineComponent<FloatingBubbleProps
14
14
  }) => any) | undefined;
15
15
  }>, {
16
16
  axis: "x" | "y" | "both" | "none";
17
+ draggable: boolean;
17
18
  gapX: number;
18
19
  gapY: number;
19
20
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -15,20 +15,12 @@
15
15
 
16
16
  <script>
17
17
  import { mergeDefaults as _mergeDefaults, defineComponent as _defineComponent } from "vue";
18
- import { computed, getCurrentInstance, onMounted, ref, watch } from "vue";
19
- import {
20
- classNames,
21
- stringifyStyle,
22
- createBem,
23
- getBoundingClientRect,
24
- uniqid,
25
- getWindowInfo,
26
- clamp
27
- } from "../../utils";
18
+ import { computed } from "vue";
19
+ import { classNames, stringifyStyle, createBem } from "../../utils";
28
20
  import {
29
21
  defaultFloatingBubbleProps
30
22
  } from "./common";
31
- import { useMouseDown, useTimeout } from "../../use";
23
+ import { useFloatingBubble } from "./useFloatingBubble";
32
24
  /**
33
25
  * @property {string} rootClass 组件根元素类名,默认值:-。
34
26
  * @property {StyleValue} rootStyle 组件根元素样式,默认值:-。
@@ -37,6 +29,7 @@ import { useMouseDown, useTimeout } from "../../use";
37
29
  * @property {number} gapX 气泡与窗口左右两边的最小间距,单位为 px,默认值:24。
38
30
  * @property {number} gapY 气泡与窗口上下两边的最小间距,单位为 px,默认值:24。
39
31
  * @property {{ x: number; y: number }} offset 控制气泡的位置,默认值:-。
32
+ * @property {boolean} draggable 是否可拖拽,默认值:true。
40
33
  * @event {(event: any) => void} click 点击时触发
41
34
  * @event {(offset: { x: number; y: number }) => void} update 因用户拖拽导致位置改变时触发
42
35
  */
@@ -51,6 +44,7 @@ export default _defineComponent({
51
44
  props: _mergeDefaults({
52
45
  rootStyle: { type: [Boolean, null, String, Object, Array], required: false, skipCheck: true },
53
46
  rootClass: { type: String, required: false },
47
+ draggable: { type: Boolean, required: false },
54
48
  axis: { type: String, required: false },
55
49
  magnet: { type: String, required: false },
56
50
  gapX: { type: Number, required: false },
@@ -63,112 +57,16 @@ export default _defineComponent({
63
57
  const props = __props;
64
58
  const emit = __emit;
65
59
  const bem = createBem("floating-bubble");
66
- const instance = getCurrentInstance();
67
- const bubbleId = uniqid();
68
- let bubbleRect;
69
- const { windowWidth, windowHeight } = getWindowInfo();
70
- let downCoord = {
71
- x: 0,
72
- y: 0
73
- };
74
- const initialized = ref(false);
75
- const position = ref({
76
- x: 0,
77
- y: 0
78
- });
79
- watch(
80
- () => props.offset,
81
- () => {
82
- if (props.offset) {
83
- position.value = props.offset;
84
- }
85
- }
86
- );
87
- const animated = ref(false);
88
- const { start: nonAnimatedLater, stop: cancelNonAnimated } = useTimeout(() => {
89
- animated.value = false;
90
- }, 500);
91
- function getMinX() {
92
- return props.gapX;
93
- }
94
- function getMaxX() {
95
- return windowWidth - props.gapX - bubbleRect.width;
96
- }
97
- function getMinY() {
98
- return props.gapY + 44 + 25;
99
- }
100
- function getMaxY() {
101
- return windowHeight - props.gapY - bubbleRect.height;
102
- }
103
- onMounted(async () => {
104
- bubbleRect = await getBoundingClientRect(`.${bubbleId}`, instance);
105
- position.value = props.offset ?? {
106
- x: getMaxX(),
107
- y: getMaxY()
108
- };
109
- bubbleRect = void 0;
110
- initialized.value = true;
111
- });
112
- const onTouchStart = async (event) => {
113
- cancelNonAnimated();
114
- animated.value = false;
115
- downCoord = {
116
- x: event.touches[0].clientX,
117
- y: event.touches[0].clientY
118
- };
119
- bubbleRect = await getBoundingClientRect(`.${bubbleId}`, instance);
120
- };
121
- const onTouchMove = (event) => {
122
- if (!bubbleRect) {
123
- return;
124
- }
125
- let x = 0;
126
- let y = 0;
127
- if (props.axis === "none") {
128
- x = getMaxX();
129
- y = getMaxY();
130
- } else {
131
- const deltaX = event.touches[0].clientX - downCoord.x;
132
- const deltaY = event.touches[0].clientY - downCoord.y;
133
- x = bubbleRect.left + deltaX;
134
- y = bubbleRect.top + deltaY;
135
- x = clamp(x, getMinX(), getMaxX());
136
- y = clamp(y, getMinY(), getMaxY());
137
- if (props.axis === "y") {
138
- x = getMaxX();
139
- } else if (props.axis === "x") {
140
- y = getMaxY();
141
- }
142
- }
143
- const offset = {
144
- x,
145
- y
146
- };
147
- position.value = offset;
148
- emit("update:offset", offset);
149
- };
150
- const onTouchEnd = () => {
151
- if (bubbleRect) {
152
- if (props.magnet) {
153
- let { x, y } = position.value;
154
- if (props.magnet === "x") {
155
- x = x < (windowWidth - bubbleRect.width) / 2 ? getMinX() : getMaxX();
156
- } else if (props.magnet === "y") {
157
- y = y < (windowHeight - bubbleRect.height) / 2 ? getMinY() : getMaxY();
158
- }
159
- const offset = {
160
- x,
161
- y
162
- };
163
- position.value = offset;
164
- emit("update:offset", offset);
165
- }
166
- }
167
- animated.value = true;
168
- nonAnimatedLater();
169
- bubbleRect = void 0;
170
- };
171
- const onMouseDown = useMouseDown(onTouchStart, onTouchMove, onTouchEnd);
60
+ const {
61
+ onTouchStart,
62
+ onTouchMove,
63
+ onTouchEnd,
64
+ onMouseDown,
65
+ position,
66
+ initialized,
67
+ animated,
68
+ bubbleId
69
+ } = useFloatingBubble(props, emit);
172
70
  const onClick = (event) => {
173
71
  emit("click", event);
174
72
  };
@@ -182,19 +80,12 @@ export default _defineComponent({
182
80
  );
183
81
  });
184
82
  const floatingBubbleStyle = computed(() => {
83
+ const { x, y } = position.value;
185
84
  return stringifyStyle(props.rootStyle, {
186
- transform: `translate3d(${position.value.x}px, ${position.value.y}px, 0)`
85
+ transform: `translate3d(${x}px, ${y}px, 0)`
187
86
  });
188
87
  });
189
- const __returned__ = { props, emit, bem, instance, bubbleId, get bubbleRect() {
190
- return bubbleRect;
191
- }, set bubbleRect(v) {
192
- bubbleRect = v;
193
- }, windowWidth, windowHeight, get downCoord() {
194
- return downCoord;
195
- }, set downCoord(v) {
196
- downCoord = v;
197
- }, initialized, position, animated, nonAnimatedLater, cancelNonAnimated, getMinX, getMaxX, getMinY, getMaxY, onTouchStart, onTouchMove, onTouchEnd, onMouseDown, onClick, floatingBubbleClass, floatingBubbleStyle };
88
+ const __returned__ = { props, emit, bem, onTouchStart, onTouchMove, onTouchEnd, onMouseDown, position, initialized, animated, bubbleId, onClick, floatingBubbleClass, floatingBubbleStyle };
198
89
  return __returned__;
199
90
  }
200
91
  });
@@ -0,0 +1,43 @@
1
+ import { MaybeRef } from 'vue';
2
+ export interface UseFloatingBubbleProps {
3
+ draggable?: boolean;
4
+ gapX: number;
5
+ gapY: number;
6
+ axis: 'x' | 'y' | 'none' | 'both';
7
+ magnet?: 'x' | 'y';
8
+ offset?: {
9
+ x: number;
10
+ y: number;
11
+ };
12
+ }
13
+ export interface UseFloatingBubbleEmits {
14
+ (e: 'update:offset', offset: {
15
+ x: number;
16
+ y: number;
17
+ }): void;
18
+ }
19
+ export interface UseFloatingBubbleOptions {
20
+ disabled?: MaybeRef<boolean>;
21
+ }
22
+ export declare function useFloatingBubble(props: UseFloatingBubbleProps, emit: UseFloatingBubbleEmits, options?: UseFloatingBubbleOptions): {
23
+ onTouchStart: (event: TouchEvent) => Promise<void>;
24
+ onTouchMove: (event: TouchEvent) => void;
25
+ onTouchEnd: () => void;
26
+ onMouseDown: (event: MouseEvent) => void;
27
+ position: import("vue").Ref<{
28
+ x: number;
29
+ y: number;
30
+ }, {
31
+ x: number;
32
+ y: number;
33
+ } | {
34
+ x: number;
35
+ y: number;
36
+ }>;
37
+ initialized: import("vue").Ref<boolean, boolean>;
38
+ animated: import("vue").Ref<boolean, boolean>;
39
+ bubbleId: string;
40
+ stopBubbling: import("vue").Ref<boolean, boolean>;
41
+ windowWidth: number;
42
+ windowHeight: number;
43
+ };
@@ -0,0 +1,137 @@
1
+ import { computed, getCurrentInstance, onMounted, ref, unref, watch, } from 'vue';
2
+ import { useMouseDown, useTimeout } from '../../use';
3
+ import { clamp, getBoundingClientRect, getWindowInfo, uniqid, } from '../../utils';
4
+ export function useFloatingBubble(props, emit, options = {}) {
5
+ const disabled = computed(() => !props.draggable || unref(options.disabled));
6
+ const instance = getCurrentInstance();
7
+ const bubbleId = uniqid();
8
+ const initialized = ref(false);
9
+ let bubbleRect;
10
+ const { windowWidth, windowHeight } = getWindowInfo();
11
+ let downCoord = {
12
+ x: 0,
13
+ y: 0,
14
+ };
15
+ const position = ref({
16
+ x: 0,
17
+ y: 0,
18
+ });
19
+ const animated = ref(false);
20
+ const stopBubbling = ref(false);
21
+ const { start: nonAnimatedLater, stop: cancelNonAnimated } = useTimeout(() => {
22
+ animated.value = false;
23
+ }, 500);
24
+ function getMinX() {
25
+ return props.gapX;
26
+ }
27
+ function getMaxX() {
28
+ return windowWidth - props.gapX - bubbleRect.width;
29
+ }
30
+ function getMinY() {
31
+ return props.gapY + 44 + 25;
32
+ }
33
+ function getMaxY() {
34
+ return windowHeight - props.gapY - bubbleRect.height;
35
+ }
36
+ const onTouchStart = async (event) => {
37
+ stopBubbling.value = false;
38
+ if (disabled.value)
39
+ return;
40
+ cancelNonAnimated();
41
+ animated.value = false;
42
+ downCoord = {
43
+ x: event.touches[0].clientX,
44
+ y: event.touches[0].clientY,
45
+ };
46
+ bubbleRect = await getBoundingClientRect(`.${bubbleId}`, instance);
47
+ };
48
+ const onTouchMove = (event) => {
49
+ if (disabled.value)
50
+ return;
51
+ if (!bubbleRect) {
52
+ return;
53
+ }
54
+ let x = 0;
55
+ let y = 0;
56
+ if (props.axis === 'none') {
57
+ x = getMaxX();
58
+ y = getMaxY();
59
+ }
60
+ else {
61
+ const deltaX = event.touches[0].clientX - downCoord.x;
62
+ const deltaY = event.touches[0].clientY - downCoord.y;
63
+ const deviation = 10;
64
+ if (Math.abs(deltaX) > deviation || Math.abs(deltaY) > deviation) {
65
+ stopBubbling.value = true;
66
+ }
67
+ x = bubbleRect.left + deltaX;
68
+ y = bubbleRect.top + deltaY;
69
+ x = clamp(x, getMinX(), getMaxX());
70
+ y = clamp(y, getMinY(), getMaxY());
71
+ if (props.axis === 'y') {
72
+ x = getMaxX();
73
+ }
74
+ else if (props.axis === 'x') {
75
+ y = getMaxY();
76
+ }
77
+ }
78
+ const offset = {
79
+ x,
80
+ y,
81
+ };
82
+ position.value = offset;
83
+ emit('update:offset', offset);
84
+ };
85
+ const onTouchEnd = () => {
86
+ if (disabled.value)
87
+ return;
88
+ if (bubbleRect) {
89
+ if (props.magnet) {
90
+ let { x, y } = position.value;
91
+ if (props.magnet === 'x') {
92
+ x = x < (windowWidth - bubbleRect.width) / 2 ? getMinX() : getMaxX();
93
+ }
94
+ else if (props.magnet === 'y') {
95
+ y = y < (windowHeight - bubbleRect.height) / 2 ? getMinY() : getMaxY();
96
+ }
97
+ const offset = {
98
+ x,
99
+ y,
100
+ };
101
+ position.value = offset;
102
+ emit('update:offset', offset);
103
+ }
104
+ }
105
+ animated.value = true;
106
+ nonAnimatedLater();
107
+ bubbleRect = undefined;
108
+ };
109
+ const onMouseDown = useMouseDown(onTouchStart, onTouchMove, onTouchEnd);
110
+ onMounted(async () => {
111
+ bubbleRect = await getBoundingClientRect(`.${bubbleId}`, instance);
112
+ position.value = props.offset ?? {
113
+ x: getMaxX(),
114
+ y: getMaxY(),
115
+ };
116
+ bubbleRect = undefined;
117
+ initialized.value = true;
118
+ });
119
+ watch(() => props.offset, () => {
120
+ if (props.offset) {
121
+ position.value = props.offset;
122
+ }
123
+ });
124
+ return {
125
+ onTouchStart,
126
+ onTouchMove,
127
+ onTouchEnd,
128
+ onMouseDown,
129
+ position,
130
+ initialized,
131
+ animated,
132
+ bubbleId,
133
+ stopBubbling,
134
+ windowWidth,
135
+ windowHeight,
136
+ };
137
+ }
@@ -77,14 +77,14 @@ type PopoutBeforeClose = (
77
77
 
78
78
  ### PopoutSlots
79
79
 
80
- | 插槽 | 描述 | 属性 |
81
- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
82
- | default | 自定义主体内容 | - |
83
- | title | 自定义标题 | - |
84
- | title-prepend <sup>1.19.2+</sup> | 自定义标题前面内容 | - |
85
- | cancel | 自定义确定按钮内容 | - |
86
- | confirm | 自定义取消按钮内容 | - |
87
- | visible | 同默认插槽,额外接收一些判断弹出框显示状态的属性,用于小程序端多节点渲染的性能问题;`whole` 当开始显示到完全隐藏时为真,`already` 当开始显示时到以后都为真 | { whole: boolean, already: boolean } |
80
+ | 插槽 | 描述 | 属性 |
81
+ | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
82
+ | default | 自定义主体内容 | - |
83
+ | title | 自定义标题 | - |
84
+ | title-prepend <sup>1.19.2+</sup> | 自定义标题前面内容 | - |
85
+ | cancel <sup>1.24.2+</sup> | 自定义取消按钮内容 | { onClick: () => void; loading: boolean; visible?: boolean; text: string } |
86
+ | confirm <sup>1.24.2+</sup> | 自定义确定按钮内容 | { onClick: () => void; loading: boolean; visible?: boolean; text: string; disabled?: boolean } |
87
+ | visible | 同默认插槽,额外接收一些判断弹出框显示状态的属性,用于小程序端多节点渲染的性能问题;`whole` 当开始显示到完全隐藏时为真,`already` 当开始显示时到以后都为真 | { whole: boolean, already: boolean } |
88
88
 
89
89
  ### PopoutEmits
90
90
 
@@ -34,8 +34,19 @@ export declare const defaultPopoutProps: {
34
34
  export interface PopoutSlots {
35
35
  default?(props: Record<string, never>): any;
36
36
  title?(props: Record<string, never>): any;
37
- cancel?(props: Record<string, never>): any;
38
- confirm?(props: Record<string, never>): any;
37
+ cancel?(props: {
38
+ onClick: () => void;
39
+ loading: boolean;
40
+ visible?: boolean;
41
+ text: string;
42
+ }): any;
43
+ confirm?(props: {
44
+ onClick: () => void;
45
+ loading: boolean;
46
+ visible?: boolean;
47
+ text: string;
48
+ disabled?: boolean;
49
+ }): any;
39
50
  visible?(props: {
40
51
  whole: boolean;
41
52
  already: boolean;
@@ -12,18 +12,23 @@
12
12
  <view :class="popoutClass" :style="popoutStyle" @transitionend.stop>
13
13
  <view :class="classNames(bem.e('header'), bem.em('header', props.type))">
14
14
  <view v-if="type === 'compact'" :class="bem.e('button-wrap')">
15
- <sar-button
16
- type="pale-text"
17
- theme="neutral"
18
- :root-class="classNames(bem.e('header-cancel'))"
15
+ <slot
16
+ name="cancel"
17
+ :on-click="onCancel"
19
18
  :loading="loading.cancel"
20
- block
21
- @click="onCancel"
19
+ :text="mergedCancelText"
22
20
  >
23
- <template v-if="cancelText">{{ cancelText }}</template>
24
- <slot v-else-if="$slots.cancel" name="cancel"></slot>
25
- <template v-else>{{ t('cancel') }}</template>
26
- </sar-button>
21
+ <sar-button
22
+ type="pale-text"
23
+ theme="neutral"
24
+ :root-class="classNames(bem.e('header-cancel'))"
25
+ :loading="loading.cancel"
26
+ block
27
+ @click="onCancel"
28
+ >
29
+ {{ mergedCancelText }}
30
+ </sar-button>
31
+ </slot>
27
32
  </view>
28
33
  <slot name="title-prepend"></slot>
29
34
  <view :class="bem.e('title')">
@@ -33,19 +38,25 @@
33
38
  <slot v-else-if="$slots.title" name="title"></slot>
34
39
  </view>
35
40
  <view v-if="type === 'compact'" :class="bem.e('button-wrap')">
36
- <sar-button
37
- type="pale-text"
38
- theme="primary"
39
- :root-class="classNames(bem.e('header-confirm'))"
40
- :loading="loading.confirm"
41
+ <slot
42
+ name="confirm"
43
+ :on-click="onConfirm"
41
44
  :disabled="confirmDisabled"
42
- block
43
- @click="onConfirm"
45
+ :loading="loading.confirm"
46
+ :text="mergedConfirmText"
44
47
  >
45
- <template v-if="confirmText">{{ confirmText }}</template>
46
- <slot v-else-if="$slots.confirm" name="confirm"></slot>
47
- <template v-else>{{ t('confirm') }}</template>
48
- </sar-button>
48
+ <sar-button
49
+ type="pale-text"
50
+ theme="primary"
51
+ :root-class="classNames(bem.e('header-confirm'))"
52
+ :loading="loading.confirm"
53
+ :disabled="confirmDisabled"
54
+ block
55
+ @click="onConfirm"
56
+ >
57
+ {{ mergedConfirmText }}
58
+ </sar-button>
59
+ </slot>
49
60
  </view>
50
61
  <view
51
62
  v-if="type === 'loose' && showClose"
@@ -60,33 +71,46 @@
60
71
  <slot></slot>
61
72
  <slot name="visible" :whole="wholeVisible" :already="already"></slot>
62
73
  <view v-if="showFooter && type === 'loose'" :class="bem.e('footer')">
63
- <sar-button
64
- v-if="showCancel"
65
- type="pale"
66
- theme="primary"
67
- round
74
+ <slot
75
+ name="cancel"
76
+ :on-click="onCancel"
77
+ :visible="showCancel"
68
78
  :loading="loading.cancel"
69
- block
70
- @click="onCancel"
79
+ :text="mergedCancelText"
71
80
  >
72
- <template v-if="cancelText">{{ cancelText }}</template>
73
- <slot v-else-if="$slots.cancel" name="cancel"></slot>
74
- <template v-else>{{ t('cancel') }}</template>
75
- </sar-button>
76
- <sar-button
77
- v-if="showConfirm"
78
- type="default"
79
- theme="primary"
80
- round
81
- :loading="loading.confirm"
81
+ <sar-button
82
+ v-if="showCancel"
83
+ type="pale"
84
+ theme="primary"
85
+ round
86
+ :loading="loading.cancel"
87
+ block
88
+ @click="onCancel"
89
+ >
90
+ {{ mergedCancelText }}
91
+ </sar-button>
92
+ </slot>
93
+ <slot
94
+ name="confirm"
95
+ :visible="showConfirm"
96
+ :on-click="onConfirm"
82
97
  :disabled="confirmDisabled"
83
- block
84
- @click="onConfirm"
98
+ :loading="loading.confirm"
99
+ :text="mergedConfirmText"
85
100
  >
86
- <template v-if="confirmText">{{ confirmText }}</template>
87
- <slot v-else-if="$slots.confirm" name="confirm"></slot>
88
- <template v-else>{{ t('confirm') }}</template>
89
- </sar-button>
101
+ <sar-button
102
+ v-if="showConfirm"
103
+ type="default"
104
+ theme="primary"
105
+ round
106
+ :loading="loading.confirm"
107
+ :disabled="confirmDisabled"
108
+ block
109
+ @click="onConfirm"
110
+ >
111
+ {{ mergedConfirmText }}
112
+ </sar-button>
113
+ </slot>
90
114
  </view>
91
115
  </view>
92
116
  </sar-popup>
@@ -269,13 +293,19 @@ export default _defineComponent({
269
293
  const onCancel = () => {
270
294
  perhapsClose("cancel");
271
295
  };
296
+ const mergedConfirmText = computed(() => {
297
+ return props.confirmText || t("confirm");
298
+ });
299
+ const mergedCancelText = computed(() => {
300
+ return props.cancelText || t("cancel");
301
+ });
272
302
  const popoutClass = computed(() => {
273
303
  return classNames(bem.b(), props.rootClass);
274
304
  });
275
305
  const popoutStyle = computed(() => {
276
306
  return stringifyStyle(props.rootStyle);
277
307
  });
278
- const __returned__ = { props, emit, bem, t, innerVisible, already, wholeVisible, onBeforeEnter, onAfterLeave, callVisibleHook, onVisibleHook, loading, readonlyLoading, asyncSet, perhapsClose, onOverlayClick, onCloseClick, onConfirm, onCancel, popoutClass, popoutStyle, get classNames() {
308
+ const __returned__ = { props, emit, bem, t, innerVisible, already, wholeVisible, onBeforeEnter, onAfterLeave, callVisibleHook, onVisibleHook, loading, readonlyLoading, asyncSet, perhapsClose, onOverlayClick, onCloseClick, onConfirm, onCancel, mergedConfirmText, mergedCancelText, popoutClass, popoutStyle, get classNames() {
279
309
  return classNames;
280
310
  }, SarPopup, SarButton, SarIcon };
281
311
  return __returned__;
@@ -1,7 +1,11 @@
1
1
  <template>
2
2
  <view :class="tagClass" :style="tagStyle" @click="$emit('click', $event)">
3
3
  <slot></slot>
4
- <view v-if="closable" :class="iconClass" @click="$emit('close', $event)">
4
+ <view
5
+ v-if="closable"
6
+ :class="iconClass"
7
+ @click.stop="$emit('close', $event)"
8
+ >
5
9
  <sar-icon family="sari" name="close" />
6
10
  </view>
7
11
  </view>
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "id": "sard-uniapp",
3
3
  "name": "sard-uniapp",
4
4
  "displayName": "sard-uniapp",
5
- "version": "1.24.1",
5
+ "version": "1.24.2",
6
6
  "description": "sard-uniapp 是一套基于 Uniapp + Vue3 框架开发的兼容多端的 UI 组件库",
7
7
  "main": "index.js",
8
8
  "scripts": {