@kine-design/core 0.0.1-beta.4 → 0.0.1-beta.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.
- package/.vlaude/last-session-id +1 -0
- package/components/base/affix/useAffix.ts +2 -1
- package/components/base/anchor/useAnchor.ts +2 -1
- package/components/base/autoComplete/useAutoComplete.ts +2 -1
- package/components/base/button/api.ts +1 -1
- package/components/base/button/props.d.ts +3 -1
- package/components/base/carousel/useCarousel.ts +2 -1
- package/components/base/cascader/useCascader.ts +2 -1
- package/components/base/checkbox/useCheckbox.ts +2 -1
- package/components/base/collapse/useCollapse.ts +2 -1
- package/components/base/datePicker/__tests__/useDatePicker.test.ts +239 -0
- package/components/base/datePicker/api.ts +4 -0
- package/components/base/datePicker/props.d.ts +26 -0
- package/components/base/dropdown/useDropdown.ts +2 -1
- package/components/base/image/__tests__/useImage.test.ts +174 -0
- package/components/base/input/api.ts +5 -1
- package/components/base/input/props.d.ts +26 -1
- package/components/base/input/useInput.ts +12 -2
- package/components/base/inputNumber/__tests__/useInputNumber.test.ts +153 -0
- package/components/base/inputNumber/api.ts +2 -1
- package/components/base/inputNumber/props.d.ts +9 -1
- package/components/base/inputNumber/useInputNumber.ts +53 -11
- package/components/base/loading/api.ts +1 -1
- package/components/base/loading/props.d.ts +3 -1
- package/components/base/popover/usePopover.ts +4 -3
- package/components/base/progress/api.ts +3 -1
- package/components/base/progress/props.d.ts +15 -0
- package/components/base/rate/useRate.ts +2 -1
- package/components/base/select/api.ts +3 -0
- package/components/base/select/props.d.ts +21 -1
- package/components/base/select/useSelect.ts +2 -1
- package/components/base/slider/api.ts +3 -1
- package/components/base/slider/props.d.ts +14 -0
- package/components/base/slider/useSlider.ts +6 -2
- package/components/base/steps/__tests__/useSteps.test.ts +46 -0
- package/components/base/switch/useSwitch.tsx +2 -1
- package/components/base/tabs/useTabs.ts +2 -1
- package/components/base/tag/api.ts +1 -1
- package/components/base/tag/props.d.ts +3 -1
- package/components/base/timePicker/__tests__/useTimePicker.test.ts +118 -0
- package/components/base/timePicker/api.ts +4 -1
- package/components/base/timePicker/props.d.ts +20 -0
- package/components/base/tooltip/api.ts +1 -0
- package/components/base/tooltip/props.d.ts +6 -0
- package/components/base/transfer/useTransfer.ts +2 -1
- package/components/base/tree/__tests__/tree.test.ts +214 -0
- package/components/message/dialog/api.ts +2 -1
- package/components/message/dialog/props.d.ts +7 -0
- package/components/message/drawer/api.ts +1 -0
- package/components/message/drawer/props.d.ts +7 -0
- package/components/message/notification/__tests__/useNotification.test.ts +129 -0
- package/components/message/popover/usePopover.ts +4 -4
- package/components/other/darkMode/useDarkMode.ts +2 -2
- package/components/template/menu/__tests__/useMenu.test.ts +157 -0
- package/components/template/pagination/__tests__/usePagination.test.ts +138 -0
- package/components/template/table/__tests__/useTable.test.ts +139 -0
- package/components/template/table/useTable.ts +2 -4
- package/components/types/hook.d.ts +11 -0
- package/components/types/props.d.ts +5 -0
- package/compositions/common/__tests__/useDebounceFn.test.ts +62 -0
- package/compositions/common/__tests__/useEventListener.test.ts +71 -0
- package/compositions/common/__tests__/usePopover.test.ts +38 -0
- package/compositions/common/__tests__/useTeleport.test.ts +25 -0
- package/compositions/common/useComponentSize.ts +17 -0
- package/compositions/common/useEventListener.ts +3 -3
- package/compositions/common/useResizeObserver.ts +6 -2
- package/compositions/input/__tests__/useBooleanInput.test.ts +73 -0
- package/compositions/modal/__tests__/useModal.test.ts +92 -0
- package/compositions/modal/useModal.ts +2 -2
- package/compositions/popper/useClickAway.ts +4 -4
- package/compositions/utils/__tests__/filters.test.ts +136 -0
- package/compositions/virtualList/__tests__/useHeightCache.test.ts +97 -0
- package/dist/components/base/affix/useAffix.d.ts +2 -1
- package/dist/components/base/anchor/useAnchor.d.ts +2 -1
- package/dist/components/base/autoComplete/useAutoComplete.d.ts +2 -1
- package/dist/components/base/carousel/useCarousel.d.ts +2 -1
- package/dist/components/base/cascader/useCascader.d.ts +2 -1
- package/dist/components/base/checkbox/useCheckbox.d.ts +2 -1
- package/dist/components/base/collapse/useCollapse.d.ts +2 -1
- package/dist/components/base/datePicker/__tests__/useDatePicker.test.d.ts +1 -0
- package/dist/components/base/dropdown/useDropdown.d.ts +2 -1
- package/dist/components/base/image/__tests__/useImage.test.d.ts +1 -0
- package/dist/components/base/input/useInput.d.ts +4 -1
- package/dist/components/base/inputNumber/__tests__/useInputNumber.test.d.ts +1 -0
- package/dist/components/base/inputNumber/useInputNumber.d.ts +2 -1
- package/dist/components/base/popover/usePopover.d.ts +2 -1
- package/dist/components/base/rate/useRate.d.ts +2 -1
- package/dist/components/base/select/useSelect.d.ts +2 -1
- package/dist/components/base/slider/useSlider.d.ts +2 -1
- package/dist/components/base/steps/__tests__/useSteps.test.d.ts +1 -0
- package/dist/components/base/switch/useSwitch.d.ts +2 -1
- package/dist/components/base/tabs/useTabs.d.ts +2 -1
- package/dist/components/base/timePicker/__tests__/useTimePicker.test.d.ts +1 -0
- package/dist/components/base/transfer/useTransfer.d.ts +2 -1
- package/dist/components/base/tree/__tests__/tree.test.d.ts +1 -0
- package/dist/components/message/notification/__tests__/useNotification.test.d.ts +1 -0
- package/dist/components/message/popover/usePopover.d.ts +1 -1
- package/dist/components/other/darkMode/useDarkMode.d.ts +2 -3
- package/dist/components/template/menu/__tests__/useMenu.test.d.ts +1 -0
- package/dist/components/template/pagination/__tests__/usePagination.test.d.ts +1 -0
- package/dist/components/template/table/__tests__/useTable.test.d.ts +1 -0
- package/dist/compositions/common/__tests__/useDebounceFn.test.d.ts +1 -0
- package/dist/compositions/common/__tests__/useEventListener.test.d.ts +1 -0
- package/dist/compositions/common/__tests__/usePopover.test.d.ts +1 -0
- package/dist/compositions/common/__tests__/useTeleport.test.d.ts +1 -0
- package/dist/compositions/common/useComponentSize.d.ts +6 -0
- package/dist/compositions/common/useEventListener.d.ts +2 -2
- package/dist/compositions/input/__tests__/useBooleanInput.test.d.ts +1 -0
- package/dist/compositions/modal/__tests__/useModal.test.d.ts +1 -0
- package/dist/compositions/modal/useModal.d.ts +2 -1
- package/dist/compositions/popper/useClickAway.d.ts +3 -3
- package/dist/compositions/utils/__tests__/filters.test.d.ts +1 -0
- package/dist/compositions/virtualList/__tests__/useHeightCache.test.d.ts +1 -0
- package/dist/core.js +153 -23
- package/dist/tools/__tests__/empty.test.d.ts +1 -0
- package/dist/tools/empty.d.ts +2 -2
- package/dist/tools/types.d.ts +1 -1
- package/dist/vitest.config.d.ts +10 -0
- package/package.json +6 -2
- package/tools/__tests__/empty.test.ts +72 -0
- package/tools/empty.ts +2 -2
- package/tools/types.ts +1 -1
- package/vitest.config.ts +17 -0
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
|
+
|
|
9
10
|
import { HTMLElementEvent } from '../../types/template';
|
|
11
|
+
import { type HookContext } from '../../types/hook';
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
export function useInput<
|
|
13
15
|
Props extends Record<string, any>,
|
|
14
|
-
>(props: Props, ctx:
|
|
16
|
+
>(props: Props, ctx: HookContext) {
|
|
15
17
|
|
|
16
18
|
const isInput = props.type !== 'textarea';
|
|
17
19
|
const inputType = isInput ? 'input' : 'textarea';
|
|
@@ -25,6 +27,7 @@ export function useInput<
|
|
|
25
27
|
disabled: props.disabled,
|
|
26
28
|
type: props.type,
|
|
27
29
|
readOnly: props.readonly,
|
|
30
|
+
maxLength: props.maxlength,
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
const onInput = (e: HTMLElementEvent<HTMLInputElement>)=>{
|
|
@@ -40,6 +43,12 @@ export function useInput<
|
|
|
40
43
|
ctx.emit('blur', e);
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
const onClear = () => {
|
|
47
|
+
ctx.emit('update:modelValue', '');
|
|
48
|
+
ctx.emit('input', '');
|
|
49
|
+
ctx.emit('clear');
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
return {
|
|
44
53
|
baseProps,
|
|
45
54
|
inputType,
|
|
@@ -47,7 +56,8 @@ export function useInput<
|
|
|
47
56
|
rowInfo,
|
|
48
57
|
onInput,
|
|
49
58
|
onFocus,
|
|
50
|
-
onBlur
|
|
59
|
+
onBlur,
|
|
60
|
+
onClear,
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description useInputNumber 归一 / emit 保真单测
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/4/24
|
|
5
|
+
* @version v1.0.0
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
8
|
+
import { nextTick, reactive } from 'vue';
|
|
9
|
+
import { useInputNumber } from '../useInputNumber';
|
|
10
|
+
import type { InputNumberProps } from '../props';
|
|
11
|
+
import type { HookContext } from '../../../types/hook';
|
|
12
|
+
|
|
13
|
+
type PropsShape = Required<InputNumberProps>;
|
|
14
|
+
|
|
15
|
+
const defaultProps: PropsShape = {
|
|
16
|
+
modelValue: null,
|
|
17
|
+
max: Infinity,
|
|
18
|
+
min: -Infinity,
|
|
19
|
+
step: 1,
|
|
20
|
+
precision: 0,
|
|
21
|
+
disabled: false,
|
|
22
|
+
readonly: false,
|
|
23
|
+
placeholder: '',
|
|
24
|
+
controls: true,
|
|
25
|
+
size: 'medium' as PropsShape['size'],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function setup(overrides: Partial<PropsShape> = {}) {
|
|
29
|
+
const props = reactive<PropsShape>({ ...defaultProps, ...overrides }) as PropsShape;
|
|
30
|
+
const emit = vi.fn();
|
|
31
|
+
const ctx: HookContext = { emit, slots: {} };
|
|
32
|
+
const hook = useInputNumber(props, ctx);
|
|
33
|
+
return { props, emit, ...hook };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const lastUpdate = (emit: ReturnType<typeof vi.fn>) =>
|
|
37
|
+
emit.mock.calls.filter((c: unknown[]) => c[0] === 'update:modelValue').at(-1)!;
|
|
38
|
+
|
|
39
|
+
describe('useInputNumber emit 归一', () => {
|
|
40
|
+
it('number 入 → number 出', () => {
|
|
41
|
+
const { validate, emit } = setup({ modelValue: 1 });
|
|
42
|
+
validate(5);
|
|
43
|
+
const call = lastUpdate(emit);
|
|
44
|
+
expect(call[1]).toBe(5);
|
|
45
|
+
expect(typeof call[1]).toBe('number');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('string 入 → string 出', () => {
|
|
49
|
+
const { validate, emit } = setup({ modelValue: '1' });
|
|
50
|
+
validate('5');
|
|
51
|
+
const call = lastUpdate(emit);
|
|
52
|
+
expect(call[1]).toBe('5');
|
|
53
|
+
expect(typeof call[1]).toBe('string');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('null 入 → 用户输入整数后默认 number 出', () => {
|
|
57
|
+
const { validate, emit } = setup({ modelValue: null });
|
|
58
|
+
validate(3);
|
|
59
|
+
const call = lastUpdate(emit);
|
|
60
|
+
expect(call[1]).toBe(3);
|
|
61
|
+
expect(typeof call[1]).toBe('number');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('空字符串 currentValue → emit null(不是空串)', () => {
|
|
65
|
+
const { validate, emit } = setup({ modelValue: 5 });
|
|
66
|
+
validate(null);
|
|
67
|
+
const call = lastUpdate(emit);
|
|
68
|
+
expect(call[1]).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('中间态 "-" → emit null(未输完)', () => {
|
|
72
|
+
const { validate, emit } = setup({ modelValue: null });
|
|
73
|
+
validate('-');
|
|
74
|
+
const call = lastUpdate(emit);
|
|
75
|
+
expect(call[1]).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('输入 "0." → emit 0(末尾小数点归一)', () => {
|
|
79
|
+
const { validate, emit } = setup({ modelValue: null, precision: 2 });
|
|
80
|
+
validate('0.');
|
|
81
|
+
const call = lastUpdate(emit);
|
|
82
|
+
expect(call[1]).toBe(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('非法字符串 → emit 原样不变(validate 不触发 emit)', () => {
|
|
86
|
+
const { validate, emit } = setup({ modelValue: 5 });
|
|
87
|
+
emit.mockClear();
|
|
88
|
+
validate('abc' as unknown as number);
|
|
89
|
+
// 非法输入不触发 emit
|
|
90
|
+
expect(emit).not.toHaveBeenCalled();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('跨类型 watch:外部传 1(number)→ 不会重复 emit 成 string', async () => {
|
|
94
|
+
const { props, emit } = setup({ modelValue: 1 });
|
|
95
|
+
emit.mockClear();
|
|
96
|
+
// 模拟外部 bind 把内部 string '1' 回传 number 1:这在真实 v-model 里是 watch 触发的
|
|
97
|
+
props.modelValue = 1;
|
|
98
|
+
await nextTick();
|
|
99
|
+
// 内部 '1' 和外部 1 的 String 归一相等,不该触发 emit
|
|
100
|
+
expect(emit).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('blur 末尾小数点归一:内部 "1." → emit 1', () => {
|
|
104
|
+
const { handleInputChange, handleInputBlur, emit } = setup({ modelValue: null, precision: 2 });
|
|
105
|
+
handleInputChange({ target: { value: '1.' } } as never);
|
|
106
|
+
emit.mockClear();
|
|
107
|
+
handleInputBlur();
|
|
108
|
+
const call = lastUpdate(emit);
|
|
109
|
+
expect(call[1]).toBe(1);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('max 截断仍然保真类型', () => {
|
|
113
|
+
const { validate, emit } = setup({ modelValue: '10', max: 100 });
|
|
114
|
+
validate(150);
|
|
115
|
+
const call = lastUpdate(emit);
|
|
116
|
+
expect(call[1]).toBe('100');
|
|
117
|
+
expect(typeof call[1]).toBe('string');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('min 截断仍然保真类型', () => {
|
|
121
|
+
const { validate, emit } = setup({ modelValue: 10, min: 0 });
|
|
122
|
+
validate(-5);
|
|
123
|
+
const call = lastUpdate(emit);
|
|
124
|
+
expect(call[1]).toBe(0);
|
|
125
|
+
expect(typeof call[1]).toBe('number');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('useInputNumber 输入流程', () => {
|
|
130
|
+
it('合法数字输入触发 emit', () => {
|
|
131
|
+
const { handleInputChange, emit } = setup({ modelValue: 0 });
|
|
132
|
+
emit.mockClear();
|
|
133
|
+
handleInputChange({ target: { value: '88' } } as never);
|
|
134
|
+
const call = lastUpdate(emit);
|
|
135
|
+
expect(call[1]).toBe(88);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('increment 按 step 递增', () => {
|
|
139
|
+
const { increment, emit } = setup({ modelValue: 5, step: 3 });
|
|
140
|
+
emit.mockClear();
|
|
141
|
+
increment();
|
|
142
|
+
const call = lastUpdate(emit);
|
|
143
|
+
expect(call[1]).toBe(8);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('decrement 按 step 递减', () => {
|
|
147
|
+
const { decrement, emit } = setup({ modelValue: 5, step: 2 });
|
|
148
|
+
emit.mockClear();
|
|
149
|
+
decrement();
|
|
150
|
+
const call = lastUpdate(emit);
|
|
151
|
+
expect(call[1]).toBe(3);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
|
-
import { MCOPO } from '../../types/props';
|
|
9
|
+
import { MCOPO, MPropType } from '../../types/props';
|
|
10
10
|
import { InputNumberProps } from './props';
|
|
11
11
|
|
|
12
12
|
export const props: MCOPO<InputNumberProps> = {
|
|
@@ -19,4 +19,5 @@ export const props: MCOPO<InputNumberProps> = {
|
|
|
19
19
|
step: { type: Number, default: 1 },
|
|
20
20
|
precision: { type: Number, default: 0 },
|
|
21
21
|
controls: { type: Boolean, default: true },
|
|
22
|
+
size: { type: String as unknown as MPropType<NonNullable<InputNumberProps['size']>>, default: undefined },
|
|
22
23
|
};
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { KineSize } from '../../types/props';
|
|
16
|
+
|
|
15
17
|
export declare type InputNumberProps = {
|
|
16
18
|
/**
|
|
17
19
|
* @description input-number modelValue
|
|
@@ -66,5 +68,11 @@ export declare type InputNumberProps = {
|
|
|
66
68
|
* @type boolean
|
|
67
69
|
* @default true
|
|
68
70
|
*/
|
|
69
|
-
controls?: boolean
|
|
71
|
+
controls?: boolean,
|
|
72
|
+
/**
|
|
73
|
+
* @description 尺寸
|
|
74
|
+
* @type KineSize
|
|
75
|
+
* @default undefined
|
|
76
|
+
*/
|
|
77
|
+
size?: KineSize
|
|
70
78
|
};
|
|
@@ -2,28 +2,66 @@
|
|
|
2
2
|
* @description inputNumber composable
|
|
3
3
|
* @author 阿怪
|
|
4
4
|
* @date 2026/2/25 00:00
|
|
5
|
-
* @version v1.
|
|
5
|
+
* @version v1.2.0
|
|
6
6
|
*
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*
|
|
9
|
+
* v1.2.0 changelog:
|
|
10
|
+
* - emit 类型保真:string 入 string 出,number 入 number 出;空值一律 emit null
|
|
11
|
+
* - 内部 currentValue 保留中间态('-' / '0.' / ''),仅在 emit 时归一
|
|
8
12
|
*/
|
|
9
13
|
import { ref, watch } from 'vue';
|
|
10
14
|
import { InputNumberProps } from './props';
|
|
11
15
|
import { HTMLElementEvent } from '../../types/template';
|
|
16
|
+
import { type HookContext } from '../../types/hook';
|
|
12
17
|
|
|
13
18
|
export type InputNumber = string | number | null;
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
/** 用户输入中间态:不构成可 emit 的数字,emit null */
|
|
21
|
+
const MID_STATES = new Set(['', '-', '.', '-.']);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 把任意内部值归一为 number | null。
|
|
25
|
+
* 规则:
|
|
26
|
+
* - null/undefined/中间态 → null
|
|
27
|
+
* - 末尾小数点去掉('1.' → '1')
|
|
28
|
+
* - 非法 NaN → null
|
|
29
|
+
* - 其他 → Number(v)
|
|
30
|
+
*/
|
|
31
|
+
const normalize = (v: string | number | null | undefined): number | null => {
|
|
32
|
+
if (v === null || v === undefined) return null;
|
|
33
|
+
const s = String(v);
|
|
34
|
+
if (MID_STATES.has(s)) return null;
|
|
35
|
+
const cleaned = s.endsWith('.') ? s.slice(0, -1) : s;
|
|
36
|
+
const n = Number(cleaned);
|
|
37
|
+
return Number.isNaN(n) ? null : n;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export function useInputNumber(props: Required<InputNumberProps>, ctx: HookContext) {
|
|
16
41
|
const currentValue = ref<string | number>(props.modelValue ?? '');
|
|
17
42
|
|
|
43
|
+
/**
|
|
44
|
+
* 按外部 modelValue 的当前 typeof 决定出参类型,保真 v-model 声明。
|
|
45
|
+
* - null/undefined → null
|
|
46
|
+
* - 外部当前是 string → emit String(n)
|
|
47
|
+
* - 否则 → emit n(默认 number)
|
|
48
|
+
*/
|
|
49
|
+
const toEmitValue = (n: number | null): number | string | null => {
|
|
50
|
+
if (n === null) return null;
|
|
51
|
+
return typeof props.modelValue === 'string' ? String(n) : n;
|
|
52
|
+
};
|
|
53
|
+
|
|
18
54
|
const updateInput = (oldVal: InputNumber) => {
|
|
19
|
-
|
|
20
|
-
ctx.emit('
|
|
55
|
+
const out = toEmitValue(normalize(currentValue.value));
|
|
56
|
+
ctx.emit('update:modelValue', out);
|
|
57
|
+
ctx.emit('change', out, oldVal);
|
|
21
58
|
};
|
|
22
59
|
|
|
23
60
|
const setCurrentValue = (newVal: string | number, e?: HTMLElementEvent<HTMLInputElement>) => {
|
|
24
61
|
const oldVal = currentValue.value;
|
|
25
62
|
const { min, max, precision } = props;
|
|
26
|
-
|
|
63
|
+
// 字符串归一比较:'1' 和 1 视为同值,避免跨类型重复 emit;'-' vs '-' 也命中
|
|
64
|
+
if (String(oldVal) === String(newVal)) {
|
|
27
65
|
return;
|
|
28
66
|
} else if (+newVal >= +max) {
|
|
29
67
|
// 超过最大值,截断到 max
|
|
@@ -75,18 +113,22 @@ export function useInputNumber(props: Required<InputNumberProps>, ctx: any) {
|
|
|
75
113
|
};
|
|
76
114
|
|
|
77
115
|
const handleInputBlur = () => {
|
|
116
|
+
const oldVal = currentValue.value;
|
|
78
117
|
// blur 时清理末尾的负号
|
|
79
118
|
if (currentValue.value === '-') {
|
|
80
119
|
currentValue.value = '';
|
|
81
120
|
} else if (currentValue.value === '-0') {
|
|
82
121
|
currentValue.value = 0;
|
|
122
|
+
} else {
|
|
123
|
+
const str = String(currentValue.value);
|
|
124
|
+
// 清理末尾小数点
|
|
125
|
+
if (str.indexOf('.') === str.length - 1 && str.length > 0) {
|
|
126
|
+
currentValue.value = str.replace(/\.$/g, '');
|
|
127
|
+
}
|
|
83
128
|
}
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
currentValue.value = str.indexOf('.') === str.length - 1
|
|
88
|
-
? str.replace(/\.$/g, '')
|
|
89
|
-
: currentValue.value;
|
|
129
|
+
// blur 后内部归一到数字(避免 '1' 和 1 并存)
|
|
130
|
+
const n = normalize(currentValue.value);
|
|
131
|
+
currentValue.value = n === null ? '' : n;
|
|
90
132
|
updateInput(oldVal);
|
|
91
133
|
};
|
|
92
134
|
|
|
@@ -11,7 +11,7 @@ import { LoadingProps } from './props';
|
|
|
11
11
|
|
|
12
12
|
export const props: MCOPO<LoadingProps> = {
|
|
13
13
|
modelValue: { type: Boolean, default: false },
|
|
14
|
-
size: { type: String as unknown as MPropType<NonNullable<LoadingProps['size']>>, default:
|
|
14
|
+
size: { type: String as unknown as MPropType<NonNullable<LoadingProps['size']>>, default: undefined },
|
|
15
15
|
mask: { type: Boolean, default: false },
|
|
16
16
|
text: { type: String, default: undefined },
|
|
17
17
|
};
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { KineSize } from '../../types/props';
|
|
15
|
+
|
|
14
16
|
export declare type LoadingProps = {
|
|
15
17
|
/**
|
|
16
18
|
* @description 是否显示 loading
|
|
@@ -23,7 +25,7 @@ export declare type LoadingProps = {
|
|
|
23
25
|
* @type 'large' | 'medium' | 'small'
|
|
24
26
|
* @default 'medium'
|
|
25
27
|
*/
|
|
26
|
-
size?:
|
|
28
|
+
size?: KineSize;
|
|
27
29
|
/**
|
|
28
30
|
* @description 是否显示遮罩层(覆盖父元素)
|
|
29
31
|
* @type boolean
|
|
@@ -10,6 +10,7 @@ import { Placement, PopperConfig, PositionStyle, usePopper } from '../../../comp
|
|
|
10
10
|
import useClickAway from '../../../compositions/popper/useClickAway';
|
|
11
11
|
import { PopoverProps } from './props';
|
|
12
12
|
import { onBeforeUnmount, onMounted, ref, type Ref, shallowRef } from 'vue';
|
|
13
|
+
import { type HookContext } from '../../types/hook';
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
export type IPopper = ReturnType<typeof usePopper>;
|
|
@@ -103,7 +104,7 @@ export class PopoverImpl {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
|
|
106
|
-
export function usePopover(props: Required<PopoverProps>, ctx:
|
|
107
|
+
export function usePopover(props: Required<PopoverProps>, ctx: HookContext) {
|
|
107
108
|
const style = ref();
|
|
108
109
|
const arrowStyle = ref();
|
|
109
110
|
const placement = ref<Placement>(props.placement);
|
|
@@ -175,8 +176,8 @@ export function usePopover(props: Required<PopoverProps>, ctx: any) {
|
|
|
175
176
|
|
|
176
177
|
onBeforeUnmount(() => {
|
|
177
178
|
if (clickAwayInstance) {
|
|
178
|
-
const {
|
|
179
|
-
|
|
179
|
+
const { onBeforeUnmount } = clickAwayInstance;
|
|
180
|
+
onBeforeUnmount();
|
|
180
181
|
}
|
|
181
182
|
instance?.destroy();
|
|
182
183
|
});
|
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
9
|
import { MCOPO, MPropType } from '../../types/props';
|
|
10
|
-
import { ProgressProps, ProgressStatus } from './props';
|
|
10
|
+
import { ProgressProps, ProgressStatus, ProgressType } from './props';
|
|
11
11
|
|
|
12
12
|
export const props: MCOPO<ProgressProps> = {
|
|
13
|
+
type: { type: String as unknown as MPropType<ProgressType>, default: 'line' },
|
|
13
14
|
value: { type: Number, default: 0 },
|
|
14
15
|
max: { type: Number, default: 100 },
|
|
15
16
|
showInfo: { type: Boolean, default: true },
|
|
16
17
|
status: { type: String as unknown as MPropType<ProgressStatus>, default: 'default' },
|
|
17
18
|
strokeWidth: { type: Number, default: 4 },
|
|
19
|
+
width: { type: Number, default: 80 },
|
|
18
20
|
};
|
|
@@ -14,7 +14,15 @@
|
|
|
14
14
|
|
|
15
15
|
export declare type ProgressStatus = 'default' | 'success' | 'warning' | 'danger';
|
|
16
16
|
|
|
17
|
+
export declare type ProgressType = 'line' | 'circle';
|
|
18
|
+
|
|
17
19
|
export declare type ProgressProps = {
|
|
20
|
+
/**
|
|
21
|
+
* @description progress type. 进度条类型:line 线性 | circle 环形
|
|
22
|
+
* @type ProgressType
|
|
23
|
+
* @default 'line'
|
|
24
|
+
*/
|
|
25
|
+
type?: ProgressType,
|
|
18
26
|
/**
|
|
19
27
|
* @description progress value
|
|
20
28
|
* 进度条的值(0 ~ max)
|
|
@@ -50,4 +58,11 @@ export declare type ProgressProps = {
|
|
|
50
58
|
* @default 4
|
|
51
59
|
*/
|
|
52
60
|
strokeWidth?: number,
|
|
61
|
+
/**
|
|
62
|
+
* @description circle diameter in px (only for type='circle')
|
|
63
|
+
* 环形进度条直径(px),仅 type='circle' 时生效
|
|
64
|
+
* @type number
|
|
65
|
+
* @default 80
|
|
66
|
+
*/
|
|
67
|
+
width?: number,
|
|
53
68
|
};
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
9
|
import { ref } from 'vue';
|
|
10
|
+
import { type HookContext } from '../../types/hook';
|
|
10
11
|
import { RateProps } from './props';
|
|
11
12
|
|
|
12
|
-
export function useRate(props: Required<RateProps>, emit:
|
|
13
|
+
export function useRate(props: Required<RateProps>, emit: HookContext['emit']) {
|
|
13
14
|
// 悬停预览值,null 表示未悬停
|
|
14
15
|
const hoverValue = ref<number | null>(null);
|
|
15
16
|
|
|
@@ -28,4 +28,7 @@ export const props: MCOPO<SelectProps> = {
|
|
|
28
28
|
optionsH: { type: [Number, String], default: undefined },
|
|
29
29
|
needFetch: { type: Boolean, default: false },
|
|
30
30
|
fetch: { type: Function as MPropType<() => Promise<void>>, default: undefined },
|
|
31
|
+
clearable: { type: Boolean, default: false },
|
|
32
|
+
size: { type: String as unknown as MPropType<NonNullable<SelectProps['size']>>, default: undefined },
|
|
33
|
+
loading: { type: Boolean, default: false },
|
|
31
34
|
};
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* v1.0.2 新增 filterable prop,支持内置搜索过滤 阿怪
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { KineSize } from '../../types/props';
|
|
19
|
+
|
|
18
20
|
export declare type SelectProps = {
|
|
19
21
|
/**
|
|
20
22
|
* @description select value
|
|
@@ -128,5 +130,23 @@ export declare type SelectProps = {
|
|
|
128
130
|
* @type Function
|
|
129
131
|
* @default undefined
|
|
130
132
|
*/
|
|
131
|
-
fetch?: () => Promise<void
|
|
133
|
+
fetch?: () => Promise<void>,
|
|
134
|
+
/**
|
|
135
|
+
* @description whether to show clear button. 是否可清空
|
|
136
|
+
* @type boolean
|
|
137
|
+
* @default false
|
|
138
|
+
*/
|
|
139
|
+
clearable?: boolean,
|
|
140
|
+
/**
|
|
141
|
+
* @description select size. 选择框尺寸
|
|
142
|
+
* @type KineSize
|
|
143
|
+
* @default undefined
|
|
144
|
+
*/
|
|
145
|
+
size?: KineSize,
|
|
146
|
+
/**
|
|
147
|
+
* @description whether to show loading indicator. 是否显示加载中状态
|
|
148
|
+
* @type boolean
|
|
149
|
+
* @default false
|
|
150
|
+
*/
|
|
151
|
+
loading?: boolean,
|
|
132
152
|
};
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { computed, ref, watch, toRef, onBeforeUnmount } from 'vue';
|
|
15
15
|
import { SelectProps } from './props';
|
|
16
16
|
import { useSelectTools } from './useSelectTools';
|
|
17
|
+
import { type HookContext } from '../../types/hook';
|
|
17
18
|
|
|
18
19
|
export interface SelectOptionItem {
|
|
19
20
|
/** 原始值 */
|
|
@@ -24,7 +25,7 @@ export interface SelectOptionItem {
|
|
|
24
25
|
isSelected: boolean;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
export function useSelect(props: SelectProps, ctx:
|
|
28
|
+
export function useSelect(props: SelectProps, ctx: HookContext) {
|
|
28
29
|
const tools = useSelectTools(props);
|
|
29
30
|
const optionsRef = toRef(() => props.options ?? []);
|
|
30
31
|
const modelValueRef = toRef(() => props.modelValue);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
|
-
import { MCOPO } from '../../types/props';
|
|
9
|
+
import { MCOPO, MPropType } from '../../types/props';
|
|
10
10
|
import { SliderProps } from './props';
|
|
11
11
|
|
|
12
12
|
export const props: MCOPO<SliderProps> = {
|
|
@@ -17,4 +17,6 @@ export const props: MCOPO<SliderProps> = {
|
|
|
17
17
|
disabled: { type: Boolean, default: false },
|
|
18
18
|
showInfo: { type: Boolean, default: false },
|
|
19
19
|
showStops: { type: Boolean, default: false },
|
|
20
|
+
readonly: { type: Boolean, default: false },
|
|
21
|
+
size: { type: String as unknown as MPropType<NonNullable<SliderProps['size']>>, default: undefined },
|
|
20
22
|
};
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { KineSize } from '../../types/props';
|
|
16
|
+
|
|
15
17
|
export declare type SliderProps = {
|
|
16
18
|
/**
|
|
17
19
|
* @description slider value
|
|
@@ -62,4 +64,16 @@ export declare type SliderProps = {
|
|
|
62
64
|
* @default false
|
|
63
65
|
*/
|
|
64
66
|
showStops?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* @description slider readonly. 是否只读
|
|
69
|
+
* @type boolean
|
|
70
|
+
* @default false
|
|
71
|
+
*/
|
|
72
|
+
readonly?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* @description 尺寸
|
|
75
|
+
* @type KineSize
|
|
76
|
+
* @default undefined
|
|
77
|
+
*/
|
|
78
|
+
size?: KineSize;
|
|
65
79
|
};
|
|
@@ -10,8 +10,9 @@ import { onMounted, ref } from 'vue';
|
|
|
10
10
|
import { SliderProps } from './props';
|
|
11
11
|
import { useElementSize } from '../../../compositions/common/useElementSize.ts';
|
|
12
12
|
import useDrag, { DragOption, DragPosition, InteractEvent } from '../../../compositions/common/useDrag.ts';
|
|
13
|
+
import { type HookContext } from '../../types/hook';
|
|
13
14
|
|
|
14
|
-
export function useSlider(props: Required<SliderProps>, ctx:
|
|
15
|
+
export function useSlider(props: Required<SliderProps>, ctx: HookContext) {
|
|
15
16
|
const sliderRef = ref<HTMLElement | null>(null);
|
|
16
17
|
const sliderSize = useElementSize(sliderRef);
|
|
17
18
|
|
|
@@ -25,7 +26,10 @@ export function useSlider(props: Required<SliderProps>, ctx: any) {
|
|
|
25
26
|
const sub = props.max - props.min;
|
|
26
27
|
|
|
27
28
|
// 处理拖拽移动,计算并约束位置,更新百分比
|
|
28
|
-
const movePositionHandler = (event: InteractEvent, position: DragPosition) => {
|
|
29
|
+
const movePositionHandler = (event: InteractEvent, position: DragPosition): DragPosition => {
|
|
30
|
+
if (props.readonly) {
|
|
31
|
+
return position;
|
|
32
|
+
}
|
|
29
33
|
const totalW = sliderSize.w.value - btnW;
|
|
30
34
|
|
|
31
35
|
let positionX = position.x + event.dx;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description resolveStepStatus 测试
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/3/23
|
|
5
|
+
* @version v1.0.0
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, it } from 'vitest';
|
|
10
|
+
import { resolveStepStatus, type StepsContext } from '../useSteps';
|
|
11
|
+
|
|
12
|
+
const createCtx = (active: number): StepsContext => ({
|
|
13
|
+
active,
|
|
14
|
+
finishStatus: 'finish',
|
|
15
|
+
processStatus: 'process',
|
|
16
|
+
total: 4,
|
|
17
|
+
direction: 'horizontal',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('resolveStepStatus', () => {
|
|
21
|
+
it('显式状态优先', () => {
|
|
22
|
+
expect(resolveStepStatus(0, 'error', createCtx(2))).toBe('error');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('index < active 返回 finishStatus', () => {
|
|
26
|
+
expect(resolveStepStatus(0, undefined, createCtx(2))).toBe('finish');
|
|
27
|
+
expect(resolveStepStatus(1, undefined, createCtx(2))).toBe('finish');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('index === active 返回 processStatus', () => {
|
|
31
|
+
expect(resolveStepStatus(2, undefined, createCtx(2))).toBe('process');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('index > active 返回 wait', () => {
|
|
35
|
+
expect(resolveStepStatus(3, undefined, createCtx(2))).toBe('wait');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('active=0 时第一步为 process', () => {
|
|
39
|
+
expect(resolveStepStatus(0, undefined, createCtx(0))).toBe('process');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('自定义 finishStatus=success', () => {
|
|
43
|
+
const ctx = { ...createCtx(2), finishStatus: 'success' as const };
|
|
44
|
+
expect(resolveStepStatus(0, undefined, ctx)).toBe('success');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { SwitchProps } from './props';
|
|
10
10
|
import { computed, ref } from 'vue';
|
|
11
11
|
import { isEmpty } from '../../../tools';
|
|
12
|
+
import { type HookContext } from '../../types/hook';
|
|
12
13
|
|
|
13
14
|
export const switchIsBoolean = (value: SwitchProps['modelValue']) => {
|
|
14
15
|
return typeof value === 'boolean';
|
|
@@ -21,7 +22,7 @@ export const getIsActive = (value: SwitchProps['modelValue'], activeValue: Switc
|
|
|
21
22
|
|
|
22
23
|
export function useSwitch<
|
|
23
24
|
Props extends Record<string, any>,
|
|
24
|
-
>(props: Props, ctx:
|
|
25
|
+
>(props: Props, ctx: HookContext) {
|
|
25
26
|
const activeValue = ref(props.activeValue);
|
|
26
27
|
const inactiveValue = ref(props.inactiveValue);
|
|
27
28
|
const { slots, emit } = ctx;
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { computed } from 'vue';
|
|
10
10
|
import { TabsProps } from './props';
|
|
11
|
+
import { type HookContext } from '../../types/hook';
|
|
11
12
|
|
|
12
13
|
export function useTabs<
|
|
13
14
|
Props extends Record<string, unknown>,
|
|
14
|
-
>(props: Props, ctx:
|
|
15
|
+
>(props: Props, ctx: HookContext) {
|
|
15
16
|
const { emit } = ctx;
|
|
16
17
|
|
|
17
18
|
/** 当前激活的标签名 */
|
|
@@ -11,7 +11,7 @@ import { TagProps } from './props';
|
|
|
11
11
|
|
|
12
12
|
export const props: MCOPO<TagProps> = {
|
|
13
13
|
type: { type: String as MPropType<'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'>, default: 'default' },
|
|
14
|
-
size: { type: String as MPropType<'large' | 'medium' | 'small'>, default:
|
|
14
|
+
size: { type: String as MPropType<'large' | 'medium' | 'small'>, default: undefined },
|
|
15
15
|
closable: { type: Boolean, default: false },
|
|
16
16
|
disabled: { type: Boolean, default: false },
|
|
17
17
|
};
|