@veltra/desktop 1.7.4 → 1.7.6

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.
@@ -0,0 +1,235 @@
1
+ import { date } from '@cat-kit/core'
2
+ import { describe, expect, it } from 'vitest'
3
+ import { createApp, h, nextTick, ref } from 'vue'
4
+
5
+ import UDatePicker from '../date-picker.vue'
6
+
7
+ function mountDatePicker(
8
+ props: Record<string, unknown> = {},
9
+ emits: Record<string, Function> = {}
10
+ ) {
11
+ const host = document.createElement('div')
12
+ document.body.appendChild(host)
13
+
14
+ const model = ref(props.modelValue)
15
+ const app = createApp({
16
+ render() {
17
+ return h(UDatePicker, {
18
+ ...props,
19
+ modelValue: model.value,
20
+ 'onUpdate:modelValue': (value: unknown) => {
21
+ model.value = value
22
+ emits['update:modelValue']?.(value)
23
+ },
24
+ onChange: (date?: Date) => {
25
+ emits.change?.(date)
26
+ }
27
+ })
28
+ }
29
+ })
30
+
31
+ app.component('Transition', (_, { slots }) => slots.default?.())
32
+
33
+ app.mount(host)
34
+
35
+ return {
36
+ host,
37
+ model,
38
+ app,
39
+ unmount() {
40
+ app.unmount()
41
+ host.remove()
42
+ }
43
+ }
44
+ }
45
+
46
+ describe('UDatePicker', () => {
47
+ it('defaults dataType to string and emits change with native Date', async () => {
48
+ let changedDate: Date | undefined
49
+ let updatedVal: unknown
50
+
51
+ const { host, unmount } = mountDatePicker(
52
+ {},
53
+ {
54
+ 'update:modelValue': (val: unknown) => {
55
+ updatedVal = val
56
+ },
57
+ change: (d?: Date) => {
58
+ changedDate = d
59
+ }
60
+ }
61
+ )
62
+
63
+ try {
64
+ const inputEl = host.querySelector('input')
65
+ expect(inputEl).not.toBeNull()
66
+ inputEl?.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
67
+ await nextTick()
68
+ await nextTick()
69
+
70
+ const cell = document.querySelector('.u-date-panel__cell.is-current') as HTMLElement
71
+ expect(cell).not.toBeNull()
72
+ cell.click()
73
+ await nextTick()
74
+
75
+ expect(typeof updatedVal).toBe('string')
76
+ expect(updatedVal).toMatch(/^\d{4}-\d{2}-\d{2}$/)
77
+ expect(changedDate instanceof Date).toBe(true)
78
+ } finally {
79
+ unmount()
80
+ }
81
+ })
82
+
83
+ it('supports dataType="date" and emits native Date for modelValue and change', async () => {
84
+ let changedDate: Date | undefined
85
+ let updatedVal: unknown
86
+
87
+ const { host, unmount } = mountDatePicker(
88
+ { dataType: 'date', valueFormat: 'yyyyMMdd' },
89
+ {
90
+ 'update:modelValue': (val: unknown) => {
91
+ updatedVal = val
92
+ },
93
+ change: (d?: Date) => {
94
+ changedDate = d
95
+ }
96
+ }
97
+ )
98
+
99
+ try {
100
+ const inputEl = host.querySelector('input')
101
+ inputEl?.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
102
+ await nextTick()
103
+ await nextTick()
104
+
105
+ const cell = document.querySelector('.u-date-panel__cell.is-current') as HTMLElement
106
+ cell.click()
107
+ await nextTick()
108
+
109
+ expect(updatedVal instanceof Date).toBe(true)
110
+ expect(changedDate instanceof Date).toBe(true)
111
+ } finally {
112
+ unmount()
113
+ }
114
+ })
115
+
116
+ it('supports dataType="timestamp" and emits timestamp for modelValue and native Date for change', async () => {
117
+ let changedDate: Date | undefined
118
+ let updatedVal: unknown
119
+
120
+ const { host, unmount } = mountDatePicker(
121
+ { dataType: 'timestamp', valueFormat: 'yyyyMMdd' },
122
+ {
123
+ 'update:modelValue': (val: unknown) => {
124
+ updatedVal = val
125
+ },
126
+ change: (d?: Date) => {
127
+ changedDate = d
128
+ }
129
+ }
130
+ )
131
+
132
+ try {
133
+ const inputEl = host.querySelector('input')
134
+ inputEl?.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
135
+ await nextTick()
136
+ await nextTick()
137
+
138
+ const cell = document.querySelector('.u-date-panel__cell.is-current') as HTMLElement
139
+ cell.click()
140
+ await nextTick()
141
+
142
+ expect(typeof updatedVal).toBe('number')
143
+ expect(updatedVal).toBeGreaterThan(0)
144
+ expect(changedDate instanceof Date).toBe(true)
145
+ } finally {
146
+ unmount()
147
+ }
148
+ })
149
+
150
+ it('applies valueFormat when dataType="string"', async () => {
151
+ let changedDate: Date | undefined
152
+ let updatedVal: unknown
153
+
154
+ const { host, unmount } = mountDatePicker(
155
+ { dataType: 'string', valueFormat: 'yyyy/MM/dd' },
156
+ {
157
+ 'update:modelValue': (val: unknown) => {
158
+ updatedVal = val
159
+ },
160
+ change: (d?: Date) => {
161
+ changedDate = d
162
+ }
163
+ }
164
+ )
165
+
166
+ try {
167
+ const inputEl = host.querySelector('input')
168
+ inputEl?.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
169
+ await nextTick()
170
+ await nextTick()
171
+
172
+ const cell = document.querySelector('.u-date-panel__cell.is-current') as HTMLElement
173
+ cell.click()
174
+ await nextTick()
175
+
176
+ expect(typeof updatedVal).toBe('string')
177
+ expect(updatedVal).toMatch(/^\d{4}\/\d{2}\/\d{2}$/)
178
+ expect(changedDate instanceof Date).toBe(true)
179
+ } finally {
180
+ unmount()
181
+ }
182
+ })
183
+
184
+ it('emits undefined for modelValue and change when cleared', async () => {
185
+ let changedDate: Date | undefined = new Date()
186
+ let updatedVal: unknown = '2024-05-01'
187
+
188
+ const { host, unmount } = mountDatePicker(
189
+ { modelValue: '2024-05-01', clearable: true },
190
+ {
191
+ 'update:modelValue': (val: unknown) => {
192
+ updatedVal = val
193
+ },
194
+ change: (d?: Date) => {
195
+ changedDate = d
196
+ }
197
+ }
198
+ )
199
+
200
+ try {
201
+ const inputWrapper = host.querySelector('.u-input') as HTMLElement
202
+ inputWrapper.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
203
+ await new Promise((r) => setTimeout(r, 50))
204
+
205
+ const clearBtn = host.querySelector('.u-input__clear') as HTMLElement
206
+ expect(clearBtn).not.toBeNull()
207
+ clearBtn.click()
208
+ await nextTick()
209
+
210
+ expect(updatedVal).toBeUndefined()
211
+ expect(changedDate).toBeUndefined()
212
+ } finally {
213
+ unmount()
214
+ }
215
+ })
216
+
217
+ it('correctly parses modelValue of Date, timestamp, and custom formatted string', async () => {
218
+ const dateObj = new Date(2024, 4, 15)
219
+ const d1 = mountDatePicker({ modelValue: dateObj })
220
+ await nextTick()
221
+ expect(d1.host.querySelector('input')?.value).toBe('2024-05-15')
222
+ d1.unmount()
223
+
224
+ const ts = date('2024-06-20').timestamp
225
+ const d2 = mountDatePicker({ modelValue: ts })
226
+ await nextTick()
227
+ expect(d2.host.querySelector('input')?.value).toBe('2024-06-20')
228
+ d2.unmount()
229
+
230
+ const d3 = mountDatePicker({ modelValue: '20240725', valueFormat: 'yyyyMMdd' })
231
+ await nextTick()
232
+ expect(d3.host.querySelector('input')?.value).toBe('2024-07-25')
233
+ d3.unmount()
234
+ })
235
+ })
@@ -36,7 +36,7 @@
36
36
  </template>
