papayaui 0.3.0 → 0.3.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.
@@ -91,7 +91,7 @@ const onInput = (e: Event) => {
91
91
  let value = (e as unknown as EventDetail<{ value: string }>).detail.value
92
92
  // 数值类型处理,整数或小数
93
93
  if (props.type === 'number') {
94
- value = formatNumericTypeString(value, props.intLength, props.decimalLength)
94
+ value = formatNumericTypeString(value, props.intLength, props.decimalLength, props.allowNegative)
95
95
  }
96
96
  // 格式化函数处理
97
97
  if (typeof props.formatter === 'function') {
@@ -22,6 +22,10 @@ const inputNumberProps = {
22
22
  * 小数位长度
23
23
  */
24
24
  decimalLength: Number,
25
+ /**
26
+ * 是否允许输入负数
27
+ */
28
+ allowNegative: Boolean,
25
29
  }
26
30
 
27
31
  export const inputProps = {
@@ -16,6 +16,10 @@ export const tabPaneProps = {
16
16
  * 是否禁用标签
17
17
  */
18
18
  disabled: Boolean,
19
+ /**
20
+ * 自定义标题插槽名称
21
+ */
22
+ titleSlot: String,
19
23
  }
20
24
 
21
25
  export type TabPaneProps = ExtractPropTypes<typeof tabPaneProps>
@@ -15,7 +15,7 @@ const ns = useNamespace('tab__pane')
15
15
 
16
16
  const props = defineProps(tabPaneProps)
17
17
 
18
- const { title, disabled } = toRefs(props)
18
+ const { title, disabled, titleSlot } = toRefs(props)
19
19
 
20
20
  const instance = getCurrentInstance()
21
21
  const parent = getParentInstance<TabsProps, TabsExpose>(instance, 'tabs')
@@ -45,6 +45,7 @@ const init = () => {
45
45
  name,
46
46
  title,
47
47
  disabled,
48
+ titleSlot,
48
49
  })
49
50
  }
50
51
  }
@@ -61,7 +61,7 @@ export const tabsEmits = {
61
61
  click: (item: any) => isObject(item),
62
62
  }
63
63
 
64
- export type TabItem = Required<TabPaneProps>
64
+ export type TabItem = Required<Omit<TabPaneProps, 'titleSlot'>> & Pick<TabPaneProps, 'titleSlot'>
65
65
  export type TabItemValue = TabItem['name']
66
66
 
67
67
  export type TabsProps = ExtractPropTypes<typeof tabsProps>
@@ -25,7 +25,13 @@
25
25
  ]"
26
26
  @click="onChangeTab(item, index)"
27
27
  >
28
- {{ item.title }}
28
+ <slot
29
+ v-if="item.titleSlot && $slots[item.titleSlot]"
30
+ :name="item.titleSlot"
31
+ :item="item"
32
+ :index="index"
33
+ />
34
+ <text v-else>{{ item.title }}</text>
29
35
  </view>
30
36
  <view :class="ns.e('line')" :style="lineStyle"></view>
31
37
  </view>
@@ -81,6 +87,7 @@ const navList = computed<TabItem[]>(() => {
81
87
  title: tab[props.labelKey as keyof typeof tab] ?? '',
82
88
  name: props.valueKey ? tab[props.valueKey as keyof typeof tab] : index,
83
89
  disabled: tab['disabled' as keyof typeof tab] ?? false,
90
+ titleSlot: tab['titleSlot' as keyof typeof tab],
84
91
  }
85
92
  })
86
93
  }
