@fiscozen/input 0.1.7 → 0.1.9
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/dist/index.d.ts +1 -0
- package/dist/input.js +5234 -0
- package/dist/input.umd.cjs +556 -0
- package/dist/src/FzCurrencyInput.vue.d.ts +124 -0
- package/dist/src/FzInput.vue.d.ts +131 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/types.d.ts +83 -0
- package/dist/src/useInputStyle.d.ts +12 -0
- package/package.json +3 -3
- package/src/FzCurrencyInput.vue +96 -4
- package/src/FzInput.vue +24 -19
- package/src/__tests__/FzCurrencyInput.spec.ts +89 -0
- package/src/types.ts +12 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
declare const _default: import('vue').DefineComponent<{
|
|
2
|
+
amount: import('vue').PropType<any>;
|
|
3
|
+
error: {
|
|
4
|
+
type: import('vue').PropType<boolean>;
|
|
5
|
+
};
|
|
6
|
+
name: {
|
|
7
|
+
type: import('vue').PropType<string>;
|
|
8
|
+
};
|
|
9
|
+
size: {
|
|
10
|
+
type: import('vue').PropType<"sm" | "md" | "lg">;
|
|
11
|
+
};
|
|
12
|
+
required: {
|
|
13
|
+
type: import('vue').PropType<boolean>;
|
|
14
|
+
};
|
|
15
|
+
label: {
|
|
16
|
+
type: import('vue').PropType<string>;
|
|
17
|
+
required: true;
|
|
18
|
+
};
|
|
19
|
+
pattern: {
|
|
20
|
+
type: import('vue').PropType<string>;
|
|
21
|
+
};
|
|
22
|
+
placeholder: {
|
|
23
|
+
type: import('vue').PropType<string>;
|
|
24
|
+
};
|
|
25
|
+
disabled: {
|
|
26
|
+
type: import('vue').PropType<boolean>;
|
|
27
|
+
};
|
|
28
|
+
leftIcon: {
|
|
29
|
+
type: import('vue').PropType<string>;
|
|
30
|
+
};
|
|
31
|
+
leftIconVariant: {
|
|
32
|
+
type: import('vue').PropType<import('@fiscozen/icons/src/types').IconVariant>;
|
|
33
|
+
};
|
|
34
|
+
rightIcon: {
|
|
35
|
+
type: import('vue').PropType<string>;
|
|
36
|
+
};
|
|
37
|
+
rightIconVariant: {
|
|
38
|
+
type: import('vue').PropType<import('@fiscozen/icons/src/types').IconVariant>;
|
|
39
|
+
};
|
|
40
|
+
valid: {
|
|
41
|
+
type: import('vue').PropType<boolean>;
|
|
42
|
+
};
|
|
43
|
+
readonly: {
|
|
44
|
+
type: import('vue').PropType<boolean>;
|
|
45
|
+
};
|
|
46
|
+
nullOnEmpty: {
|
|
47
|
+
type: import('vue').PropType<boolean>;
|
|
48
|
+
};
|
|
49
|
+
minimumFractionDigits: {
|
|
50
|
+
type: import('vue').PropType<number>;
|
|
51
|
+
default: number;
|
|
52
|
+
};
|
|
53
|
+
maximumFractionDigits: {
|
|
54
|
+
type: import('vue').PropType<number | null>;
|
|
55
|
+
default: number;
|
|
56
|
+
};
|
|
57
|
+
}, {
|
|
58
|
+
inputRef: import('vue').ComputedRef<HTMLInputElement | null | undefined>;
|
|
59
|
+
containerRef: import('vue').ComputedRef<HTMLElement | null | undefined>;
|
|
60
|
+
}, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
61
|
+
"update:amount": (...args: any[]) => void;
|
|
62
|
+
}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
|
63
|
+
amount: import('vue').PropType<any>;
|
|
64
|
+
error: {
|
|
65
|
+
type: import('vue').PropType<boolean>;
|
|
66
|
+
};
|
|
67
|
+
name: {
|
|
68
|
+
type: import('vue').PropType<string>;
|
|
69
|
+
};
|
|
70
|
+
size: {
|
|
71
|
+
type: import('vue').PropType<"sm" | "md" | "lg">;
|
|
72
|
+
};
|
|
73
|
+
required: {
|
|
74
|
+
type: import('vue').PropType<boolean>;
|
|
75
|
+
};
|
|
76
|
+
label: {
|
|
77
|
+
type: import('vue').PropType<string>;
|
|
78
|
+
required: true;
|
|
79
|
+
};
|
|
80
|
+
pattern: {
|
|
81
|
+
type: import('vue').PropType<string>;
|
|
82
|
+
};
|
|
83
|
+
placeholder: {
|
|
84
|
+
type: import('vue').PropType<string>;
|
|
85
|
+
};
|
|
86
|
+
disabled: {
|
|
87
|
+
type: import('vue').PropType<boolean>;
|
|
88
|
+
};
|
|
89
|
+
leftIcon: {
|
|
90
|
+
type: import('vue').PropType<string>;
|
|
91
|
+
};
|
|
92
|
+
leftIconVariant: {
|
|
93
|
+
type: import('vue').PropType<import('@fiscozen/icons/src/types').IconVariant>;
|
|
94
|
+
};
|
|
95
|
+
rightIcon: {
|
|
96
|
+
type: import('vue').PropType<string>;
|
|
97
|
+
};
|
|
98
|
+
rightIconVariant: {
|
|
99
|
+
type: import('vue').PropType<import('@fiscozen/icons/src/types').IconVariant>;
|
|
100
|
+
};
|
|
101
|
+
valid: {
|
|
102
|
+
type: import('vue').PropType<boolean>;
|
|
103
|
+
};
|
|
104
|
+
readonly: {
|
|
105
|
+
type: import('vue').PropType<boolean>;
|
|
106
|
+
};
|
|
107
|
+
nullOnEmpty: {
|
|
108
|
+
type: import('vue').PropType<boolean>;
|
|
109
|
+
};
|
|
110
|
+
minimumFractionDigits: {
|
|
111
|
+
type: import('vue').PropType<number>;
|
|
112
|
+
default: number;
|
|
113
|
+
};
|
|
114
|
+
maximumFractionDigits: {
|
|
115
|
+
type: import('vue').PropType<number | null>;
|
|
116
|
+
default: number;
|
|
117
|
+
};
|
|
118
|
+
}>> & {
|
|
119
|
+
"onUpdate:amount"?: ((...args: any[]) => any) | undefined;
|
|
120
|
+
}, {
|
|
121
|
+
minimumFractionDigits: number;
|
|
122
|
+
maximumFractionDigits: number | null;
|
|
123
|
+
}, {}>;
|
|
124
|
+
export default _default;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
|
|
3
|
+
declare const _default: __VLS_WithTemplateSlots<import('vue').DefineComponent<{
|
|
4
|
+
modelValue: import('vue').PropType<any>;
|
|
5
|
+
error: {
|
|
6
|
+
type: import('vue').PropType<boolean>;
|
|
7
|
+
default: boolean;
|
|
8
|
+
};
|
|
9
|
+
name: {
|
|
10
|
+
type: import('vue').PropType<string>;
|
|
11
|
+
};
|
|
12
|
+
size: {
|
|
13
|
+
type: import('vue').PropType<"sm" | "md" | "lg">;
|
|
14
|
+
default: string;
|
|
15
|
+
};
|
|
16
|
+
type: {
|
|
17
|
+
type: import('vue').PropType<"number" | "text" | "password" | "email" | "tel" | "url">;
|
|
18
|
+
default: string;
|
|
19
|
+
};
|
|
20
|
+
required: {
|
|
21
|
+
type: import('vue').PropType<boolean>;
|
|
22
|
+
};
|
|
23
|
+
label: {
|
|
24
|
+
type: import('vue').PropType<string>;
|
|
25
|
+
required: true;
|
|
26
|
+
};
|
|
27
|
+
pattern: {
|
|
28
|
+
type: import('vue').PropType<string>;
|
|
29
|
+
};
|
|
30
|
+
placeholder: {
|
|
31
|
+
type: import('vue').PropType<string>;
|
|
32
|
+
};
|
|
33
|
+
disabled: {
|
|
34
|
+
type: import('vue').PropType<boolean>;
|
|
35
|
+
};
|
|
36
|
+
leftIcon: {
|
|
37
|
+
type: import('vue').PropType<string>;
|
|
38
|
+
};
|
|
39
|
+
leftIconVariant: {
|
|
40
|
+
type: import('vue').PropType<import('@fiscozen/icons/src/types').IconVariant>;
|
|
41
|
+
};
|
|
42
|
+
rightIcon: {
|
|
43
|
+
type: import('vue').PropType<string>;
|
|
44
|
+
};
|
|
45
|
+
rightIconVariant: {
|
|
46
|
+
type: import('vue').PropType<import('@fiscozen/icons/src/types').IconVariant>;
|
|
47
|
+
};
|
|
48
|
+
valid: {
|
|
49
|
+
type: import('vue').PropType<boolean>;
|
|
50
|
+
};
|
|
51
|
+
readonly: {
|
|
52
|
+
type: import('vue').PropType<boolean>;
|
|
53
|
+
};
|
|
54
|
+
}, {
|
|
55
|
+
inputRef: Ref<HTMLInputElement | null>;
|
|
56
|
+
containerRef: Ref<HTMLElement | null>;
|
|
57
|
+
}, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
58
|
+
focus: (...args: any[]) => void;
|
|
59
|
+
input: (...args: any[]) => void;
|
|
60
|
+
paste: (...args: any[]) => void;
|
|
61
|
+
}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
|
62
|
+
modelValue: import('vue').PropType<any>;
|
|
63
|
+
error: {
|
|
64
|
+
type: import('vue').PropType<boolean>;
|
|
65
|
+
default: boolean;
|
|
66
|
+
};
|
|
67
|
+
name: {
|
|
68
|
+
type: import('vue').PropType<string>;
|
|
69
|
+
};
|
|
70
|
+
size: {
|
|
71
|
+
type: import('vue').PropType<"sm" | "md" | "lg">;
|
|
72
|
+
default: string;
|
|
73
|
+
};
|
|
74
|
+
type: {
|
|
75
|
+
type: import('vue').PropType<"number" | "text" | "password" | "email" | "tel" | "url">;
|
|
76
|
+
default: string;
|
|
77
|
+
};
|
|
78
|
+
required: {
|
|
79
|
+
type: import('vue').PropType<boolean>;
|
|
80
|
+
};
|
|
81
|
+
label: {
|
|
82
|
+
type: import('vue').PropType<string>;
|
|
83
|
+
required: true;
|
|
84
|
+
};
|
|
85
|
+
pattern: {
|
|
86
|
+
type: import('vue').PropType<string>;
|
|
87
|
+
};
|
|
88
|
+
placeholder: {
|
|
89
|
+
type: import('vue').PropType<string>;
|
|
90
|
+
};
|
|
91
|
+
disabled: {
|
|
92
|
+
type: import('vue').PropType<boolean>;
|
|
93
|
+
};
|
|
94
|
+
leftIcon: {
|
|
95
|
+
type: import('vue').PropType<string>;
|
|
96
|
+
};
|
|
97
|
+
leftIconVariant: {
|
|
98
|
+
type: import('vue').PropType<import('@fiscozen/icons/src/types').IconVariant>;
|
|
99
|
+
};
|
|
100
|
+
rightIcon: {
|
|
101
|
+
type: import('vue').PropType<string>;
|
|
102
|
+
};
|
|
103
|
+
rightIconVariant: {
|
|
104
|
+
type: import('vue').PropType<import('@fiscozen/icons/src/types').IconVariant>;
|
|
105
|
+
};
|
|
106
|
+
valid: {
|
|
107
|
+
type: import('vue').PropType<boolean>;
|
|
108
|
+
};
|
|
109
|
+
readonly: {
|
|
110
|
+
type: import('vue').PropType<boolean>;
|
|
111
|
+
};
|
|
112
|
+
}>> & {
|
|
113
|
+
onPaste?: ((...args: any[]) => any) | undefined;
|
|
114
|
+
onFocus?: ((...args: any[]) => any) | undefined;
|
|
115
|
+
onInput?: ((...args: any[]) => any) | undefined;
|
|
116
|
+
}, {
|
|
117
|
+
error: boolean;
|
|
118
|
+
size: "sm" | "md" | "lg";
|
|
119
|
+
type: "number" | "text" | "password" | "email" | "tel" | "url";
|
|
120
|
+
}, {}>, {
|
|
121
|
+
"left-icon"?(_: {}): any;
|
|
122
|
+
"right-icon"?(_: {}): any;
|
|
123
|
+
errorMessage?(_: {}): any;
|
|
124
|
+
helpText?(_: {}): any;
|
|
125
|
+
}>;
|
|
126
|
+
export default _default;
|
|
127
|
+
type __VLS_WithTemplateSlots<T, S> = T & {
|
|
128
|
+
new (): {
|
|
129
|
+
$slots: S;
|
|
130
|
+
};
|
|
131
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { IconVariant } from '@fiscozen/icons';
|
|
2
|
+
|
|
3
|
+
type FzInputProps = {
|
|
4
|
+
/**
|
|
5
|
+
* The label displayed on top of the input
|
|
6
|
+
*/
|
|
7
|
+
label: string;
|
|
8
|
+
/**
|
|
9
|
+
* The size of the input
|
|
10
|
+
*/
|
|
11
|
+
size?: "sm" | "md" | "lg";
|
|
12
|
+
/**
|
|
13
|
+
* The placeholder displayed in the input
|
|
14
|
+
*/
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
/**
|
|
17
|
+
* If set to true, the input is required
|
|
18
|
+
*/
|
|
19
|
+
required?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* If set to true, the input is disabled
|
|
22
|
+
*/
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* If set to true, the input is in error state
|
|
26
|
+
*/
|
|
27
|
+
error?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Left icon name
|
|
30
|
+
*/
|
|
31
|
+
leftIcon?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Left icon variant
|
|
34
|
+
*/
|
|
35
|
+
leftIconVariant?: IconVariant;
|
|
36
|
+
/**
|
|
37
|
+
* Right icon name
|
|
38
|
+
*/
|
|
39
|
+
rightIcon?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Right icon variant
|
|
42
|
+
*/
|
|
43
|
+
rightIconVariant?: IconVariant;
|
|
44
|
+
/**
|
|
45
|
+
* The input type
|
|
46
|
+
*/
|
|
47
|
+
type?: "text" | "password" | "email" | "number" | "tel" | "url";
|
|
48
|
+
/**
|
|
49
|
+
* If set to true, the input is valid
|
|
50
|
+
*/
|
|
51
|
+
valid?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Pattern to validate the input
|
|
54
|
+
*/
|
|
55
|
+
pattern?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Defines the textarea key in a form
|
|
58
|
+
*/
|
|
59
|
+
name?: string;
|
|
60
|
+
/**
|
|
61
|
+
* native readonly input value
|
|
62
|
+
*/
|
|
63
|
+
readonly?: boolean;
|
|
64
|
+
};
|
|
65
|
+
interface FzCurrencyInputProps extends Omit<FzInputProps, "type" | "modelValue"> {
|
|
66
|
+
/**
|
|
67
|
+
* Is set to true, an empty string will be casted to null
|
|
68
|
+
*/
|
|
69
|
+
nullOnEmpty?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Minimum number of decimal places allowed, set null to allow arbitrary decimal values length
|
|
72
|
+
* note that limits from Intl.NumberFormat still apply
|
|
73
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#digit_options
|
|
74
|
+
*/
|
|
75
|
+
minimumFractionDigits?: number;
|
|
76
|
+
/**
|
|
77
|
+
* Maximum number of decimal places allowed, set null to allow arbitrary decimal values length
|
|
78
|
+
* note that limits from Intl.NumberFormat still apply
|
|
79
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#digit_options
|
|
80
|
+
*/
|
|
81
|
+
maximumFractionDigits?: number | null;
|
|
82
|
+
}
|
|
83
|
+
export { FzInputProps, FzCurrencyInputProps };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
import { FzInputProps } from './types';
|
|
3
|
+
|
|
4
|
+
export default function useInputStyle(props: FzInputProps, container: Ref<HTMLElement | null>): {
|
|
5
|
+
staticContainerClass: string;
|
|
6
|
+
computedContainerClass: import('vue').ComputedRef<string[]>;
|
|
7
|
+
computedLabelClass: import('vue').ComputedRef<string[]>;
|
|
8
|
+
staticInputClass: string;
|
|
9
|
+
computedHelpClass: import('vue').ComputedRef<string[]>;
|
|
10
|
+
computedErrorClass: import('vue').ComputedRef<string[]>;
|
|
11
|
+
containerWidth: import('vue').ComputedRef<string>;
|
|
12
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fiscozen/input",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Design System Input component",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"peerDependencies": {
|
|
10
10
|
"tailwindcss": "^3.4.1",
|
|
11
11
|
"vue": "^3.4.13",
|
|
12
|
-
"@fiscozen/
|
|
13
|
-
"@fiscozen/
|
|
12
|
+
"@fiscozen/composables": "^0.1.29",
|
|
13
|
+
"@fiscozen/icons": "^0.1.12"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@rushstack/eslint-patch": "^1.3.3",
|
package/src/FzCurrencyInput.vue
CHANGED
|
@@ -1,26 +1,118 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<FzInput
|
|
2
|
+
<FzInput
|
|
3
|
+
ref="fzInputRef"
|
|
4
|
+
v-bind="props"
|
|
5
|
+
:modelValue="fzInputModel"
|
|
6
|
+
type="text"
|
|
7
|
+
@paste="onPaste"
|
|
8
|
+
></FzInput>
|
|
3
9
|
</template>
|
|
4
10
|
|
|
5
11
|
<script setup lang="ts">
|
|
6
|
-
import { computed, onMounted, ref } from "vue";
|
|
12
|
+
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
|
7
13
|
import FzInput from "./FzInput.vue";
|
|
8
14
|
import { FzCurrencyInputProps } from "./types";
|
|
9
15
|
import { useCurrency } from "@fiscozen/composables";
|
|
10
16
|
|
|
11
17
|
const fzInputRef = ref<InstanceType<typeof FzInput>>();
|
|
18
|
+
const fzInputModel = ref();
|
|
12
19
|
const containerRef = computed(() => fzInputRef.value?.containerRef);
|
|
13
20
|
const inputRef = computed(() => fzInputRef.value?.inputRef);
|
|
14
|
-
const props = defineProps<FzCurrencyInputProps>()
|
|
15
|
-
|
|
21
|
+
const props = withDefaults(defineProps<FzCurrencyInputProps>(), {
|
|
22
|
+
minimumFractionDigits: 2,
|
|
23
|
+
maximumFractionDigits: 2,
|
|
24
|
+
});
|
|
25
|
+
const {
|
|
26
|
+
inputRef: currencyInputRef,
|
|
27
|
+
setValue,
|
|
28
|
+
emitAmount,
|
|
29
|
+
parse,
|
|
30
|
+
format,
|
|
31
|
+
} = useCurrency({
|
|
32
|
+
minimumFractionDigits: props.minimumFractionDigits,
|
|
33
|
+
maximumFractionDigits: props.maximumFractionDigits,
|
|
34
|
+
});
|
|
16
35
|
|
|
17
36
|
defineEmits(["update:amount"]);
|
|
18
37
|
|
|
38
|
+
const onPaste = (e: ClipboardEvent) => {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
|
|
41
|
+
if (props.readonly) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let rawPastedText;
|
|
46
|
+
if (e.clipboardData && e.clipboardData.getData) {
|
|
47
|
+
rawPastedText = e.clipboardData.getData("text/plain");
|
|
48
|
+
} else {
|
|
49
|
+
throw "invalid paste value";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Fix for firefox paste handling on `contenteditable` elements where `e.target` is the text node, not the element
|
|
53
|
+
let eventTarget;
|
|
54
|
+
if ((!e.target as any)?.tagName) {
|
|
55
|
+
eventTarget = (e as any).explicitOriginalTarget;
|
|
56
|
+
} else {
|
|
57
|
+
eventTarget = e.target;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let isNegative = rawPastedText.slice(0, 1) === "-";
|
|
61
|
+
const separatorRegex = /[,.]/g;
|
|
62
|
+
const separators: string[] = [...rawPastedText.matchAll(separatorRegex)].map(
|
|
63
|
+
(regexRes) => regexRes[0],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const uniqueSeparators = new Set(separators);
|
|
67
|
+
let decimalSeparator = ".";
|
|
68
|
+
let thousandSeparator = "";
|
|
69
|
+
let unknownSeparator;
|
|
70
|
+
|
|
71
|
+
// case 1: there are 2 different separators pasted, therefore we can assume the rightmost is the decimal separator
|
|
72
|
+
if (uniqueSeparators.size > 1) {
|
|
73
|
+
decimalSeparator = separators[separators.length - 1];
|
|
74
|
+
thousandSeparator = separators[0];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// case 2: there are multiple instances of the same separator, therefore it must be the thousand separator
|
|
78
|
+
if (uniqueSeparators.size === 1) {
|
|
79
|
+
if (separators.length > 1) {
|
|
80
|
+
thousandSeparator = separators[0];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// case 3: there is only one instance of a separator with < 3 digits afterwards (must be decimal separator)
|
|
84
|
+
unknownSeparator = separators[0];
|
|
85
|
+
const splitted = rawPastedText.split(unknownSeparator);
|
|
86
|
+
|
|
87
|
+
if (splitted[1].length !== 3) {
|
|
88
|
+
decimalSeparator = unknownSeparator;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// case 3: there is only one instance of a separator with 3 digits afterwards. Here we cannot make assumptions
|
|
93
|
+
// we will format based on settings
|
|
94
|
+
//@ts-ignore
|
|
95
|
+
let safeText = rawPastedText.replaceAll(thousandSeparator, "").trim();
|
|
96
|
+
safeText = safeText.replaceAll(decimalSeparator, ".").trim();
|
|
97
|
+
|
|
98
|
+
const safeNum = parse(safeText);
|
|
99
|
+
safeText = format(safeNum);
|
|
100
|
+
setValue(safeText);
|
|
101
|
+
emitAmount(safeNum);
|
|
102
|
+
};
|
|
103
|
+
|
|
19
104
|
onMounted(() => {
|
|
20
105
|
currencyInputRef.value = inputRef.value;
|
|
106
|
+
nextTick(() => {
|
|
107
|
+
fzInputModel.value = inputRef.value?.value;
|
|
108
|
+
});
|
|
21
109
|
});
|
|
22
110
|
const model = defineModel("amount");
|
|
23
111
|
|
|
112
|
+
watch(model, (newVal) => {
|
|
113
|
+
fzInputModel.value = newVal as string;
|
|
114
|
+
});
|
|
115
|
+
|
|
24
116
|
defineExpose({
|
|
25
117
|
inputRef,
|
|
26
118
|
containerRef,
|
package/src/FzInput.vue
CHANGED
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
ref="containerRef"
|
|
9
9
|
@click="inputRef?.focus()"
|
|
10
10
|
>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
<slot name="left-icon">
|
|
12
|
+
<FzIcon
|
|
13
|
+
v-if="leftIcon"
|
|
14
|
+
:name="leftIcon"
|
|
15
|
+
:size="size"
|
|
16
|
+
:variant="leftIconVariant"
|
|
17
|
+
/>
|
|
18
|
+
</slot>
|
|
17
19
|
<input
|
|
18
20
|
:type="type"
|
|
19
21
|
:required="required ? required : false"
|
|
@@ -27,19 +29,22 @@
|
|
|
27
29
|
:pattern="pattern"
|
|
28
30
|
:name
|
|
29
31
|
@focus="(e) => $emit('focus', e)"
|
|
32
|
+
@paste="(e) => $emit('paste', e)"
|
|
30
33
|
/>
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
<slot name="right-icon">
|
|
35
|
+
<FzIcon
|
|
36
|
+
v-if="valid"
|
|
37
|
+
name="check"
|
|
38
|
+
:size="size"
|
|
39
|
+
class="text-semantic-success"
|
|
40
|
+
/>
|
|
41
|
+
<FzIcon
|
|
42
|
+
v-if="rightIcon"
|
|
43
|
+
:name="rightIcon"
|
|
44
|
+
:size="size"
|
|
45
|
+
:variant="rightIconVariant"
|
|
46
|
+
/>
|
|
47
|
+
</slot>
|
|
43
48
|
</div>
|
|
44
49
|
<div
|
|
45
50
|
v-if="error && $slots.errorMessage"
|
|
@@ -92,7 +97,7 @@ const {
|
|
|
92
97
|
containerWidth,
|
|
93
98
|
} = useInputStyle(props, containerRef);
|
|
94
99
|
|
|
95
|
-
const emit = defineEmits(["input", "focus"]);
|
|
100
|
+
const emit = defineEmits(["input", "focus", "paste"]);
|
|
96
101
|
defineExpose({
|
|
97
102
|
inputRef,
|
|
98
103
|
containerRef,
|
|
@@ -37,4 +37,93 @@ describe.concurrent("FzCurrencyInput", () => {
|
|
|
37
37
|
await new Promise((resolve) => window.setTimeout(resolve, 100));
|
|
38
38
|
expect(inputElement.element.value).toBe("12,30");
|
|
39
39
|
});
|
|
40
|
+
|
|
41
|
+
it("should allow to set value at 0", async () => {
|
|
42
|
+
const wrapper = mount(FzCurrencyInput, {
|
|
43
|
+
props: {
|
|
44
|
+
label: "Label",
|
|
45
|
+
amount: 10,
|
|
46
|
+
"onUpdate:amount": (e) => wrapper.setProps({ amount: e }),
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const inputElement = wrapper.find("input");
|
|
51
|
+
await inputElement.trigger("blur");
|
|
52
|
+
await new Promise((resolve) => window.setTimeout(resolve, 100));
|
|
53
|
+
expect(inputElement.element.value).toBe("10,00");
|
|
54
|
+
wrapper.setProps({ amount: 0 });
|
|
55
|
+
await new Promise((resolve) => window.setTimeout(resolve, 100));
|
|
56
|
+
expect(inputElement.element.value).toBe("0,00");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should handle pasted values using the best possible euristic to parse and render it correctly", async () => {
|
|
60
|
+
const wrapper = mount(FzCurrencyInput, {
|
|
61
|
+
props: {
|
|
62
|
+
label: "Label",
|
|
63
|
+
"onUpdate:amount": (e) => wrapper.setProps({ amount: e }),
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const inputElement = wrapper.find("input");
|
|
68
|
+
// we need to mock the paste event
|
|
69
|
+
await inputElement.trigger("paste", {
|
|
70
|
+
clipboardData: {
|
|
71
|
+
getData() {
|
|
72
|
+
return "1.233.222,43";
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
await new Promise((resolve) => window.setTimeout(resolve, 100));
|
|
77
|
+
expect(inputElement.element.value).toBe("1233222,43");
|
|
78
|
+
|
|
79
|
+
await inputElement.trigger("paste", {
|
|
80
|
+
clipboardData: {
|
|
81
|
+
getData() {
|
|
82
|
+
return "1.23";
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
await new Promise((resolve) => window.setTimeout(resolve, 100));
|
|
87
|
+
expect(inputElement.element.value).toBe("1,23");
|
|
88
|
+
|
|
89
|
+
await inputElement.trigger("paste", {
|
|
90
|
+
clipboardData: {
|
|
91
|
+
getData() {
|
|
92
|
+
return "1,23";
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
await new Promise((resolve) => window.setTimeout(resolve, 100));
|
|
97
|
+
expect(inputElement.element.value).toBe("1,23");
|
|
98
|
+
|
|
99
|
+
await inputElement.trigger("paste", {
|
|
100
|
+
clipboardData: {
|
|
101
|
+
getData() {
|
|
102
|
+
return "1.232.111";
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
await new Promise((resolve) => window.setTimeout(resolve, 100));
|
|
107
|
+
expect(inputElement.element.value).toBe("1232111,00");
|
|
108
|
+
|
|
109
|
+
await inputElement.trigger("paste", {
|
|
110
|
+
clipboardData: {
|
|
111
|
+
getData() {
|
|
112
|
+
return "1.232";
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
await new Promise((resolve) => window.setTimeout(resolve, 100));
|
|
117
|
+
expect(inputElement.element.value).toBe("1,23");
|
|
118
|
+
|
|
119
|
+
await inputElement.trigger("paste", {
|
|
120
|
+
clipboardData: {
|
|
121
|
+
getData() {
|
|
122
|
+
return "1.232555";
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
await new Promise((resolve) => window.setTimeout(resolve, 100));
|
|
127
|
+
expect(inputElement.element.value).toBe("1,23");
|
|
128
|
+
});
|
|
40
129
|
});
|
package/src/types.ts
CHANGED
|
@@ -70,6 +70,18 @@ interface FzCurrencyInputProps
|
|
|
70
70
|
* Is set to true, an empty string will be casted to null
|
|
71
71
|
*/
|
|
72
72
|
nullOnEmpty?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Minimum number of decimal places allowed, set null to allow arbitrary decimal values length
|
|
75
|
+
* note that limits from Intl.NumberFormat still apply
|
|
76
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#digit_options
|
|
77
|
+
*/
|
|
78
|
+
minimumFractionDigits?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Maximum number of decimal places allowed, set null to allow arbitrary decimal values length
|
|
81
|
+
* note that limits from Intl.NumberFormat still apply
|
|
82
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#digit_options
|
|
83
|
+
*/
|
|
84
|
+
maximumFractionDigits?: number | null;
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
export { FzInputProps, FzCurrencyInputProps };
|