37
37
 
38
38
  <script lang="ts" setup>
39
- import { date, type Dater } from '@cat-kit/core'
39
+ import { date, Dater } from '@cat-kit/core'
40
40
  import { useFormFallbackProps, useUserAction } from '@veltra/compositions'
41
41
  import { Calendar } from '@veltra/icons/normal'
42
42
  import { bem, FORM_EMPTY_CONTENT } from '@veltra/utils'
@@ -55,6 +55,7 @@ defineOptions({ name: 'UDatePicker', inheritAttrs: false })
55
55
  const props = withDefaults(defineProps<DatePickerProps>(), {
56
56
  placeholder: '选择日期',
57
57
  type: 'date',
58
+ dataType: 'string',
58
59
  disabled: undefined,
59
60
  readonly: undefined,
60
61
  clearable: true
@@ -91,11 +92,28 @@ const currentDate = shallowRef<Dater>()
91
92
 
92
93
  const { userAction, isUserActive } = useUserAction()
93
94
 
95
+ function parseModelValue(val?: string | number | Date): Dater | undefined {
96
+ if (val == null || val === '') return undefined
97
+ if (val instanceof Date || typeof val === 'number') {
98
+ const d = date(val)
99
+ return isNaN(d.timestamp) ? undefined : d
100
+ }
101
+ if (typeof val === 'string') {
102
+ if (props.dataType === 'string' && props.valueFormat) {
103
+ const parsed = Dater.parse(val, props.valueFormat)
104
+ if (!isNaN(parsed.timestamp)) return parsed
105
+ }
106
+ const fallback = date(val)
107
+ return isNaN(fallback.timestamp) ? undefined : fallback
108
+ }
109
+ return undefined
110
+ }
111
+
94
112
  watch(
95
113
  () => props.modelValue,
96
114
  (modelValue) => {
97
115
  if (isUserActive()) return
98
- currentDate.value = modelValue ? date(modelValue) : undefined
116
+ currentDate.value = parseModelValue(modelValue)
99
117
  },
100
118
  { immediate: true }
101
119
  )
@@ -104,9 +122,20 @@ const displayedValue = computed(() => {
104
122
  return currentDate.value?.format(formatStr.value) ?? ''
105
123
  })
106
124
 
125
+ function formatModelValue(d: Dater) {
126
+ if (props.dataType === 'date') {
127
+ return d.raw
128
+ }
129
+ if (props.dataType === 'timestamp') {
130
+ return d.timestamp
131
+ }
132
+ return d.format(props.valueFormat ?? formatStr.value)
133
+ }
134
+
107
135
  const commitSelectedDate = userAction((date: Dater) => {
108
136
  currentDate.value = date
109
- emit('update:modelValue', date.format(formatStr.value))
137
+ emit('update:modelValue', formatModelValue(date))
138
+ emit('change', date.raw)
110
139
  })
111
140
 
112
141
  async function handleSelectDate(date: Dater) {
@@ -117,5 +146,6 @@ async function handleSelectDate(date: Dater) {
117
146
  function handleClear() {
118
147
  currentDate.value = undefined
119
148
  emit('update:modelValue', undefined)
149
+ emit('change', undefined)
120
150
  }
121
151
  </script>
@@ -14,7 +14,7 @@ export type CodeEditorLang =
14
14
  export interface CodeEditorProps extends Omit<FormComponentProps, 'size'> {
15
15
  modelValue?: string
16
16
  /**
17
- * 可选语言列表。长度大于 1 时在编辑器右上角显示内置语言选择器;
17
+ * 可选语言列表。长度大于 1 时在顶部工具栏显示内置语言选择器;
18
18
  * 仅一种语言时显示语言名称标签
19
19
  */
20
20
  langs?: CodeEditorLang[]
@@ -26,6 +26,8 @@ export interface CodeEditorProps extends Omit<FormComponentProps, 'size'> {
26
26
  suffix?: string
27
27
  /** 是否使用暗色主题 */
28
28
  dark?: boolean
29
+ /** 是否显示放大按钮,默认 true;false 时不渲染放大按钮 */
30
+ zoomable?: boolean
29
31
  /**
30
32
  * 默认显示的行数,用于撑起编辑器最小高度,超出后滚动
31
33
  * @default 8
@@ -7,7 +7,7 @@ export interface DatePanelProps {
7
7
  date?: Dater
8
8
  rangeDate?: [Dater, Dater]
9
9
  range?: boolean
10
- disabledDate?: (date: Dater) => boolean
10
+ disabledDate?: (date: Dater, raw: Date) => boolean
11
11
  type?: 'date' | 'month' | 'year'
12
12
  size?: FormComponentProps['size']
13
13
  }
@@ -1,6 +1,9 @@
1
1
  import type { Dater } from '@cat-kit/core'
2
2
  import type { DeconstructValue, FormComponentProps } from '@veltra/utils'
3
3
 
4
+ /** 绑定值类型 */
5
+ export type DatePickerDataType = 'date' | 'timestamp' | 'string'
6
+
4
7
  /** date-picker组件属性 */
5
8
  export interface DatePickerProps extends FormComponentProps {
6
9
  modelValue?: string | number | Date
@@ -12,8 +15,13 @@ export interface DatePickerProps extends FormComponentProps {
12
15
  format?: string
13
16
  /** 日期值格式化, 当没有指定时默认使用format属性,仅当值和显示的内容不一致时才需要使用到该属性 */
14
17
  valueFormat?: string
18
+ /**
19
+ * 绑定值的类型,默认为字符串。
20
+ * 当 dataType 没有指定为字符串时,valueFormat 属性不生效
21
+ */
22
+ dataType?: DatePickerDataType
15
23
  /** 最小可选日期 */
16
- disabledDate?: (date: Dater) => boolean
24
+ disabledDate?: (date: Dater, raw: Date) => boolean
17
25
  /** 是否显示清除按钮 */
18
26
  clearable?: boolean
19
27
  }
@@ -21,6 +29,7 @@ export interface DatePickerProps extends FormComponentProps {
21
29
  /** date-picker组件定义的事件 */
22
30
  export interface DatePickerEmits {
23
31
  (e: 'update:modelValue', value?: string | number | Date): void
32
+ (e: 'change', date?: Date): void
24
33
  }
25
34
 
26
35
  /** date-picker组件暴露的属性和方法(组件内部使用) */
@@ -13,7 +13,7 @@ export interface DateRangePickerProps extends FormComponentProps {
13
13
  /** 日期值格式化, 当没有指定时默认使用format属性,仅当值和显示的内容不一致时才需要使用到该属性 */
14
14
  valueFormat?: string
15
15
  /** 最小可选日期 */
16
- disabledDate?: (date: Dater) => boolean
16
+ disabledDate?: (date: Dater, raw: Date) => boolean
17
17
  /** 是否显示清除按钮 */
18
18
  clearable?: boolean
19
19
  }