@wot-ui/ui 2.1.0 → 2.2.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.
package/changelog.md CHANGED
@@ -1,6 +1,22 @@
1
1
  # 更新日志
2
2
 
3
3
 
4
+ ## [2.2.0](https://github.com/wot-ui/wot-ui/compare/v2.1.0...v2.2.0) (2026-06-30)
5
+
6
+
7
+ ### ✨ Features | 新功能
8
+
9
+ * ✨ VideoPreview 支持预览时默认 video 铺满屏幕 ([#79](https://github.com/wot-ui/wot-ui/issues/79)) ([8e97be7](https://github.com/wot-ui/wot-ui/commit/8e97be72f25043bd2160e510f56bbddc5c77943f)), closes [#77](https://github.com/wot-ui/wot-ui/issues/77)
10
+ * **button:** ✨ 新增 subtle 类型变体 ([#80](https://github.com/wot-ui/wot-ui/issues/80)) ([5c9e3d1](https://github.com/wot-ui/wot-ui/commit/5c9e3d1d12e29b926640fbc45fcb59586bea566b))
11
+
12
+
13
+ ### 🐛 Bug Fixes | Bug 修复
14
+
15
+ * 🐛 修复 Toast 组件图标设置图标大小后行高异常的问题 ([#84](https://github.com/wot-ui/wot-ui/issues/84)) ([13585ff](https://github.com/wot-ui/wot-ui/commit/13585ffdf7f6dc181c39ff16e861a5bcf7273eb7))
16
+ * 🐛 修复未渲染的表单项仍触发表单校验的问题 ([#78](https://github.com/wot-ui/wot-ui/issues/78)) ([01d08f1](https://github.com/wot-ui/wot-ui/commit/01d08f127ec786275af40b0b0515176173d57173)), closes [#73](https://github.com/wot-ui/wot-ui/issues/73)
17
+ * 🐛 修复支付宝小程序 tabs 子组件渲染顺序错误的问题 ([#83](https://github.com/wot-ui/wot-ui/issues/83)) ([58c4c61](https://github.com/wot-ui/wot-ui/commit/58c4c618de497a066b058cc911c3c01e0fa7548b))
18
+ * 修复文档中自动导入示例代码的问题 ([#75](https://github.com/wot-ui/wot-ui/issues/75)) ([be68ccd](https://github.com/wot-ui/wot-ui/commit/be68ccdede09476da833afd9583fbf97dc618291))
19
+
4
20
  ## [2.1.0](https://github.com/wot-ui/wot-ui/compare/v2.0.8...v2.1.0) (2026-06-10)
5
21
 
6
22
 
@@ -275,6 +275,7 @@ $button-variants: (
275
275
  "plain": ("show-border": true),
276
276
  "dashed": ("show-border": true, "border-style": dashed),
277
277
  "soft": ("hide-border": true, "soft-bg": true),
278
+ "subtle": ("show-border": true, "soft-bg": true),
278
279
  "text": ("hide-border": true)
279
280
  );
280
281
 
@@ -9,9 +9,9 @@ import { type LoadingProps } from '../wd-loading/types'
9
9
  export type ButtonType = 'primary' | 'success' | 'info' | 'warning' | 'danger'
10
10
  /**
11
11
  * 按钮变体
12
- * 可选值: 'base' | 'plain' | 'dashed' | 'soft' | 'text'
12
+ * 可选值: 'base' | 'plain' | 'dashed' | 'soft' | 'subtle' | 'text'
13
13
  */
14
- export type ButtonVariant = 'base' | 'plain' | 'dashed' | 'soft' | 'text'
14
+ export type ButtonVariant = 'base' | 'plain' | 'dashed' | 'soft' | 'subtle' | 'text'
15
15
  /**
16
16
  * 按钮尺寸
17
17
  * 可选值: 'mini' | 'small' | 'medium' | 'large'
@@ -204,7 +204,7 @@ export const buttonProps = {
204
204
  /**
205
205
  * 按钮变体
206
206
  * 类型: ButtonVariant
207
- * 可选值: 'base' | 'plain' | 'dashed' | 'soft' | 'text'
207
+ * 可选值: 'base' | 'plain' | 'dashed' | 'soft' | 'subtle' | 'text'
208
208
  * 不传则继承全局配置
209
209
  */
210
210
  variant: String as PropType<ButtonVariant>
@@ -57,12 +57,12 @@ async function validate(prop?: string | string[]): Promise<{ valid: boolean; err
57
57
  prop: issue.path.map((item) => String(item)).join('.'),
58
58
  message: issue.message
59
59
  }))
60
+ const childrenProps = getChildrenProps()
61
+ const visibleErrors = errors.filter((error) => getMatchedChildProp(error.prop, childrenProps))
60
62
  const filteredErrors =
61
63
  propsToValidate.length > 0
62
- ? errors.filter((error) =>
63
- propsToValidate.some((target) => error.prop === target || error.prop.startsWith(`${target}.`) || target.startsWith(`${error.prop}.`))
64
- )
65
- : errors
64
+ ? visibleErrors.filter((error) => propsToValidate.some((target) => isSameOrSubPath(error.prop, target)))
65
+ : visibleErrors
66
66
  const valid = filteredErrors.length === 0
67
67
 
68
68
  showMessage(filteredErrors)
@@ -81,9 +81,30 @@ async function validate(prop?: string | string[]): Promise<{ valid: boolean; err
81
81
  }
82
82
  }
83
83
 
84
+ function getChildrenProps() {
85
+ return children.map((e) => e.prop).filter((prop): prop is string => Boolean(prop))
86
+ }
87
+
88
+ function isSameOrSubPath(prop: string, target: string) {
89
+ return prop === target || prop.startsWith(`${target}.`) || target.startsWith(`${prop}.`)
90
+ }
91
+
92
+ function getMatchedChildProp(prop: string, childrenProps: string[]) {
93
+ return childrenProps.find((target) => target === prop) || childrenProps.find((target) => isSameOrSubPath(prop, target))
94
+ }
95
+
84
96
  function showMessage(errors: ErrorMessage[]) {
85
- const childrenProps = children.map((e) => e.prop).filter(Boolean)
86
- const messages = errors.filter((error) => error.message && childrenProps.includes(error.prop))
97
+ const childrenProps = getChildrenProps()
98
+ const messages = errors.reduce<ErrorMessage[]>((result, error) => {
99
+ const matchedProp = getMatchedChildProp(error.prop, childrenProps)
100
+ if (error.message && matchedProp) {
101
+ result.push({
102
+ prop: matchedProp,
103
+ message: error.message
104
+ })
105
+ }
106
+ return result
107
+ }, [])
87
108
  if (messages.length) {
88
109
  messages.sort((a, b) => {
89
110
  return childrenProps.indexOf(a.prop) - childrenProps.indexOf(b.prop)
@@ -100,7 +121,11 @@ function showMessage(errors: ErrorMessage[]) {
100
121
 
101
122
  function clearMessage(prop?: string) {
102
123
  if (prop) {
103
- errorMessages[prop] = ''
124
+ Object.keys(errorMessages).forEach((key) => {
125
+ if (isSameOrSubPath(key, prop)) {
126
+ errorMessages[key] = ''
127
+ }
128
+ })
104
129
  } else {
105
130
  Object.keys(errorMessages).forEach((key) => {
106
131
  errorMessages[key] = ''
@@ -54,7 +54,6 @@ $toast-loading-margin-bottom: var(--wot-toast-loading-margin-bottom, $spacing-ex
54
54
 
55
55
  @include edeep(icon) {
56
56
  font-size: $toast-icon-size;
57
- line-height: $toast-icon-size;
58
57
  @include when(vertical) {
59
58
  margin-bottom: $toast-icon-margin-bottom;
60
59
  }
@@ -33,6 +33,15 @@ $video-preview-close-z-index: var(--wot-video-preview-close-z-index, 10) !defaul
33
33
  width: 100%;
34
34
  height: $video-preview-video-height;
35
35
  transition: all 0.3s ease;
36
+
37
+ @include when(fullscreen) {
38
+ height: 100%;
39
+ }
40
+ }
41
+
42
+ @include e(video-player) {
43
+ width: 100%;
44
+ height: 100%;
36
45
  }
37
46
 
38
47
  @include e(close) {
@@ -42,8 +51,17 @@ $video-preview-close-z-index: var(--wot-video-preview-close-z-index, 10) !defaul
42
51
  justify-content: center;
43
52
  align-items: center;
44
53
  top: $video-preview-close-margin;
45
- right: $video-preview-close-margin;
46
54
  cursor: pointer;
55
+
56
+ @include when(left-top) {
57
+ left: $video-preview-close-margin;
58
+ right: auto;
59
+ }
60
+
61
+ @include when(right-top) {
62
+ left: auto;
63
+ right: $video-preview-close-margin;
64
+ }
47
65
  }
48
66
 
49
67
  @include edeep(close-icon) {
@@ -10,7 +10,9 @@
10
10
  import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
11
11
  import { baseProps } from '../../common/props'
12
12
 
13
- import { makeStringProp, makeNumberProp } from '../../common/props'
13
+ import { makeBooleanProp, makeNumberProp, makeStringProp } from '../../common/props'
14
+
15
+ export type VideoPreviewClosePosition = 'left-top' | 'right-top'
14
16
 
15
17
  export const videoPreviewProps = {
16
18
  ...baseProps,
@@ -24,6 +26,19 @@ export const videoPreviewProps = {
24
26
  * 默认值: 1000
25
27
  */
26
28
  zIndex: makeNumberProp(1000),
29
+ /**
30
+ * 是否全屏预览
31
+ * 类型: boolean
32
+ * 默认值: false
33
+ */
34
+ fullScreen: makeBooleanProp(false),
35
+ /**
36
+ * 关闭按钮位置
37
+ * 类型: VideoPreviewClosePosition
38
+ * 可选值: 'left-top' | 'right-top'
39
+ * 默认值: 'left-top'
40
+ */
41
+ closePosition: makeStringProp<VideoPreviewClosePosition>('left-top'),
27
42
  /**
28
43
  * 打开时的回调
29
44
  */
@@ -45,6 +60,10 @@ export type VideoPreviewProps = ExtractPropTypes<typeof videoPreviewProps>
45
60
  export interface VideoPreviewOptions extends PreviewVideo {
46
61
  show?: boolean
47
62
  zIndex?: number
63
+ /** 是否全屏预览 */
64
+ fullScreen?: boolean
65
+ /** 关闭按钮位置 */
66
+ closePosition?: VideoPreviewClosePosition
48
67
  /** 打开时的回调 */
49
68
  onOpen?: () => void
50
69
  /** 关闭时的回调 */
@@ -58,7 +77,7 @@ export type VideoPreview = {
58
77
 
59
78
  export type VideoPreviewExpose = {
60
79
  /** 打开预览 */
61
- open: (video: PreviewVideo) => void
80
+ open: (video: VideoPreviewOptions) => void
62
81
  /** 关闭预览 */
63
82
  close: () => void
64
83
  }
@@ -1,31 +1,51 @@
1
1
  <template>
2
- <wd-overlay
3
- :show="state.show"
4
- :z-index="options.zIndex"
5
- :lock-scroll="true"
6
- :custom-class="`wd-video-preview ${customClass}`"
7
- :custom-style="customStyle"
8
- @click="close"
9
- @enter="handleEnter"
10
- @after-leave="handleAfterLeave"
11
- >
12
- <view class="wd-video-preview__video" @click.stop="">
13
- <video
14
- class="wd-video-preview__video"
15
- v-if="state.visible && previewVideo.url"
16
- :controls="true"
17
- :poster="previewVideo.poster"
18
- :title="previewVideo.title"
19
- play-btn-position="center"
20
- :enableNative="true"
21
- :src="previewVideo.url"
22
- :enable-progress-gesture="false"
23
- ></video>
24
- </view>
25
- <view class="wd-video-preview__close" @click.stop="close">
26
- <wd-icon name="close" custom-class="wd-video-preview__close-icon" />
27
- </view>
28
- </wd-overlay>
2
+ <!-- #ifdef APP-PLUS -->
3
+ <view v-if="state.show" :class="`wd-video-preview ${customClass}`" :style="`z-index: ${options.zIndex}; ${customStyle}`" @click="close">
4
+ <!-- #endif -->
5
+ <!-- #ifndef APP-PLUS -->
6
+ <wd-overlay
7
+ :show="state.show"
8
+ :z-index="options.zIndex"
9
+ :lock-scroll="true"
10
+ :custom-class="`wd-video-preview ${customClass}`"
11
+ :custom-style="customStyle"
12
+ @click="close"
13
+ @enter="handleEnter"
14
+ @after-leave="handleAfterLeave"
15
+ >
16
+ <!-- #endif -->
17
+
18
+ <view :class="videoClass" @click.stop="">
19
+ <video
20
+ :id="videoId"
21
+ class="wd-video-preview__video-player"
22
+ v-if="state.visible && previewVideo.url"
23
+ :controls="true"
24
+ :poster="previewVideo.poster"
25
+ :title="previewVideo.title"
26
+ play-btn-position="center"
27
+ :enableNative="true"
28
+ :src="previewVideo.url"
29
+ :enable-progress-gesture="false"
30
+ @fullscreenchange="handleFullscreenChange"
31
+ ></video>
32
+ </view>
33
+ <!-- #ifndef APP-PLUS -->
34
+ <view :class="closeClass" @click.stop="close">
35
+ <wd-icon name="close" custom-class="wd-video-preview__close-icon" />
36
+ </view>
37
+ <!-- #endif -->
38
+ <!-- #ifdef APP-PLUS -->
39
+ <view v-if="!isFullScreen" :class="closeClass" @click.stop="close">
40
+ <wd-icon name="close" custom-class="wd-video-preview__close-icon" />
41
+ </view>
42
+ <!-- #endif -->
43
+ <!-- #ifndef APP-PLUS -->
44
+ </wd-overlay>
45
+ <!-- #endif -->
46
+ <!-- #ifdef APP-PLUS -->
47
+ </view>
48
+ <!-- #endif -->
29
49
  </template>
30
50
 
31
51
  <script lang="ts">
@@ -44,10 +64,10 @@ export default {
44
64
  <script lang="ts" setup>
45
65
  import wdIcon from '../wd-icon/wd-icon.vue'
46
66
  import wdOverlay from '../wd-overlay/wd-overlay.vue'
47
- import { reactive, ref, inject, watch, computed } from 'vue'
67
+ import { reactive, ref, inject, watch, computed, getCurrentInstance, nextTick } from 'vue'
48
68
  import { videoPreviewProps, type PreviewVideo, type VideoPreviewOptions, type VideoPreviewExpose } from './types'
49
69
  import { defaultOptions, getVideoPreviewOptionKey } from './index'
50
- import { isDef, isFunction } from '../../common/util'
70
+ import { isDef, isFunction, uuid } from '../../common/util'
51
71
 
52
72
  const props = defineProps(videoPreviewProps)
53
73
 
@@ -62,6 +82,12 @@ const state = reactive({
62
82
  })
63
83
 
64
84
  const previewVideo = reactive<PreviewVideo>({ url: '', poster: '', title: '' })
85
+ const fullScreenValue = ref<boolean | undefined>()
86
+ const closePositionValue = ref<VideoPreviewOptions['closePosition']>()
87
+
88
+ // App 端原生 video 走系统全屏,需要唯一 id 创建上下文
89
+ const videoId = `wd-video-preview-${uuid()}`
90
+ const { proxy } = getCurrentInstance() as any
65
91
 
66
92
  // 获取注入的选项
67
93
  const videoPreviewOptionKey = getVideoPreviewOptionKey(props.selector)
@@ -73,6 +99,21 @@ const options = computed(() => ({
73
99
  onClose: videoPreviewOption.value.onClose || props.onClose || null
74
100
  }))
75
101
 
102
+ const isFullScreen = computed(() => {
103
+ const optionFullScreen = videoPreviewOption.value.fullScreen
104
+ if (isDef(optionFullScreen)) return optionFullScreen!
105
+ return isDef(fullScreenValue.value) ? fullScreenValue.value! : props.fullScreen
106
+ })
107
+
108
+ const closePosition = computed(() => {
109
+ const optionClosePosition = videoPreviewOption.value.closePosition
110
+ if (isDef(optionClosePosition)) return optionClosePosition!
111
+ return isDef(closePositionValue.value) ? closePositionValue.value! : props.closePosition
112
+ })
113
+
114
+ const videoClass = computed(() => ['wd-video-preview__video', isFullScreen.value ? 'is-fullscreen' : ''])
115
+ const closeClass = computed(() => ['wd-video-preview__close', `is-${closePosition.value}`])
116
+
76
117
  // 监听选项变化
77
118
  watch(
78
119
  () => videoPreviewOption.value,
@@ -87,11 +128,23 @@ watch(
87
128
  () => state.show,
88
129
  (newVal, oldVal) => {
89
130
  if (newVal && !oldVal) {
131
+ // #ifdef APP-PLUS
132
+ // App 端不使用 overlay,直接驱动 video 渲染并进入系统全屏
133
+ state.visible = true
134
+ enterFullScreen()
135
+ // #endif
90
136
  emit('open')
91
137
  if (isFunction(options.value.onOpen)) {
92
138
  options.value.onOpen()
93
139
  }
94
140
  } else if (!newVal && oldVal) {
141
+ // #ifdef APP-PLUS
142
+ // App 端关闭时重置 video 渲染与预览数据
143
+ state.visible = false
144
+ previewVideo.url = ''
145
+ previewVideo.poster = ''
146
+ previewVideo.title = ''
147
+ // #endif
95
148
  emit('close')
96
149
  if (isFunction(options.value.onClose)) {
97
150
  options.value.onClose()
@@ -106,9 +159,12 @@ function reset(option: VideoPreviewOptions) {
106
159
  previewVideo.url = option.url
107
160
  previewVideo.poster = option.poster
108
161
  previewVideo.title = option.title
162
+ fullScreenValue.value = option.fullScreen
163
+ closePositionValue.value = option.closePosition
109
164
  }
110
165
  }
111
166
 
167
+ // #ifndef APP-PLUS
112
168
  function handleEnter() {
113
169
  state.visible = true
114
170
  }
@@ -119,15 +175,38 @@ function handleAfterLeave() {
119
175
  previewVideo.poster = ''
120
176
  previewVideo.title = ''
121
177
  }
178
+ // #endif
179
+
180
+ // App 端全屏预览时进入原生全屏播放,规避原生 video 同层渲染遮挡自定义浮层的问题
181
+ function enterFullScreen() {
182
+ // #ifdef APP-PLUS
183
+ if (!isFullScreen.value) return
184
+ nextTick(() => {
185
+ const videoContext = uni.createVideoContext(videoId, proxy)
186
+ videoContext && videoContext.requestFullScreen({ direction: 0 })
187
+ })
188
+ // #endif
189
+ }
190
+
191
+ // 监听原生全屏状态变化,全屏预览模式下退出全屏时关闭预览
192
+ function handleFullscreenChange(event: any) {
193
+ // #ifdef APP-PLUS
194
+ if (isFullScreen.value && event && event.detail && !event.detail.fullScreen) {
195
+ close()
196
+ }
197
+ // #endif
198
+ }
122
199
 
123
200
  function close() {
124
201
  state.show = false
125
202
  }
126
203
 
127
- function open(video: PreviewVideo) {
204
+ function open(video: VideoPreviewOptions) {
128
205
  previewVideo.url = video.url
129
206
  previewVideo.poster = video.poster
130
207
  previewVideo.title = video.title
208
+ fullScreenValue.value = video.fullScreen
209
+ closePositionValue.value = video.closePosition
131
210
  state.show = true
132
211
  }
133
212
 
@@ -80,14 +80,8 @@ export function useChildren<
80
80
  const link = (child: ComponentInternalInstance) => {
81
81
  if (child.proxy) {
82
82
  if (internalChildren.indexOf(child) === -1) {
83
- // #ifdef MP-ALIPAY
84
- internalChildren.unshift(child)
85
- publicChildren.unshift(child.proxy as Child)
86
- // #endif
87
- // #ifndef MP-ALIPAY
88
83
  internalChildren.push(child)
89
84
  publicChildren.push(child.proxy as Child)
90
- // #endif
91
85
  sortChildren(parent, publicChildren, internalChildren)
92
86
  }
93
87
  }
package/package.json CHANGED
@@ -1 +1 @@
1
- {"id":"wot-ui","name":"@wot-ui/ui","displayName":"wot-ui 一个轻量、美观、AI友好的 uni-app 组件库","version":"2.1.0","license":"MIT","description":"wot-ui 一个轻量、美观、AI友好的 uni-app 组件库。","keywords":["wot-ui","国际化","组件库","vue3","暗黑模式"],"main":"index.ts","repository":{"type":"git","url":"https://github.com/wot-ui/wot-ui.git"},"engines":{"HBuilderX":"^3.8.7"},"dcloudext":{"type":"component-vue","sale":{"regular":{"price":"0.00"},"sourcecode":{"price":"0.00"}},"contact":{"qq":""},"declaration":{"ads":"无","data":"插件不采集任何数据","permissions":"无"},"npmurl":"https://www.npmjs.com/package/@wot-ui/ui"},"vetur":{"tags":"tags.json","attributes":"attributes.json"},"web-types":"web-types.json","uni_modules":{"dependencies":[],"encrypt":[],"platforms":{"cloud":{"tcb":"y","aliyun":"y","alipay":"n"},"client":{"Vue":{"vue2":"n","vue3":"y"},"App":{"app-vue":"y","app-nvue":"n","app-uvue":"n"},"H5-mobile":{"Safari":"y","Android Browser":"y","微信浏览器(Android)":"y","QQ浏览器(Android)":"y"},"H5-pc":{"Chrome":"y","IE":"u","Edge":"y","Firefox":"y","Safari":"y"},"小程序":{"微信":"y","阿里":"y","百度":"u","字节跳动":"u","QQ":"y","钉钉":"y","快手":"u","飞书":"u","京东":"u"},"快应用":{"华为":"u","联盟":"u"}}}},"peerDependencies":{"vue":">=3.2.47"}}
1
+ {"id":"wot-ui","name":"@wot-ui/ui","displayName":"wot-ui 一个轻量、美观、AI友好的 uni-app 组件库","version":"2.2.0","license":"MIT","description":"wot-ui 一个轻量、美观、AI友好的 uni-app 组件库。","keywords":["wot-ui","国际化","组件库","vue3","暗黑模式"],"main":"index.ts","repository":{"type":"git","url":"https://github.com/wot-ui/wot-ui.git"},"engines":{"HBuilderX":"^3.8.7"},"dcloudext":{"type":"component-vue","sale":{"regular":{"price":"0.00"},"sourcecode":{"price":"0.00"}},"contact":{"qq":""},"declaration":{"ads":"无","data":"插件不采集任何数据","permissions":"无"},"npmurl":"https://www.npmjs.com/package/@wot-ui/ui"},"vetur":{"tags":"tags.json","attributes":"attributes.json"},"web-types":"web-types.json","uni_modules":{"dependencies":[],"encrypt":[],"platforms":{"cloud":{"tcb":"y","aliyun":"y","alipay":"n"},"client":{"Vue":{"vue2":"n","vue3":"y"},"App":{"app-vue":"y","app-nvue":"n","app-uvue":"n"},"H5-mobile":{"Safari":"y","Android Browser":"y","微信浏览器(Android)":"y","QQ浏览器(Android)":"y"},"H5-pc":{"Chrome":"y","IE":"u","Edge":"y","Firefox":"y","Safari":"y"},"小程序":{"微信":"y","阿里":"y","百度":"u","字节跳动":"u","QQ":"y","钉钉":"y","快手":"u","飞书":"u","京东":"u"},"快应用":{"华为":"u","联盟":"u"}}}},"peerDependencies":{"vue":">=3.2.47"}}