@@ -151,6 +158,13 @@ const updateScrollLeft = async () => {
151
158
 
152
159
  const onChangeTab = (item: TabItem, _index: number) => {
153
160
  if (item.disabled) return
161
+
162
+ // 如果值没有变化,只触发 click 事件
163
+ if (modelValue.value === item.name) {
164
+ emit('click', item)
165
+ return
166
+ }
167
+
154
168
  emit('update:modelValue', item.name)
155
169
  emit('change', item)
156
170
  emit('click', item)
@@ -0,0 +1,306 @@
1
+ import { describe, test, expect } from 'vitest'
2
+ import { formatNumericTypeString, minAndMax, useInputNumber } from '.'
3
+
4
+ describe('formatNumericTypeString', () => {
5
+ test('should format basic numbers correctly', () => {
6
+ expect(formatNumericTypeString('123')).toBe('123')
7
+ expect(formatNumericTypeString('123.45')).toBe('123')
8
+ expect(formatNumericTypeString('abc123')).toBe('123')
9
+ expect(formatNumericTypeString('12.34.56', Number.MAX_SAFE_INTEGER.toString().length, 2)).toBe(
10
+ '12.34',
11
+ )
12
+ })
13
+
14
+ test('should format numbers with decimal length', () => {
15
+ expect(formatNumericTypeString('123.456', Number.MAX_SAFE_INTEGER.toString().length, 2)).toBe(
16
+ '123.45',
17
+ )
18
+ expect(formatNumericTypeString('123.4', Number.MAX_SAFE_INTEGER.toString().length, 2)).toBe(
19
+ '123.4',
20
+ )
21
+ expect(formatNumericTypeString('123', Number.MAX_SAFE_INTEGER.toString().length, 2)).toBe('123')
22
+ expect(
23
+ formatNumericTypeString('123.123456', Number.MAX_SAFE_INTEGER.toString().length, 3),
24
+ ).toBe('123.123')
25
+ })
26
+
27
+ test('should format numbers with integer length limit', () => {
28
+ expect(formatNumericTypeString('123456', 3)).toBe('123')
29
+ expect(formatNumericTypeString('12', 3)).toBe('12')
30
+ expect(formatNumericTypeString('1234.56', 3, 2)).toBe('123.56')
31
+ })
32
+
33
+ test('should remove leading dot', () => {
34
+ expect(formatNumericTypeString('.123')).toBe('123')
35
+ expect(formatNumericTypeString('.123', Number.MAX_SAFE_INTEGER.toString().length, 2)).toBe(
36
+ '123',
37
+ )
38
+ })
39
+
40
+ test('should handle negative numbers when allowNegative is true', () => {
41
+ expect(
42
+ formatNumericTypeString('-123', Number.MAX_SAFE_INTEGER.toString().length, 0, true),
43
+ ).toBe('-123')
44
+ expect(
45
+ formatNumericTypeString('-123.45', Number.MAX_SAFE_INTEGER.toString().length, 2, true),
46
+ ).toBe('-123.45')
47
+ expect(
48
+ formatNumericTypeString('-123.456', Number.MAX_SAFE_INTEGER.toString().length, 2, true),
49
+ ).toBe('-123.45')
50
+ expect(
51
+ formatNumericTypeString('-0.5', Number.MAX_SAFE_INTEGER.toString().length, 2, true),
52
+ ).toBe('-0.5')
53
+ })
54
+
55
+ test('should remove negative sign when allowNegative is false', () => {
56
+ expect(
57
+ formatNumericTypeString('-123', Number.MAX_SAFE_INTEGER.toString().length, 0, false),
58
+ ).toBe('123')
59
+ expect(
60
+ formatNumericTypeString('-123.45', Number.MAX_SAFE_INTEGER.toString().length, 2, false),
61
+ ).toBe('123.45')
62
+ })
63
+
64
+ test('should handle negative numbers with integer length limit', () => {
65
+ expect(formatNumericTypeString('-123456', 3, 0, true)).toBe('-123')
66
+ expect(formatNumericTypeString('-12', 3, 0, true)).toBe('-12')
67
+ expect(formatNumericTypeString('-1234.56', 3, 2, true)).toBe('-123.56')
68
+ })
69
+
70
+ test('should handle multiple negative signs correctly', () => {
71
+ expect(
72
+ formatNumericTypeString('--123', Number.MAX_SAFE_INTEGER.toString().length, 0, true),
73
+ ).toBe('-123')
74
+ expect(
75
+ formatNumericTypeString('-12-3', Number.MAX_SAFE_INTEGER.toString().length, 0, true),
76
+ ).toBe('-123')
77
+ expect(
78
+ formatNumericTypeString('1-23', Number.MAX_SAFE_INTEGER.toString().length, 0, true),
79
+ ).toBe('123')
80
+ })
81
+
82
+ test('should handle edge cases', () => {
83
+ expect(formatNumericTypeString('')).toBe('')
84
+ expect(formatNumericTypeString('0')).toBe('0')
85
+ expect(formatNumericTypeString('-0', Number.MAX_SAFE_INTEGER.toString().length, 0, true)).toBe(
86
+ '-0',
87
+ )
88
+ expect(formatNumericTypeString('abc')).toBe('')
89
+ expect(formatNumericTypeString('-', Number.MAX_SAFE_INTEGER.toString().length, 0, true)).toBe(
90
+ '-',
91
+ )
92
+ })
93
+ })
94
+
95
+ describe('minAndMax', () => {
96
+ test('should limit value by min', () => {
97
+ expect(minAndMax(5, 10)).toBe(10)
98
+ expect(minAndMax(10, 10)).toBe(10)
99
+ expect(minAndMax(15, 10)).toBe(15)
100
+ })
101
+
102
+ test('should limit value by max', () => {
103
+ expect(minAndMax(5, undefined, 10)).toBe(5)
104
+ expect(minAndMax(10, undefined, 10)).toBe(10)
105
+ expect(minAndMax(15, undefined, 10)).toBe(10)
106
+ })
107
+
108
+ test('should limit value by both min and max', () => {
109
+ expect(minAndMax(5, 10, 20)).toBe(10)
110
+ expect(minAndMax(15, 10, 20)).toBe(15)
111
+ expect(minAndMax(25, 10, 20)).toBe(20)
112
+ })
113
+
114
+ test('should handle negative min and max', () => {
115
+ expect(minAndMax(-15, -10, 10)).toBe(-10)
116
+ expect(minAndMax(-5, -10, 10)).toBe(-5)
117
+ expect(minAndMax(15, -10, 10)).toBe(10)
118
+ expect(minAndMax(-20, -10)).toBe(-10)
119
+ })
120
+
121
+ test('should return value when no limits provided', () => {
122
+ expect(minAndMax(5)).toBe(5)
123
+ expect(minAndMax(-5)).toBe(-5)
124
+ expect(minAndMax(0)).toBe(0)
125
+ })
126
+ })
127
+
128
+ describe('useInputNumber', () => {
129
+ test('should initialize with default values', () => {
130
+ const { numberVal, safeNumberVal } = useInputNumber({})
131
+
132
+ expect(numberVal.value).toBe(0)
133
+ expect(safeNumberVal.value).toBe(0)
134
+ })
135
+
136
+ test('should initialize with provided modelValue', () => {
137
+ const { numberVal, safeNumberVal } = useInputNumber({ modelValue: 10 })
138
+
139
+ expect(numberVal.value).toBe(10)
140
+ expect(safeNumberVal.value).toBe(10)
141
+ })
142
+
143
+ test('should initialize with string modelValue', () => {
144
+ const { numberVal, safeNumberVal } = useInputNumber({ modelValue: '25' })
145
+
146
+ expect(numberVal.value).toBe(25)
147
+ expect(safeNumberVal.value).toBe(25)
148
+ })
149
+
150
+ test('should handle negative modelValue', () => {
151
+ const { numberVal, safeNumberVal } = useInputNumber({ modelValue: -10, allowNegative: true })
152
+
153
+ expect(numberVal.value).toBe(-10)
154
+ expect(safeNumberVal.value).toBe(-10)
155
+ })
156
+
157
+ test('should add value correctly', () => {
158
+ const { numberVal, onAdd } = useInputNumber({ modelValue: 5, step: 2 })
159
+
160
+ expect(numberVal.value).toBe(5)
161
+ const newVal = onAdd()
162
+ expect(newVal).toBe(7)
163
+ expect(numberVal.value).toBe(7)
164
+ })
165
+
166
+ test('should reduce value correctly', () => {
167
+ const { numberVal, onReduce } = useInputNumber({ modelValue: 10, step: 3 })
168
+
169
+ expect(numberVal.value).toBe(10)
170
+ const newVal = onReduce()
171
+ expect(newVal).toBe(7)
172
+ expect(numberVal.value).toBe(7)
173
+ })
174
+
175
+ test('should reduce to negative value when allowNegative is true', () => {
176
+ const { numberVal, onReduce } = useInputNumber({
177
+ modelValue: 5,
178
+ step: 10,
179
+ min: -20,
180
+ allowNegative: true,
181
+ })
182
+
183
+ expect(numberVal.value).toBe(5)
184
+ const newVal = onReduce()
185
+ expect(newVal).toBe(-5)
186
+ expect(numberVal.value).toBe(-5)
187
+ })
188
+
189
+ test('should respect min limit', () => {
190
+ const { numberVal, onReduce } = useInputNumber({ modelValue: 5, step: 10, min: 0 })
191
+
192
+ expect(numberVal.value).toBe(5)
193
+ const newVal = onReduce()
194
+ expect(newVal).toBe(0)
195
+ expect(numberVal.value).toBe(0)
196
+ })
197
+
198
+ test('should respect max limit', () => {
199
+ const { numberVal, onAdd } = useInputNumber({ modelValue: 15, step: 10, max: 20 })
200
+
201
+ expect(numberVal.value).toBe(15)
202
+ const newVal = onAdd()
203
+ expect(newVal).toBe(20)
204
+ expect(numberVal.value).toBe(20)
205
+ })
206
+
207
+ test('should format decimal correctly', () => {
208
+ const { numberVal, onAdd } = useInputNumber({
209
+ modelValue: 0,
210
+ step: 0.1,
211
+ decimalLength: 2,
212
+ min: 0,
213
+ })
214
+
215
+ expect(numberVal.value).toBe(0)
216
+ onAdd()
217
+ expect(numberVal.value).toBe(0.1)
218
+ onAdd()
219
+ expect(numberVal.value).toBe(0.2)
220
+ })
221
+
222
+ test('should format value string correctly', () => {
223
+ const { getFormatVal } = useInputNumber({ intLength: 3, decimalLength: 2 })
224
+
225
+ expect(getFormatVal('123.456')).toBe('123.45')
226
+ expect(getFormatVal('1234.56')).toBe('123.56')
227
+ expect(getFormatVal('12.3')).toBe('12.3')
228
+ })
229
+
230
+ test('should format negative value string when allowNegative is true', () => {
231
+ const { getFormatVal } = useInputNumber({
232
+ intLength: 3,
233
+ decimalLength: 2,
234
+ allowNegative: true,
235
+ })
236
+
237
+ expect(getFormatVal('-123.456')).toBe('-123.45')
238
+ expect(getFormatVal('-1234.56')).toBe('-123.56')
239
+ expect(getFormatVal('-12.3')).toBe('-12.3')
240
+ })
241
+
242
+ test('should not format negative value string when allowNegative is false', () => {
243
+ const { getFormatVal } = useInputNumber({
244
+ intLength: 3,
245
+ decimalLength: 2,
246
+ allowNegative: false,
247
+ })
248
+
249
+ expect(getFormatVal('-123.456')).toBe('123.45')
250
+ expect(getFormatVal('-1234.56')).toBe('123.56')
251
+ })
252
+
253
+ test('should not update value when updateValue is false', () => {
254
+ const { numberVal, onAdd } = useInputNumber({ modelValue: 5, step: 2 })
255
+
256
+ expect(numberVal.value).toBe(5)
257
+ const newVal = onAdd(false)
258
+ expect(newVal).toBe(7)
259
+ expect(numberVal.value).toBe(5) // value should not be updated
260
+ })
261
+
262
+ test('should handle complex scenario with negative numbers', () => {
263
+ const { numberVal, onAdd, onReduce } = useInputNumber({
264
+ modelValue: 0,
265
+ step: 5,
266
+ min: -20,
267
+ max: 20,
268
+ allowNegative: true,
269
+ })
270
+
271
+ expect(numberVal.value).toBe(0)
272
+
273
+ // Add to positive
274
+ onAdd()
275
+ expect(numberVal.value).toBe(5)
276
+ onAdd()
277
+ expect(numberVal.value).toBe(10)
278
+ onAdd()
279
+ expect(numberVal.value).toBe(15)
280
+
281
+ // Try to exceed max
282
+ onAdd()
283
+ expect(numberVal.value).toBe(20)
284
+ onAdd()
285
+ expect(numberVal.value).toBe(20) // capped at max
286
+
287
+ // Reduce to negative
288
+ onReduce()
289
+ onReduce()
290
+ onReduce()
291
+ onReduce()
292
+ expect(numberVal.value).toBe(0)
293
+ onReduce()
294
+ expect(numberVal.value).toBe(-5)
295
+ onReduce()
296
+ expect(numberVal.value).toBe(-10)
297
+
298
+ // Try to exceed min
299
+ onReduce()
300
+ onReduce()
301
+ onReduce()
302
+ expect(numberVal.value).toBe(-20)
303
+ onReduce()
304
+ expect(numberVal.value).toBe(-20) // capped at min
305
+ })
306
+ })
@@ -16,6 +16,8 @@ export interface UseInputNumberProps {
16
16
  intLength?: number
17
17
  /** 小数位长度 */
18
18
  decimalLength?: number
19
+ /** 是否允许输入负数 */
20
+ allowNegative?: boolean
19
21
  }
20
22
 
21
23
  export function useInputNumber(props: IncludeRefs<UseInputNumberProps>) {
@@ -28,6 +30,7 @@ export function useInputNumber(props: IncludeRefs<UseInputNumberProps>) {
28
30
  step: _props.step ?? 1,
29
31
  intLength: _props.intLength ?? Number.MAX_SAFE_INTEGER.toString().length,
30
32
  decimalLength: _props.decimalLength ?? 0,
33
+ allowNegative: _props.allowNegative ?? false,
31
34
  })
32
35
 
33
36
  const numberVal = ref<number | null>(
@@ -52,7 +55,7 @@ export function useInputNumber(props: IncludeRefs<UseInputNumberProps>) {
52
55
  )
53
56
 
54
57
  const getFormatVal = (text: InputNumberValue) => {
55
- return formatNumericTypeString(text, state.intLength, state.decimalLength)
58
+ return formatNumericTypeString(text, state.intLength, state.decimalLength, state.allowNegative)
56
59
  }
57
60
 
58
61
  const onUpdate = (type: 'add' | 'reduce', updateValue = true) => {
@@ -96,13 +99,25 @@ export const formatNumericTypeString = (
96
99
  value: InputNumberValue,
97
100
  intLength = Number.MAX_SAFE_INTEGER.toString().length,
98
101
  decimalLength = 0,
102
+ allowNegative = false,
99
103
  ) => {
100
104
  // 根据整数位和小数位处理值
101
- value = String(value)
105
+ const strValue = String(value)
106
+
107
+ // 移除所有非数字和点的字符
108
+ value = strValue
102
109
  .replace(/^\./, '')
103
- .replace(/[^0-9.]/g, '')
110
+ .replace(allowNegative ? /[^0-9.-]/g : /[^0-9.]/g, '')
111
+ // 如果允许负数,只保留第一个负号,且只能在开头
112
+ .replace(allowNegative ? /(?!^)-/g : /-/g, '')
113
+
104
114
  const valSplit = value.split('.').slice(0, 2)
105
- valSplit[0] = valSplit[0].slice(0, intLength)
115
+ // 处理整数部分(保留负号)
116
+ const intPart = valSplit[0]
117
+ const hasNegativeSign = intPart.startsWith('-')
118
+ const intDigits = intPart.replace('-', '')
119
+ valSplit[0] = (hasNegativeSign ? '-' : '') + intDigits.slice(0, intLength)
120
+
106
121
  if (typeof valSplit[1] !== 'undefined' && decimalLength) {
107
122
  valSplit[1] = valSplit[1].slice(0, decimalLength)
108
123
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "papayaui",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "适用于uniapp的ui扩展库",
5
5
  "main": "index.ts",
6
6
  "module": "index.ts",