vueless 1.2.8-beta.0 → 1.2.8-beta.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.2.8-beta.0",
3
+ "version": "1.2.8-beta.2",
4
4
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
5
5
  "author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
6
6
  "homepage": "https://vueless.com",
@@ -25,7 +25,7 @@ const props = withDefaults(defineProps<Props>(), {
25
25
 
26
26
  const emit = defineEmits([
27
27
  /**
28
- * Triggers when the input value is changes.
28
+ * Triggers when the input value is changed.
29
29
  * @property {string} modelValue
30
30
  * @property {number} modelValue
31
31
  */
@@ -43,6 +43,7 @@ const emit = defineEmits([
43
43
 
44
44
  /**
45
45
  * Triggers when the input gains focus.
46
+ * @property {FocusEvent} event
46
47
  */
47
48
  "focus",
48
49
 
@@ -53,11 +54,12 @@ const emit = defineEmits([
53
54
 
54
55
  /**
55
56
  * Triggers when the input loses focus.
57
+ * @property {FocusEvent} event
56
58
  */
57
59
  "blur",
58
60
 
59
61
  /**
60
- * Triggers when the input value is changes.
62
+ * Triggers when the input value is changed.
61
63
  * @property {string} modelValue
62
64
  * @property {number} modelValue
63
65
  */
@@ -27,6 +27,18 @@ const emit = defineEmits([
27
27
  * @property {number} modelValue
28
28
  */
29
29
  "update:modelValue",
30
+
31
+ /**
32
+ * Triggers when the input gains focus.
33
+ * @property {FocusEvent} event
34
+ */
35
+ "focus",
36
+
37
+ /**
38
+ * Triggers when the input loses focus.
39
+ * @property {FocusEvent} event
40
+ */
41
+ "blur",
30
42
  ]);
31
43
 
32
44
  const inputComponentRef = useTemplateRef<InstanceType<typeof UInput>>("inputComponent");
@@ -120,9 +132,15 @@ function onMouseLeave() {
120
132
  clearIntervals();
121
133
  }
122
134
 
123
- function onBlur() {
135
+ function onFocus(event: FocusEvent) {
136
+ emit("focus", event);
137
+ }
138
+
139
+ function onBlur(event: FocusEvent) {
124
140
  if (Number(count.value) > props.max) count.value = props.max;
125
141
  if (Number(count.value) < props.min) count.value = props.min;
142
+
143
+ emit("blur", event);
126
144
  }
127
145
 
128
146
  function onInput() {
@@ -187,7 +205,13 @@ const {
187
205
  :size="size"
188
206
  :disabled="disabled"
189
207
  :readonly="readonly"
208
+ :min-fraction-digits="minFractionDigits"
209
+ :max-fraction-digits="maxFractionDigits"
210
+ :decimal-separator="decimalSeparator"
211
+ :thousands-separator="thousandsSeparator"
212
+ :prefix="prefix"
190
213
  v-bind="counterInputAttrs"
214
+ @focus="onFocus"
191
215
  @blur="onBlur"
192
216
  @input="onInput"
193
217
  />
@@ -33,9 +33,14 @@ export default /*tw*/ {
33
33
  subtractIcon: "{UIcon}",
34
34
  defaults: {
35
35
  size: "md",
36
+ decimalSeparator: ",",
37
+ thousandsSeparator: " ",
38
+ prefix: "",
36
39
  step: 1,
37
- min: 1,
40
+ min: 0,
38
41
  max: 999,
42
+ minFractionDigits: 0,
43
+ maxFractionDigits: 2,
39
44
  readonly: false,
40
45
  disabled: false,
41
46
  /* icons */
@@ -1,4 +1,4 @@
1
- import { mount } from "@vue/test-utils";
1
+ import { flushPromises, mount } from "@vue/test-utils";
2
2
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3
3
 
4
4
  import UInputCounter from "../UInputCounter.vue";
@@ -169,6 +169,90 @@ describe("UInputCounter.vue", () => {
169
169
  expect(addButton.props("disabled")).toBe(true);
170
170
  });
171
171
 
172
+ it("Min Fraction Digit – set fraction digit when it was not provided", async () => {
173
+ const initialValue = 12345678;
174
+ const initialValueWithFactions = "12 345 678,00";
175
+
176
+ const component = mount(UInputCounter, {
177
+ props: {
178
+ modelValue: initialValue,
179
+ minFractionDigits: 2,
180
+ },
181
+ });
182
+
183
+ await flushPromises();
184
+
185
+ expect(component.get("input").element.value).toBe(initialValueWithFactions);
186
+ });
187
+
188
+ it("Max Fraction Digit – truncate fraction digit to max value", async () => {
189
+ const initialValue = 12345678.011;
190
+ const initialValueWithFactions = "12 345 678,01";
191
+
192
+ const component = mount(UInputCounter, {
193
+ props: {
194
+ modelValue: initialValue,
195
+ maxFractionDigits: 2,
196
+ },
197
+ });
198
+
199
+ await flushPromises();
200
+
201
+ expect(component.get("input").element.value).toBe(initialValueWithFactions);
202
+ });
203
+
204
+ it("Decimal Separator – set correct decimal separator char", async () => {
205
+ const initialValue = 12345678.01;
206
+ const expectedDecimalSubString = "/01";
207
+
208
+ const component = mount(UInputCounter, {
209
+ props: {
210
+ modelValue: initialValue,
211
+ decimalSeparator: "/",
212
+ },
213
+ });
214
+
215
+ await flushPromises();
216
+
217
+ expect(component.get("input").element.value).toContain(expectedDecimalSubString);
218
+ });
219
+
220
+ it("Thousands Separator – set correct decimal separator char", async () => {
221
+ const initialValue = 12_345_678.01;
222
+ const expectedThousandsSeparator = "/";
223
+ const expectedThousandsSeparatorAmount = 2;
224
+
225
+ const component = mount(UInputCounter, {
226
+ props: {
227
+ modelValue: initialValue,
228
+ thousandsSeparator: expectedThousandsSeparator,
229
+ },
230
+ });
231
+
232
+ await flushPromises();
233
+
234
+ const inputValue = component.get("input").element.value;
235
+ const separatorCount = inputValue.split(expectedThousandsSeparator).length - 1;
236
+
237
+ expect(separatorCount).toBe(expectedThousandsSeparatorAmount);
238
+ });
239
+
240
+ it("Prefix – displays prefix at the beginning of the value", async () => {
241
+ const initialValue = 12345678;
242
+ const prefix = "pizza";
243
+
244
+ const component = mount(UInputCounter, {
245
+ props: {
246
+ modelValue: initialValue,
247
+ prefix: prefix,
248
+ },
249
+ });
250
+
251
+ await flushPromises();
252
+
253
+ expect(component.get("input").element.value.startsWith(prefix)).toBe(true);
254
+ });
255
+
172
256
  it("Readonly – renders text instead of input when readonly set to true", async () => {
173
257
  const component = mount(UInputCounter, {
174
258
  props: {
@@ -25,6 +25,31 @@ export interface Props {
25
25
  */
26
26
  max?: number;
27
27
 
28
+ /**
29
+ * Minimal number of signs after the decimal separator (fractional part of a number).
30
+ */
31
+ minFractionDigits?: number;
32
+
33
+ /**
34
+ * Maximal number of signs after the decimal separator (fractional part of a number).
35
+ */
36
+ maxFractionDigits?: number;
37
+
38
+ /**
39
+ * A symbol used to separate the integer part from the fractional part of a number.
40
+ */
41
+ decimalSeparator?: string;
42
+
43
+ /**
44
+ * A symbol used to separate the thousand parts of a number.
45
+ */
46
+ thousandsSeparator?: string;
47
+
48
+ /**
49
+ * Prefix to display before input value.
50
+ */
51
+ prefix?: string;
52
+
28
53
  /**
29
54
  * Input size.
30
55
  */
@@ -11,7 +11,7 @@ import useFormatNumber from "./useFormatNumber";
11
11
  import { COMPONENT_NAME, RAW_DECIMAL_MARK } from "./constants";
12
12
 
13
13
  import type { Props, Config } from "./types";
14
- import { getRawValue } from "./utilFormat";
14
+ import { getRawValue, getFixedNumber } from "./utilFormat";
15
15
 
16
16
  defineOptions({ inheritAttrs: false });
17
17
 
@@ -41,8 +41,15 @@ const emit = defineEmits([
41
41
  */
42
42
  "keyup",
43
43
 
44
+ /**
45
+ * Triggers when the input gains focus.
46
+ * @property {FocusEvent} event
47
+ */
48
+ "focus",
49
+
44
50
  /**
45
51
  * Triggers when the input loses focus.
52
+ * @property {FocusEvent} event
46
53
  */
47
54
  "blur",
48
55
  ]);
@@ -103,13 +110,17 @@ onMounted(() => {
103
110
  });
104
111
 
105
112
  function onKeyup(event: KeyboardEvent) {
106
- const numberValue = !Number.isNaN(parseFloat(rawValue.value)) ? parseFloat(rawValue.value) : "";
113
+ const numberValue = getFixedNumber(parseFloat(rawValue.value), props.maxFractionDigits || 10);
107
114
 
108
115
  localValue.value = props.valueType === "number" ? numberValue : rawValue.value || "";
109
116
 
110
117
  emit("keyup", event);
111
118
  }
112
119
 
120
+ function onFocus(event: FocusEvent) {
121
+ emit("focus", event);
122
+ }
123
+
113
124
  function onBlur(event: FocusEvent) {
114
125
  emit("blur", event);
115
126
  }
@@ -164,6 +175,7 @@ const { getDataTest, numberInputAttrs } = useUI<Config>(defaultConfig);
164
175
  v-bind="numberInputAttrs"
165
176
  :data-test="getDataTest()"
166
177
  @keyup="onKeyup"
178
+ @focus="onFocus"
167
179
  @blur="onBlur"
168
180
  @input="onInput"
169
181
  >
@@ -2,6 +2,13 @@ import { RAW_DECIMAL_MARK } from "./constants";
2
2
 
3
3
  import type { FormatOptions } from "./types";
4
4
 
5
+ export function getFixedNumber(value: number, maxDecimals: number = 10): number | "" {
6
+ if (!isFinite(value) || isNaN(value)) return "";
7
+ if (maxDecimals < 0) maxDecimals = 10;
8
+
9
+ return parseFloat(value.toFixed(maxDecimals));
10
+ }
11
+
5
12
  export function getRawValue(
6
13
  value: string,
7
14
  options: Pick<FormatOptions, "prefix" | "decimalSeparator" | "thousandsSeparator">,
@@ -26,6 +33,11 @@ export function getFormattedValue(value: string, options: FormatOptions): string
26
33
  const isValidMinFractionDigits = minFractionDigits <= maxFractionDigits;
27
34
  const actualMinFractionDigit = isValidMinFractionDigits ? minFractionDigits : maxFractionDigits;
28
35
 
36
+ const numericValue = parseFloat(value);
37
+ const fixedValue = getFixedNumber(numericValue, Math.max(maxFractionDigits, 10));
38
+
39
+ if (fixedValue === "") return prefix;
40
+
29
41
  const intlNumberOptions: Intl.NumberFormatOptions = {
30
42
  minimumFractionDigits: actualMinFractionDigit,
31
43
  maximumFractionDigits: maxFractionDigits,
@@ -38,14 +50,12 @@ export function getFormattedValue(value: string, options: FormatOptions): string
38
50
 
39
51
  const intlNumber = new Intl.NumberFormat("en-US", intlNumberOptions);
40
52
 
41
- const formattedValue = intlNumber
42
- .formatToParts(value as Intl.StringNumericLiteral)
43
- .map((part) => {
44
- if (part.type === "group") part.value = thousandsSeparator;
45
- if (part.type === "decimal") part.value = decimalSeparator;
53
+ const formattedValue = intlNumber.formatToParts(fixedValue).map((part) => {
54
+ if (part.type === "group") part.value = thousandsSeparator;
55
+ if (part.type === "decimal") part.value = decimalSeparator;
46
56
 
47
- return part;
48
- });
57
+ return part;
58
+ });
49
59
 
50
60
  formattedValue.unshift({ value: prefix, type: "minusSign" });
51
61
 
@@ -21,11 +21,23 @@ const props = withDefaults(defineProps<Props>(), {
21
21
 
22
22
  const emit = defineEmits([
23
23
  /**
24
- * Triggers when the input value is changes.
24
+ * Triggers when the input value is changed.
25
25
  * @property {string} modelValue
26
26
  * @property {number} modelValue
27
27
  */
28
28
  "update:modelValue",
29
+
30
+ /**
31
+ * Triggers when the input gains focus.
32
+ * @property {FocusEvent} event
33
+ */
34
+ "focus",
35
+
36
+ /**
37
+ * Triggers when the input loses focus.
38
+ * @property {FocusEvent} event
39
+ */
40
+ "blur",
29
41
  ]);
30
42
 
31
43
  const elementId = props.id || useId();
@@ -51,6 +63,14 @@ function onClickShowPassword() {
51
63
  isShownPassword.value = !isShownPassword.value;
52
64
  }
53
65
 
66
+ function onFocus(event: FocusEvent) {
67
+ emit("focus", event);
68
+ }
69
+
70
+ function onBlur(event: FocusEvent) {
71
+ emit("blur", event);
72
+ }
73
+
54
74
  /**
55
75
  * Get element / nested component attributes for each config token ✨
56
76
  * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
@@ -80,6 +100,8 @@ const { getDataTest, config, passwordInputAttrs, passwordIconAttrs, passwordIcon
80
100
  :disabled="disabled"
81
101
  v-bind="passwordInputAttrs"
82
102
  :data-test="getDataTest()"
103
+ @focus="onFocus"
104
+ @blur="onBlur"
83
105
  >
84
106
  <template #left>
85
107
  <!--
@@ -49,6 +49,7 @@ const emit = defineEmits([
49
49
 
50
50
  /**
51
51
  * Triggers when the search input value changes.
52
+ * @property {string} value
52
53
  */
53
54
  "searchChange",
54
55