@milaboratories/uikit 2.10.45 → 2.11.0

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.
Files changed (41) hide show
  1. package/.turbo/turbo-build.log +19 -19
  2. package/.turbo/turbo-formatter$colon$check.log +2 -2
  3. package/.turbo/turbo-linter$colon$check.log +2 -2
  4. package/.turbo/turbo-types$colon$check.log +1 -1
  5. package/CHANGELOG.md +18 -0
  6. package/dist/components/PlNumberField/PlNumberField.js.map +1 -1
  7. package/dist/components/PlNumberField/PlNumberField.vue.d.ts +45 -75
  8. package/dist/components/PlNumberField/PlNumberField.vue.d.ts.map +1 -1
  9. package/dist/components/PlNumberField/PlNumberField.vue2.js +129 -121
  10. package/dist/components/PlNumberField/PlNumberField.vue2.js.map +1 -1
  11. package/dist/components/PlNumberField/__test__/PlNumberField.spec.d.ts.map +1 -0
  12. package/dist/components/PlNumberField/__test__/parseNumber.spec.d.ts +2 -0
  13. package/dist/components/PlNumberField/__test__/parseNumber.spec.d.ts.map +1 -0
  14. package/dist/components/PlNumberField/parseNumber.d.ts +56 -7
  15. package/dist/components/PlNumberField/parseNumber.d.ts.map +1 -1
  16. package/dist/components/PlNumberField/parseNumber.js +40 -56
  17. package/dist/components/PlNumberField/parseNumber.js.map +1 -1
  18. package/dist/components/PlNumberField/pl-number-field.css +1 -1
  19. package/dist/components/PlSearchField/PlSearchField.js.map +1 -1
  20. package/dist/components/PlSearchField/PlSearchField.style.js.map +1 -1
  21. package/dist/components/PlSearchField/PlSearchField.vue.d.ts +20 -32
  22. package/dist/components/PlSearchField/PlSearchField.vue.d.ts.map +1 -1
  23. package/dist/components/PlSearchField/PlSearchField.vue2.js +4 -2
  24. package/dist/components/PlSearchField/PlSearchField.vue2.js.map +1 -1
  25. package/dist/components/PlTextField/PlTextField.js.map +1 -1
  26. package/dist/components/PlTextField/PlTextField.vue.d.ts +46 -118
  27. package/dist/components/PlTextField/PlTextField.vue.d.ts.map +1 -1
  28. package/dist/components/PlTextField/PlTextField.vue2.js +61 -58
  29. package/dist/components/PlTextField/PlTextField.vue2.js.map +1 -1
  30. package/package.json +5 -5
  31. package/src/components/PlNumberField/PlNumberField.vue +151 -143
  32. package/src/components/PlNumberField/__test__/PlNumberField.spec.ts +296 -0
  33. package/src/components/PlNumberField/__test__/parseNumber.spec.ts +204 -0
  34. package/src/components/PlNumberField/parseNumber.ts +125 -98
  35. package/src/components/PlNumberField/pl-number-field.scss +17 -4
  36. package/src/components/PlSearchField/PlSearchField.vue +8 -4
  37. package/src/components/PlTextField/PlTextField.vue +37 -49
  38. package/src/components/PlTextField/__tests__/TextField.spec.ts +2 -2
  39. package/dist/components/PlNumberField/__tests__/PlNumberField.spec.d.ts.map +0 -1
  40. package/src/components/PlNumberField/__tests__/PlNumberField.spec.ts +0 -182
  41. /package/dist/components/PlNumberField/{__tests__ → __test__}/PlNumberField.spec.d.ts +0 -0
@@ -1,169 +1,177 @@
1
1
  import e from "../PlTooltip/PlTooltip.js";
2
2
  import "../PlTooltip/index.js";
3
- import t from "../../utils/DoubleContour.js";
4
- import { useLabelNotch as n } from "../../utils/useLabelNotch.js";
3
+ import t from "../PlIcon16/PlIcon16.js";
4
+ import n from "../../utils/DoubleContour.js";
5
+ import { useLabelNotch as r } from "../../utils/useLabelNotch.js";
6
+ import "../PlIcon16/index.js";
5
7
  import './pl-number-field.css';/* empty css */
6
- import { parseNumber as r } from "./parseNumber.js";
7
- import { computed as i, createBlock as a, createCommentVNode as o, createElementBlock as s, createElementVNode as c, createTextVNode as l, createVNode as u, defineComponent as d, mergeModels as f, normalizeClass as p, openBlock as m, ref as h, renderSlot as g, toDisplayString as _, unref as v, useModel as y, useSlots as b, vModelText as x, watch as S, withCtx as C, withDirectives as w } from "vue";
8
- var T = { class: "pl-number-field__main-wrapper d-flex" }, E = {
8
+ import { numberToDecimalString as i, tryParseNumber as a, validateNumber as o } from "./parseNumber.js";
9
+ import { computed as s, createBlock as c, createCommentVNode as l, createElementBlock as u, createElementVNode as d, createTextVNode as f, createVNode as p, defineComponent as m, mergeModels as h, normalizeClass as g, openBlock as _, ref as v, renderSlot as y, toDisplayString as b, unref as x, useModel as S, useSlots as C, watch as w, withCtx as T, withModifiers as E } from "vue";
10
+ var D = { class: "pl-number-field__main-wrapper d-flex" }, O = { class: "pl-number-field__wrapper flex-grow d-flex flex-align-center" }, k = {
9
11
  key: 0,
10
12
  class: "text-description"
11
- }, D = ["disabled", "placeholder"], O = {
13
+ }, A = [
14
+ "value",
15
+ "disabled",
16
+ "placeholder"
17
+ ], j = {
12
18
  key: 0,
13
19
  class: "pl-number-field__error"
14
- }, k = /* @__PURE__ */ d({
20
+ }, M = /* @__PURE__ */ m({
15
21
  name: "PlNumberField",
16
- props: /* @__PURE__ */ f({
17
- disabled: { type: Boolean },
18
- label: { default: void 0 },
19
- placeholder: { default: void 0 },
22
+ props: /* @__PURE__ */ h({
23
+ required: {},
24
+ label: {},
25
+ placeholder: {},
20
26
  step: { default: 1 },
21
- minValue: { default: void 0 },
22
- maxValue: { default: void 0 },
23
- useIncrementButtons: {
24
- type: Boolean,
25
- default: !0
26
- },
27
- updateOnEnterOrClickOutside: { type: Boolean },
28
- errorMessage: { default: void 0 },
29
- validate: {
30
- type: Function,
31
- default: void 0
32
- },
33
- groupPosition: { default: void 0 }
27
+ minValue: {},
28
+ maxValue: {},
29
+ disabled: { type: Boolean },
30
+ disableSteps: { type: Boolean },
31
+ errorMessage: {},
32
+ validate: {},
33
+ clearable: {},
34
+ groupPosition: {}
34
35
  }, {
35
36
  modelValue: { required: !0 },
36
37
  modelModifiers: {}
37
38
  }),
38
- emits: ["update:modelValue"],
39
- setup(d) {
40
- let f = d, k = y(d, "modelValue"), A = b(), j = h(), M = h();
41
- n(j);
42
- function N(e) {
43
- return e === void 0 ? "" : String(+e);
44
- }
45
- let P = i(() => r(f, L.value)), F = h(void 0), I = () => F.value = void 0;
46
- S(k, (e) => {
47
- let t = P.value;
48
- (t.error || e !== t.value) && I();
39
+ emits: /* @__PURE__ */ h([
40
+ "blur",
41
+ "focus",
42
+ "enter"
43
+ ], ["update:modelValue"]),
44
+ setup(m, { emit: h }) {
45
+ let M = S(m, "modelValue"), N = h, P = m, F = C(), I = v();
46
+ r(I);
47
+ let L = v(i(M.value));
48
+ w(M, (e) => {
49
+ a(L.value).value !== e && (L.value = i(e));
49
50
  });
50
- let L = i({
51
- get() {
52
- return F.value ?? N(k.value);
53
- },
54
- set(e) {
55
- let t = r(f, e);
56
- F.value = t.cleanInput, t.error || f.updateOnEnterOrClickOutside ? M.value.value = t.cleanInput : k.value = t.value;
57
- }
58
- }), R = h(!1);
59
- function z() {
60
- P.value.error === void 0 && (k.value = P.value.value);
51
+ function R(e) {
52
+ let t = e.target;
53
+ L.value = t.value;
54
+ let n = a(t.value);
55
+ n.value !== void 0 && (M.value = n.value);
61
56
  }
62
- let B = i(() => {
63
- let e = [];
64
- f.errorMessage && e.push(f.errorMessage);
65
- let t = P.value;
66
- if (t.error) e.push(t.error.message);
67
- else if (f.validate && t.value !== void 0) {
68
- let n = f.validate(t.value);
69
- n && e.push(n);
70
- }
71
- return e = [...e], e.join(" ");
72
- }), V = i(() => {
73
- let e = P.value;
74
- return f.maxValue !== void 0 && e.value !== void 0 ? e.value >= f.maxValue : !1;
75
- }), H = i(() => {
76
- let e = P.value;
77
- return f.minValue !== void 0 && e.value !== void 0 ? e.value <= f.minValue : !1;
78
- }), U = i(() => 10 ** (f.step.toString().split(".").at(1)?.length ?? 0));
79
- function W() {
80
- let e = P.value.value;
81
- if (!V.value) {
82
- let t;
83
- t = e === void 0 ? f.minValue ? f.minValue : 0 : ((e || 0) * U.value + f.step * U.value) / U.value, k.value = f.maxValue === void 0 ? t : Math.min(f.maxValue, t);
57
+ function z() {
58
+ let e = L.value.trim(), t = a(e);
59
+ if (e === "" || t.value === void 0 && t.error === void 0) {
60
+ L.value = "", P.required || (M.value = void 0);
61
+ return;
84
62
  }
63
+ t.value !== void 0 && (M.value = t.value, L.value = i(t.value));
85
64
  }
86
- function G() {
87
- let e = P.value.value;
88
- if (!H.value) {
89
- let t;
90
- t = e === void 0 ? 0 : ((e || 0) * U.value - f.step * U.value) / U.value, k.value = f.minValue === void 0 ? t : Math.max(f.minValue, t);
91
- }
65
+ let B = s(() => {
66
+ if (P.errorMessage) return P.errorMessage;
67
+ let e = a(L.value);
68
+ if (e.error) return e.error;
69
+ if (e.value !== void 0) return o(e.value, P);
70
+ if (P.required && L.value.trim() === "") return "Value is required";
71
+ }), V = s(() => P.clearable && (M.value !== void 0 || L.value.trim() !== "") && !P.disabled);
72
+ function H() {
73
+ typeof P.clearable == "function" ? (M.value = P.clearable(), L.value = i(M.value)) : (M.value = void 0, L.value = "");
74
+ }
75
+ let U = s(() => B.value ? !0 : P.maxValue !== void 0 && M.value !== void 0 && M.value >= P.maxValue), W = s(() => B.value ? !0 : P.minValue !== void 0 && M.value !== void 0 && M.value <= P.minValue), G = s(() => 10 ** (P.step.toString().split(".").at(1)?.length ?? 0));
76
+ function K() {
77
+ if (U.value) return;
78
+ let e;
79
+ e = M.value === void 0 ? P.minValue ?? 0 : ((M.value || 0) * G.value + P.step * G.value) / G.value, M.value = P.maxValue === void 0 ? e : Math.min(P.maxValue, e);
80
+ }
81
+ function q() {
82
+ if (W.value) return;
83
+ let e;
84
+ e = M.value === void 0 ? 0 : ((M.value || 0) * G.value - P.step * G.value) / G.value, M.value = P.minValue === void 0 ? e : Math.max(P.minValue, e);
85
+ }
86
+ function J() {
87
+ z(), N("blur", M.value);
92
88
  }
93
- function K(e) {
94
- f.updateOnEnterOrClickOutside && (e.code === "Escape" && (L.value = N(k.value), M.value?.blur()), e.code === "Enter" && M.value?.blur()), e.code === "Enter" && (L.value = String(k.value)), ["ArrowDown", "ArrowUp"].includes(e.code) && e.preventDefault(), f.useIncrementButtons && e.code === "ArrowUp" && W(), f.useIncrementButtons && e.code === "ArrowDown" && G();
89
+ function Y() {
90
+ N("focus", M.value);
95
91
  }
96
- let q = (e) => {
92
+ function X(e) {
93
+ e.code === "Enter" && (z(), N("enter", M.value)), ["ArrowDown", "ArrowUp"].includes(e.code) && e.preventDefault(), !P.disableSteps && e.code === "ArrowUp" && K(), !P.disableSteps && e.code === "ArrowDown" && q();
94
+ }
95
+ function Z(e) {
97
96
  e.detail > 1 && e.preventDefault();
98
- };
99
- return (n, r) => (m(), s("div", {
97
+ }
98
+ return (r, i) => (_(), u("div", {
100
99
  ref_key: "rootRef",
101
- ref: j,
102
- class: p([{
103
- error: !!B.value.trim(),
104
- disabled: d.disabled
100
+ ref: I,
101
+ class: g([{
102
+ error: !!B.value,
103
+ disabled: m.disabled
105
104
  }, "pl-number-field d-flex-column"]),
106
- onKeydown: r[3] ||= (e) => K(e)
107
- }, [c("div", T, [
108
- u(t, {
105
+ onKeydown: X
106
+ }, [d("div", D, [
107
+ p(n, {
109
108
  class: "pl-number-field__contour",
110
- "group-position": d.groupPosition
109
+ "group-position": m.groupPosition
111
110
  }, null, 8, ["group-position"]),
112
- c("div", { class: p(["pl-number-field__wrapper flex-grow d-flex flex-align-center", { withoutArrows: !d.useIncrementButtons }]) }, [d.label ? (m(), s("label", E, [l(_(d.label) + " ", 1), v(A).tooltip ? (m(), a(v(e), {
113
- key: 0,
114
- class: "info",
115
- position: "top"
116
- }, {
117
- tooltip: C(() => [g(n.$slots, "tooltip")]),
118
- _: 3
119
- })) : o("", !0)])) : o("", !0), w(c("input", {
120
- ref_key: "inputRef",
121
- ref: M,
122
- "onUpdate:modelValue": r[0] ||= (e) => L.value = e,
123
- disabled: d.disabled,
124
- placeholder: d.placeholder,
125
- class: "text-s flex-grow",
126
- onFocusin: r[1] ||= (e) => R.value = !0,
127
- onFocusout: r[2] ||= (e) => {
128
- R.value = !1, z();
129
- }
130
- }, null, 40, D), [[x, L.value]])], 2),
131
- d.useIncrementButtons ? (m(), s("div", {
111
+ d("div", O, [
112
+ m.label ? (_(), u("label", k, [f(b(m.label) + " ", 1), x(F).tooltip ? (_(), c(x(e), {
113
+ key: 0,
114
+ class: "info",
115
+ position: "top"
116
+ }, {
117
+ tooltip: T(() => [y(r.$slots, "tooltip")]),
118
+ _: 3
119
+ })) : l("", !0)])) : l("", !0),
120
+ d("input", {
121
+ ref: "inputRef",
122
+ type: "text",
123
+ inputmode: "numeric",
124
+ value: L.value,
125
+ disabled: m.disabled,
126
+ placeholder: m.placeholder,
127
+ class: "text-s flex-grow",
128
+ onInput: R,
129
+ onFocusout: J,
130
+ onFocusin: Y
131
+ }, null, 40, A),
132
+ V.value ? (_(), c(x(t), {
133
+ key: 1,
134
+ class: "pl-number-field__clearable",
135
+ name: "delete-clear",
136
+ onClick: E(H, ["stop"])
137
+ })) : l("", !0)
138
+ ]),
139
+ P.disableSteps ? l("", !0) : (_(), u("div", {
132
140
  key: 0,
133
141
  class: "pl-number-field__icons d-flex-column",
134
- onMousedown: q
135
- }, [c("div", {
136
- class: p([{ disabled: V.value }, "pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center"]),
137
- onClick: W
138
- }, [...r[4] ||= [c("svg", {
142
+ onMousedown: Z
143
+ }, [d("div", {
144
+ class: g([{ disabled: U.value }, "pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center"]),
145
+ onClick: K
146
+ }, [...i[0] ||= [d("svg", {
139
147
  xmlns: "http://www.w3.org/2000/svg",
140
148
  width: "16",
141
149
  height: "16",
142
150
  viewBox: "0 0 16 16",
143
151
  fill: "none"
144
- }, [c("path", {
152
+ }, [d("path", {
145
153
  "fill-rule": "evenodd",
146
154
  "clip-rule": "evenodd",
147
155
  d: "M8 4.93933L13.5303 10.4697L12.4697 11.5303L8 7.06065L3.53033 11.5303L2.46967 10.4697L8 4.93933Z",
148
156
  fill: "#110529"
149
- })], -1)]], 2), c("div", {
150
- class: p([{ disabled: H.value }, "pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center"]),
151
- onClick: G
152
- }, [...r[5] ||= [c("svg", {
157
+ })], -1)]], 2), d("div", {
158
+ class: g([{ disabled: W.value }, "pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center"]),
159
+ onClick: q
160
+ }, [...i[1] ||= [d("svg", {
153
161
  xmlns: "http://www.w3.org/2000/svg",
154
162
  width: "16",
155
163
  height: "16",
156
164
  viewBox: "0 0 16 16",
157
165
  fill: "none"
158
- }, [c("path", {
166
+ }, [d("path", {
159
167
  "fill-rule": "evenodd",
160
168
  "clip-rule": "evenodd",
161
169
  d: "M2.46967 6.53033L3.53033 5.46967L8 9.93934L12.4697 5.46967L13.5303 6.53033L8 12.0607L2.46967 6.53033Z",
162
170
  fill: "#110529"
163
- })], -1)]], 2)], 32)) : o("", !0)
164
- ]), B.value.trim() ? (m(), s("div", O, _(B.value), 1)) : o("", !0)], 34));
171
+ })], -1)]], 2)], 32))
172
+ ]), B.value ? (_(), u("div", j, b(B.value), 1)) : l("", !0)], 34));
165
173
  }
166
174
  });
167
- export { k as default };
175
+ export { M as default };
168
176
 
169
177
  //# sourceMappingURL=PlNumberField.vue2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlNumberField.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlNumberField/PlNumberField.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Number input field with increment/decrement buttons, validation, and min/max constraints.\n *\n * @example\n * <PlNumberField v-model=\"price\" :step=\"0.01\" :min-value=\"0\" label=\"Price\" />\n *\n * @example\n * <PlNumberField\n * v-model=\"evenNumber\"\n * :validate=\"(v) => v % 2 !== 0 ? 'Number must be even' : undefined\"\n * :update-on-enter-or-click-outside=\"true\"\n * label=\"Even Number\"\n * />\n */\nexport default {\n name: \"PlNumberField\",\n};\n</script>\n\n<script setup lang=\"ts\">\nimport \"./pl-number-field.scss\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport { computed, ref, useSlots, watch } from \"vue\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport { parseNumber } from \"./parseNumber\";\n\nconst props = withDefaults(\n defineProps<{\n /** Input is disabled if true */\n disabled?: boolean;\n /** Label on the top border of the field, empty by default */\n label?: string;\n /** Input placeholder, empty by default */\n placeholder?: string;\n /** Step for increment/decrement buttons, 1 by default */\n step?: number;\n /** If defined - show an error if value is lower */\n minValue?: number;\n /** If defined - show an error if value is higher */\n maxValue?: number;\n /** If false - remove buttons on the right */\n useIncrementButtons?: boolean;\n /** If true - changes do not apply immediately, they apply only by removing focus from the input (by click enter or by click outside) */\n updateOnEnterOrClickOutside?: boolean;\n /** Error message that shows always when it's provided, without other checks */\n errorMessage?: string;\n /** Additional validity check for input value that must return an error text if failed */\n validate?: (v: number) => string | undefined;\n /** Makes some of corners not rounded */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n {\n step: 1,\n label: undefined,\n placeholder: undefined,\n minValue: undefined,\n maxValue: undefined,\n useIncrementButtons: true,\n updateOnEnter: false,\n errorMessage: undefined,\n validate: undefined,\n groupPosition: undefined,\n },\n);\n\nconst modelValue = defineModel<number | undefined>({ required: true });\n\nconst slots = useSlots();\n\nconst rootRef = ref<HTMLElement>();\nconst inputRef = ref<HTMLInputElement>();\n\nuseLabelNotch(rootRef);\n\nfunction modelToString(v: number | undefined) {\n return v === undefined ? \"\" : String(+v); // (+v) to avoid staying in input non-number values if they are provided in model\n}\n\nconst parsedResult = computed(() => parseNumber(props, inputValue.value));\n\nconst cachedValue = ref<string | undefined>(undefined);\n\nconst resetCachedValue = () => (cachedValue.value = undefined);\n\nwatch(modelValue, (n) => {\n const r = parsedResult.value;\n if (r.error || n !== r.value) {\n resetCachedValue();\n }\n});\n\nconst inputValue = computed({\n get() {\n return cachedValue.value ?? modelToString(modelValue.value);\n },\n set(nextValue: string) {\n const r = parseNumber(props, nextValue);\n\n cachedValue.value = r.cleanInput;\n\n if (r.error || props.updateOnEnterOrClickOutside) {\n inputRef.value!.value = r.cleanInput;\n } else {\n modelValue.value = r.value;\n }\n },\n});\n\nconst focused = ref(false);\n\nfunction applyChanges() {\n if (parsedResult.value.error === undefined) {\n modelValue.value = parsedResult.value.value;\n }\n}\n\nconst errors = computed(() => {\n let ers: string[] = [];\n\n if (props.errorMessage) {\n ers.push(props.errorMessage);\n }\n\n const r = parsedResult.value;\n\n if (r.error) {\n ers.push(r.error.message);\n } else if (props.validate && r.value !== undefined) {\n const error = props.validate(r.value);\n if (error) {\n ers.push(error);\n }\n }\n\n ers = [...ers];\n\n return ers.join(\" \");\n});\n\nconst isIncrementDisabled = computed(() => {\n const r = parsedResult.value;\n\n if (props.maxValue !== undefined && r.value !== undefined) {\n return r.value >= props.maxValue;\n }\n\n return false;\n});\n\nconst isDecrementDisabled = computed(() => {\n const r = parsedResult.value;\n\n if (props.minValue !== undefined && r.value !== undefined) {\n return r.value <= props.minValue;\n }\n\n return false;\n});\n\nconst multiplier = computed(() => 10 ** (props.step.toString().split(\".\").at(1)?.length ?? 0));\n\nfunction increment() {\n const r = parsedResult.value;\n\n const parsedValue = r.value;\n\n if (!isIncrementDisabled.value) {\n let nV;\n if (parsedValue === undefined) {\n nV = props.minValue ? props.minValue : 0;\n } else {\n nV =\n ((parsedValue || 0) * multiplier.value + props.step * multiplier.value) / multiplier.value;\n }\n modelValue.value = props.maxValue !== undefined ? Math.min(props.maxValue, nV) : nV;\n }\n}\n\nfunction decrement() {\n const r = parsedResult.value;\n\n const parsedValue = r.value;\n\n if (!isDecrementDisabled.value) {\n let nV;\n if (parsedValue === undefined) {\n nV = 0;\n } else {\n nV =\n ((parsedValue || 0) * multiplier.value - props.step * multiplier.value) / multiplier.value;\n }\n modelValue.value = props.minValue !== undefined ? Math.max(props.minValue, nV) : nV;\n }\n}\n\nfunction handleKeyPress(e: { code: string; preventDefault(): void }) {\n if (props.updateOnEnterOrClickOutside) {\n if (e.code === \"Escape\") {\n inputValue.value = modelToString(modelValue.value);\n inputRef.value?.blur();\n }\n if (e.code === \"Enter\") {\n inputRef.value?.blur();\n }\n }\n\n if (e.code === \"Enter\") {\n inputValue.value = String(modelValue.value); // to make .1 => 0.1, 10.00 => 10, remove leading zeros etc\n }\n\n if ([\"ArrowDown\", \"ArrowUp\"].includes(e.code)) {\n e.preventDefault();\n }\n\n if (props.useIncrementButtons && e.code === \"ArrowUp\") {\n increment();\n }\n\n if (props.useIncrementButtons && e.code === \"ArrowDown\") {\n decrement();\n }\n}\n\n// https://stackoverflow.com/questions/880512/prevent-text-selection-after-double-click#:~:text=If%20you%20encounter%20a%20situation,none%3B%20to%20the%20summary%20element.\n// this prevents selecting of more than input content in some cases,\n// but also disable selecting input content by double-click (useful feature)\nconst onMousedown = (ev: MouseEvent) => {\n if (ev.detail > 1) {\n ev.preventDefault();\n }\n};\n</script>\n\n<template>\n <div\n ref=\"rootRef\"\n :class=\"{ error: !!errors.trim(), disabled: disabled }\"\n class=\"pl-number-field d-flex-column\"\n @keydown=\"handleKeyPress($event)\"\n >\n <div class=\"pl-number-field__main-wrapper d-flex\">\n <DoubleContour class=\"pl-number-field__contour\" :group-position=\"groupPosition\" />\n <div\n class=\"pl-number-field__wrapper flex-grow d-flex flex-align-center\"\n :class=\"{ withoutArrows: !useIncrementButtons }\"\n >\n <label v-if=\"label\" class=\"text-description\">\n {{ label }}\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <input\n ref=\"inputRef\"\n v-model=\"inputValue\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n class=\"text-s flex-grow\"\n @focusin=\"focused = true\"\n @focusout=\"\n focused = false;\n applyChanges();\n \"\n />\n </div>\n <div\n v-if=\"useIncrementButtons\"\n class=\"pl-number-field__icons d-flex-column\"\n @mousedown=\"onMousedown\"\n >\n <div\n :class=\"{ disabled: isIncrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"increment\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M8 4.93933L13.5303 10.4697L12.4697 11.5303L8 7.06065L3.53033 11.5303L2.46967 10.4697L8 4.93933Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n <div\n :class=\"{ disabled: isDecrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"decrement\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M2.46967 6.53033L3.53033 5.46967L8 9.93934L12.4697 5.46967L13.5303 6.53033L8 12.0607L2.46967 6.53033Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n </div>\n </div>\n <div v-if=\"errors.trim()\" class=\"pl-number-field__error\">\n {{ errors }}\n </div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PlNumberField.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlNumberField/PlNumberField.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Number input field with increment/decrement buttons, validation, and min/max constraints.\n *\n * @example\n * <PlNumberField v-model=\"price\" :step=\"0.01\" :min-value=\"0\" label=\"Price\" />\n *\n * @example\n * <PlNumberField\n * v-model=\"evenNumber\"\n * :validate=\"(v) => v % 2 !== 0 ? 'Number must be even' : undefined\"\n * label=\"Even Number\"\n * />\n */\nexport default {\n name: \"PlNumberField\",\n};\n</script>\n\n<script\n setup\n lang=\"ts\"\n generic=\"\n R extends true | false,\n V extends undefined | number,\n C extends Exclude<V, R extends true ? undefined : never>\n \"\n>\nimport \"./pl-number-field.scss\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport { computed, ref, useSlots, watch } from \"vue\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { tryParseNumber, numberToDecimalString, validateNumber } from \"./parseNumber\";\n\nconst modelValue = defineModel<V>({ required: true });\n\nconst emit = defineEmits<{\n blur: [value: V];\n focus: [value: V];\n enter: [value: V];\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /** If `true`, the field is required and will show an error if left empty. */\n required?: R;\n /** Label on the top border of the field, empty by default */\n label?: string;\n /** Input placeholder, empty by default */\n placeholder?: string;\n /** Step for increment/decrement buttons, 1 by default */\n step?: number;\n /** If defined - show an error if value is lower */\n minValue?: number;\n /** If defined - show an error if value is higher */\n maxValue?: number;\n /** Input is disabled if true */\n disabled?: boolean;\n /** If true - remove buttons on the right */\n disableSteps?: boolean;\n /** Error message that shows always when it's provided, without other checks */\n errorMessage?: string;\n /** Additional validity check for input value that must return an error text if failed */\n validate?: (v: number) => string | undefined;\n /** If `true`, shows a clear button that resets value to `undefined`. If a function, calls it to get the reset value. */\n clearable?: (R extends true ? never : boolean) | (() => C);\n /** Makes some of corners not rounded */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n { step: 1 },\n);\n\nconst slots = useSlots();\n\nconst rootRef = ref<HTMLElement>();\n\nuseLabelNotch(rootRef);\n\nconst displayText = ref(numberToDecimalString(modelValue.value));\n\n// Sync display when model changes externally (parent, increment/decrement).\n// Skip if the current input already represents the same value.\nwatch(modelValue, (newVal) => {\n const parsed = tryParseNumber(displayText.value);\n if (parsed.value === newVal) return;\n displayText.value = numberToDecimalString(newVal);\n});\n\nfunction handleInput(event: Event) {\n const input = event.target as HTMLInputElement;\n displayText.value = input.value;\n\n const result = tryParseNumber(input.value);\n if (result.value !== undefined) {\n modelValue.value = result.value as V;\n }\n}\n\n// On Enter or blur: if parseable, replace display with canonical decimal string.\n// Converts exponential (1e-5) to plain form (0.00001).\nfunction commitValue() {\n const text = displayText.value.trim();\n const result = tryParseNumber(text);\n\n // Empty or partial (-, ., -.) → clear display and reset model for non-required fields\n if (text === \"\" || (result.value === undefined && result.error === undefined)) {\n displayText.value = \"\";\n if (!props.required) {\n modelValue.value = undefined as V;\n }\n return;\n }\n\n if (result.value !== undefined) {\n modelValue.value = result.value as V;\n displayText.value = numberToDecimalString(result.value);\n }\n}\n\nconst error = computed(() => {\n if (props.errorMessage) return props.errorMessage;\n\n const result = tryParseNumber(displayText.value);\n if (result.error) return result.error;\n if (result.value !== undefined) {\n return validateNumber(result.value, props);\n }\n\n if (props.required && displayText.value.trim() === \"\") {\n return \"Value is required\";\n }\n\n return undefined;\n});\n\nconst canShowClearable = computed(\n () =>\n props.clearable &&\n (modelValue.value !== undefined || displayText.value.trim() !== \"\") &&\n !props.disabled,\n);\n\nfunction clear() {\n if (typeof props.clearable === \"function\") {\n modelValue.value = props.clearable();\n displayText.value = numberToDecimalString(modelValue.value);\n } else {\n modelValue.value = undefined as V;\n displayText.value = \"\";\n }\n}\n\nconst isIncrementDisabled = computed(() => {\n if (error.value) return true;\n return (\n props.maxValue !== undefined &&\n modelValue.value !== undefined &&\n modelValue.value >= props.maxValue\n );\n});\n\nconst isDecrementDisabled = computed(() => {\n if (error.value) return true;\n return (\n props.minValue !== undefined &&\n modelValue.value !== undefined &&\n modelValue.value <= props.minValue\n );\n});\n\nconst multiplier = computed(() => 10 ** (props.step.toString().split(\".\").at(1)?.length ?? 0));\n\nfunction increment() {\n if (isIncrementDisabled.value) return;\n\n let nV: number;\n if (modelValue.value === undefined) {\n nV = props.minValue ?? 0;\n } else {\n nV =\n ((modelValue.value || 0) * multiplier.value + props.step * multiplier.value) /\n multiplier.value;\n }\n\n modelValue.value = (props.maxValue !== undefined ? Math.min(props.maxValue, nV) : nV) as V;\n}\n\nfunction decrement() {\n if (isDecrementDisabled.value) return;\n\n let nV: number;\n if (modelValue.value === undefined) {\n nV = 0;\n } else {\n nV =\n ((modelValue.value || 0) * multiplier.value - props.step * multiplier.value) /\n multiplier.value;\n }\n\n modelValue.value = (props.minValue !== undefined ? Math.max(props.minValue, nV) : nV) as V;\n}\n\nfunction handleBlur() {\n commitValue();\n emit(\"blur\", modelValue.value);\n}\n\nfunction handleFocus() {\n emit(\"focus\", modelValue.value);\n}\n\nfunction handleKeyDown(e: KeyboardEvent) {\n if (e.code === \"Enter\") {\n commitValue();\n emit(\"enter\", modelValue.value);\n }\n\n if ([\"ArrowDown\", \"ArrowUp\"].includes(e.code)) {\n e.preventDefault();\n }\n\n if (!props.disableSteps && e.code === \"ArrowUp\") {\n increment();\n }\n\n if (!props.disableSteps && e.code === \"ArrowDown\") {\n decrement();\n }\n}\n\n// Prevent selecting beyond input content on triple-click etc.\nfunction handleMousedown(ev: MouseEvent) {\n if (ev.detail > 1) {\n ev.preventDefault();\n }\n}\n</script>\n\n<template>\n <div\n ref=\"rootRef\"\n :class=\"{ error: !!error, disabled: disabled }\"\n class=\"pl-number-field d-flex-column\"\n @keydown=\"handleKeyDown\"\n >\n <div class=\"pl-number-field__main-wrapper d-flex\">\n <DoubleContour class=\"pl-number-field__contour\" :group-position=\"groupPosition\" />\n <div class=\"pl-number-field__wrapper flex-grow d-flex flex-align-center\">\n <label v-if=\"label\" class=\"text-description\">\n {{ label }}\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <input\n ref=\"inputRef\"\n type=\"text\"\n inputmode=\"numeric\"\n :value=\"displayText\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n class=\"text-s flex-grow\"\n @input=\"handleInput\"\n @focusout=\"handleBlur\"\n @focusin=\"handleFocus\"\n />\n <PlIcon16\n v-if=\"canShowClearable\"\n class=\"pl-number-field__clearable\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n </div>\n <div\n v-if=\"!props.disableSteps\"\n class=\"pl-number-field__icons d-flex-column\"\n @mousedown=\"handleMousedown\"\n >\n <div\n :class=\"{ disabled: isIncrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"increment\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M8 4.93933L13.5303 10.4697L12.4697 11.5303L8 7.06065L3.53033 11.5303L2.46967 10.4697L8 4.93933Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n <div\n :class=\"{ disabled: isDecrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"decrement\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M2.46967 6.53033L3.53033 5.46967L8 9.93934L12.4697 5.46967L13.5303 6.53033L8 12.0607L2.46967 6.53033Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n </div>\n </div>\n <div v-if=\"error\" class=\"pl-number-field__error\">\n {{ error }}\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlNumberField.spec.d.ts","sourceRoot":"","sources":["../../../../src/components/PlNumberField/__test__/PlNumberField.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=parseNumber.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseNumber.spec.d.ts","sourceRoot":"","sources":["../../../../src/components/PlNumberField/__test__/parseNumber.spec.ts"],"names":[],"mappings":""}
@@ -1,12 +1,61 @@
1
- type ParseResult = {
2
- error?: Error;
3
- value?: number;
4
- cleanInput: string;
1
+ /**
2
+ * Strict number parser. No locale guessing, no normalization.
3
+ * Non-canonical forms (leading zeros, trailing dots, etc.) are accepted —
4
+ * formatting to canonical form happens on blur/enter in the component.
5
+ *
6
+ * Input/output table (covers ~90% of real cases):
7
+ *
8
+ * | Input | Result | Reason |
9
+ * |---------------------|---------------------------------|---------------------------------|
10
+ * | "" | {} | empty |
11
+ * | "-" | {} | partial | apply blur/enter format
12
+ * | "." | {} | partial | apply blur/enter format
13
+ * | "-." | {} | partial | apply blur/enter format
14
+ * | "123." | { value: 123 } | trailing dot | apply blur/enter format
15
+ * | "1e" | { value: 1 } | partial exp → 1e+0 | apply blur/enter format
16
+ * | "1e-" | { value: 1 } | partial exp → 1e-0 | apply blur/enter format
17
+ * | "1e+" | { value: 1 } | partial exp → 1e+0 | apply blur/enter format
18
+ * | "123" | { value: 123 } | exact match |
19
+ * | "-5" | { value: -5 } | exact match |
20
+ * | "0.5" | { value: 0.5 } | exact match |
21
+ * | "0.0000000001" | { value: 1e-10 } | decimal form matches |
22
+ * | "1e-5" | { value: 0.00001 } | exponential notation | apply blur/enter format
23
+ * | "2e+10" | { value: 2e10 } | exponential notation | apply blur/enter format
24
+ * | ".5" | { value: 0.5 } | not canonical | apply blur/enter format
25
+ * | "01" | { value: 1 } | leading zero | apply blur/enter format
26
+ * | "1.0" | { value: 1 } | trailing zero | apply blur/enter format
27
+ * | "1.10" | { value: 1.1 } | trailing zero | apply blur/enter format
28
+ * | "+5" | { value: 5 } | unnecessary plus | apply blur/enter format
29
+ * | "1,5" | { error: "...separator..." } | comma instead of dot |
30
+ * | "1.232,111" | { error: "...separator..." } | EU locale format |
31
+ * | "1.237.62" | { error: "...separator..." } | multiple dots (EU thousands) |
32
+ * | "555.555.555,100" | { error: "...separator..." } | EU locale format |
33
+ * | "1,222,333.05" | { error: "...separator..." } | US locale format |
34
+ * | "abc" | { error: "not a number" } | letters |
35
+ * | "12abc" | { error: "not a number" } | letters mixed in |
36
+ * | "1.237.asdf62" | { error: "not a number" } | letters mixed in |
37
+ * | "9007199254740993" | { error: "precision..." } | integer exceeds safe range |
38
+ * | "0.1234567890123456789" | { error: "precision..." } | too many digits for float64 |
39
+ */
40
+ export type ParseResult = {
41
+ value: number;
42
+ error?: undefined;
43
+ } | {
44
+ value?: undefined;
45
+ error: string;
46
+ } | {
47
+ value?: undefined;
48
+ error?: undefined;
5
49
  };
6
- export declare function parseNumber(props: {
50
+ export declare function tryParseNumber(str: string): ParseResult;
51
+ /**
52
+ * Converts a number to a plain decimal string (no exponential notation).
53
+ * E.g. 1e-7 → "0.0000001", 2e+21 → "2000000000000000000000"
54
+ */
55
+ export declare function numberToDecimalString(n: number | undefined): string;
56
+ export declare function validateNumber(value: number, props: {
7
57
  minValue?: number;
8
58
  maxValue?: number;
9
59
  validate?: (v: number) => string | undefined;
10
- }, str: string): ParseResult;
11
- export {};
60
+ }): string | undefined;
12
61
  //# sourceMappingURL=parseNumber.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parseNumber.d.ts","sourceRoot":"","sources":["../../../src/components/PlNumberField/parseNumber.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GAAG;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AA8CF,wBAAgB,WAAW,CACzB,KAAK,EAAE;IACL,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC9C,EACD,GAAG,EAAE,MAAM,GACV,WAAW,CAkEb"}
1
+ {"version":3,"file":"parseNumber.d.ts","sourceRoot":"","sources":["../../../src/components/PlNumberField/parseNumber.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,MAAM,MAAM,WAAW,GACnB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GACpC;IAAE,KAAK,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,KAAK,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC;AA6C7C,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CA8BvD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CASnE;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE;IACL,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC9C,GACA,MAAM,GAAG,SAAS,CAQpB"}
@@ -1,65 +1,49 @@
1
- var e = /^[-−–+]?(\d+)?[.,]?(\d+)?$/;
2
- function t(e) {
3
- return e === "." || e === "," || e === "-";
4
- }
1
+ var e = /^-?\d+(\.\d+)?e[+-]?\d+$/i, t = /^-?\d+(\.\d+)?e[+-]?$/i;
2
+ /** "-", ".", "-." — NaN for Number() but clearly in-progress typing */
5
3
  function n(e) {
6
- return e = e.trim(), e = e.replace(",", "."), e = e.replace("−", "-"), e = e.replace("–", "-"), e = e.replace("+", ""), e;
4
+ return e === "-" || e === "." || e === "-.";
7
5
  }
6
+ /**
7
+ * Normalize a decimal string by removing cosmetic differences:
8
+ * leading +, leading zeros, trailing zeros after decimal, trailing dot.
9
+ * Used to compare user input with canonical float representation.
10
+ */
8
11
  function r(e) {
9
- return parseFloat(n(e));
12
+ let t = "";
13
+ return e.startsWith("-") ? (t = "-", e = e.slice(1)) : e.startsWith("+") && (e = e.slice(1)), e = e.replace(/^0+(?=\d)/, ""), e.startsWith(".") && (e = "0" + e), e.includes(".") && (e = e.replace(/0+$/, "").replace(/\.$/, "")), e === "" || e === "0" ? "0" : t + e;
14
+ }
15
+ /** Complete partial exponential: "1e" → "1e+0", "1e-" → "1e-0", "1e+" → "1e+0" */
16
+ function i(e) {
17
+ return /e$/i.test(e) ? e + "+0" : /e[+-]$/i.test(e) ? e + "0" : e;
10
18
  }
11
- function i(i) {
12
- if (i = i.trim(), t(i)) return i;
13
- if (/^-[^0-9.]/.test(i)) return "-";
14
- let a = i.match(/^(.*)[.,][^0-9].*$/);
15
- if (a) return a[1] + ".";
16
- if (i.match(e)) return n(i);
17
- let o = r(i);
18
- return isNaN(o) ? "" : String(+o);
19
+ function a(a) {
20
+ if (a = a.trim(), a === "" || n(a)) return {};
21
+ if (e.test(a) || t.test(a)) {
22
+ let e = i(a), t = Number(e);
23
+ return Number.isFinite(t) ? { value: t } : { error: "Value is not a number" };
24
+ }
25
+ let s = Number(a);
26
+ if (!Number.isFinite(s)) return /^[-+]?[\d.,\s]+$/.test(a) ? { error: "Use dot as decimal separator, e.g. 3.14" } : { error: "Value is not a number" };
27
+ let c = o(s);
28
+ return r(a) === c ? { value: s } : { error: `Precision exceeded, actual value: ${c}` };
19
29
  }
20
- function a(n, a) {
21
- a = a.trim();
22
- let o = i(a);
23
- if (a === "") return {
24
- value: void 0,
25
- cleanInput: o
26
- };
27
- if (!a.match(e)) return {
28
- error: Error("Value is not a number"),
29
- cleanInput: o
30
- };
31
- if (t(a)) return {
32
- error: Error("Enter a number"),
33
- cleanInput: o
34
- };
35
- let s = r(a);
36
- if (isNaN(s)) return {
37
- error: Error("Value is not a number"),
38
- cleanInput: o
39
- };
40
- if (n.minValue !== void 0 && s < n.minValue) return {
41
- error: Error(`Value must be higher than ${n.minValue}`),
42
- value: s,
43
- cleanInput: o
44
- };
45
- if (n.maxValue !== void 0 && s > n.maxValue) return {
46
- error: Error(`Value must be less than ${n.maxValue}`),
47
- value: s,
48
- cleanInput: o
49
- };
50
- if (n.validate) {
51
- let e = n.validate(s);
52
- if (e) return {
53
- error: Error(e),
54
- value: s,
55
- cleanInput: o
56
- };
30
+ /**
31
+ * Converts a number to a plain decimal string (no exponential notation).
32
+ * E.g. 1e-7 → "0.0000001", 2e+21 → "2000000000000000000000"
33
+ */
34
+ function o(e) {
35
+ if (e === void 0) return "";
36
+ let t = String(e);
37
+ if (!t.includes("e") && !t.includes("E")) return t;
38
+ try {
39
+ return e.toFixed(20).replace(/\.?0+$/, "");
40
+ } catch {
41
+ return t;
57
42
  }
58
- return {
59
- value: s,
60
- cleanInput: o
61
- };
62
43
  }
63
- export { a as parseNumber };
44
+ function s(e, t) {
45
+ return t.minValue !== void 0 && e < t.minValue ? `Value must be higher than ${t.minValue}` : t.maxValue !== void 0 && e > t.maxValue ? `Value must be less than ${t.maxValue}` : t.validate?.(e);
46
+ }
47
+ export { o as numberToDecimalString, a as tryParseNumber, s as validateNumber };
64
48
 
65
49
  //# sourceMappingURL=parseNumber.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parseNumber.js","names":[],"sources":["../../../src/components/PlNumberField/parseNumber.ts"],"sourcesContent":["type ParseResult = {\n error?: Error;\n value?: number;\n cleanInput: string;\n};\n\nconst NUMBER_REGEX = /^[-−–+]?(\\d+)?[.,]?(\\d+)?$/; // parseFloat works without errors on strings with multiple dots, or letters in value\n\nfunction isPartial(v: string) {\n return v === \".\" || v === \",\" || v === \"-\";\n}\n\nfunction clearNumericValue(v: string) {\n v = v.trim();\n v = v.replace(\",\", \".\");\n v = v.replace(\"−\", \"-\"); // minus, replacing for the case of input the whole copied value\n v = v.replace(\"–\", \"-\"); // dash, replacing for the case of input the whole copied value\n v = v.replace(\"+\", \"\");\n return v;\n}\n\nfunction stringToNumber(v: string) {\n return parseFloat(clearNumericValue(v));\n}\n\nfunction clearInput(v: string): string {\n v = v.trim();\n\n if (isPartial(v)) {\n return v;\n }\n\n if (/^-[^0-9.]/.test(v)) {\n return \"-\";\n }\n\n const match = v.match(/^(.*)[.,][^0-9].*$/);\n if (match) {\n return match[1] + \".\";\n }\n\n if (v.match(NUMBER_REGEX)) {\n return clearNumericValue(v);\n }\n\n const n = stringToNumber(v);\n\n return isNaN(n) ? \"\" : String(+n);\n}\n\nexport function parseNumber(\n props: {\n minValue?: number;\n maxValue?: number;\n validate?: (v: number) => string | undefined;\n },\n str: string,\n): ParseResult {\n str = str.trim();\n\n const cleanInput = clearInput(str);\n\n if (str === \"\") {\n return {\n value: undefined,\n cleanInput,\n };\n }\n\n if (!str.match(NUMBER_REGEX)) {\n return {\n error: Error(\"Value is not a number\"),\n cleanInput,\n };\n }\n\n if (isPartial(str)) {\n return {\n error: Error(\"Enter a number\"),\n cleanInput,\n };\n }\n\n const value = stringToNumber(str);\n\n if (isNaN(value)) {\n return {\n error: Error(\"Value is not a number\"),\n cleanInput,\n };\n }\n\n if (props.minValue !== undefined && value < props.minValue) {\n return {\n error: Error(`Value must be higher than ${props.minValue}`),\n value,\n cleanInput,\n };\n }\n\n if (props.maxValue !== undefined && value > props.maxValue) {\n return {\n error: Error(`Value must be less than ${props.maxValue}`),\n value,\n cleanInput,\n };\n }\n\n if (props.validate) {\n const error = props.validate(value);\n if (error) {\n return {\n error: Error(error),\n value,\n cleanInput,\n };\n }\n }\n\n return {\n value,\n cleanInput,\n };\n}\n"],"mappings":"AAMA,IAAM,IAAe;AAErB,SAAS,EAAU,GAAW;AAC5B,QAAO,MAAM,OAAO,MAAM,OAAO,MAAM;;AAGzC,SAAS,EAAkB,GAAW;AAMpC,QALA,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,KAAK,IAAI,EACvB,IAAI,EAAE,QAAQ,KAAK,IAAI,EACvB,IAAI,EAAE,QAAQ,KAAK,IAAI,EACvB,IAAI,EAAE,QAAQ,KAAK,GAAG,EACf;;AAGT,SAAS,EAAe,GAAW;AACjC,QAAO,WAAW,EAAkB,EAAE,CAAC;;AAGzC,SAAS,EAAW,GAAmB;AAGrC,KAFA,IAAI,EAAE,MAAM,EAER,EAAU,EAAE,CACd,QAAO;AAGT,KAAI,YAAY,KAAK,EAAE,CACrB,QAAO;CAGT,IAAM,IAAQ,EAAE,MAAM,qBAAqB;AAC3C,KAAI,EACF,QAAO,EAAM,KAAK;AAGpB,KAAI,EAAE,MAAM,EAAa,CACvB,QAAO,EAAkB,EAAE;CAG7B,IAAM,IAAI,EAAe,EAAE;AAE3B,QAAO,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,EAAE;;AAGnC,SAAgB,EACd,GAKA,GACa;AACb,KAAM,EAAI,MAAM;CAEhB,IAAM,IAAa,EAAW,EAAI;AAElC,KAAI,MAAQ,GACV,QAAO;EACL,OAAO,KAAA;EACP;EACD;AAGH,KAAI,CAAC,EAAI,MAAM,EAAa,CAC1B,QAAO;EACL,OAAO,MAAM,wBAAwB;EACrC;EACD;AAGH,KAAI,EAAU,EAAI,CAChB,QAAO;EACL,OAAO,MAAM,iBAAiB;EAC9B;EACD;CAGH,IAAM,IAAQ,EAAe,EAAI;AAEjC,KAAI,MAAM,EAAM,CACd,QAAO;EACL,OAAO,MAAM,wBAAwB;EACrC;EACD;AAGH,KAAI,EAAM,aAAa,KAAA,KAAa,IAAQ,EAAM,SAChD,QAAO;EACL,OAAO,MAAM,6BAA6B,EAAM,WAAW;EAC3D;EACA;EACD;AAGH,KAAI,EAAM,aAAa,KAAA,KAAa,IAAQ,EAAM,SAChD,QAAO;EACL,OAAO,MAAM,2BAA2B,EAAM,WAAW;EACzD;EACA;EACD;AAGH,KAAI,EAAM,UAAU;EAClB,IAAM,IAAQ,EAAM,SAAS,EAAM;AACnC,MAAI,EACF,QAAO;GACL,OAAO,MAAM,EAAM;GACnB;GACA;GACD;;AAIL,QAAO;EACL;EACA;EACD"}
1
+ {"version":3,"file":"parseNumber.js","names":[],"sources":["../../../src/components/PlNumberField/parseNumber.ts"],"sourcesContent":["/**\n * Strict number parser. No locale guessing, no normalization.\n * Non-canonical forms (leading zeros, trailing dots, etc.) are accepted —\n * formatting to canonical form happens on blur/enter in the component.\n *\n * Input/output table (covers ~90% of real cases):\n *\n * | Input | Result | Reason |\n * |---------------------|---------------------------------|---------------------------------|\n * | \"\" | {} | empty |\n * | \"-\" | {} | partial | apply blur/enter format\n * | \".\" | {} | partial | apply blur/enter format\n * | \"-.\" | {} | partial | apply blur/enter format\n * | \"123.\" | { value: 123 } | trailing dot | apply blur/enter format\n * | \"1e\" | { value: 1 } | partial exp → 1e+0 | apply blur/enter format\n * | \"1e-\" | { value: 1 } | partial exp → 1e-0 | apply blur/enter format\n * | \"1e+\" | { value: 1 } | partial exp → 1e+0 | apply blur/enter format\n * | \"123\" | { value: 123 } | exact match |\n * | \"-5\" | { value: -5 } | exact match |\n * | \"0.5\" | { value: 0.5 } | exact match |\n * | \"0.0000000001\" | { value: 1e-10 } | decimal form matches |\n * | \"1e-5\" | { value: 0.00001 } | exponential notation | apply blur/enter format\n * | \"2e+10\" | { value: 2e10 } | exponential notation | apply blur/enter format\n * | \".5\" | { value: 0.5 } | not canonical | apply blur/enter format\n * | \"01\" | { value: 1 } | leading zero | apply blur/enter format\n * | \"1.0\" | { value: 1 } | trailing zero | apply blur/enter format\n * | \"1.10\" | { value: 1.1 } | trailing zero | apply blur/enter format\n * | \"+5\" | { value: 5 } | unnecessary plus | apply blur/enter format\n * | \"1,5\" | { error: \"...separator...\" } | comma instead of dot |\n * | \"1.232,111\" | { error: \"...separator...\" } | EU locale format |\n * | \"1.237.62\" | { error: \"...separator...\" } | multiple dots (EU thousands) |\n * | \"555.555.555,100\" | { error: \"...separator...\" } | EU locale format |\n * | \"1,222,333.05\" | { error: \"...separator...\" } | US locale format |\n * | \"abc\" | { error: \"not a number\" } | letters |\n * | \"12abc\" | { error: \"not a number\" } | letters mixed in |\n * | \"1.237.asdf62\" | { error: \"not a number\" } | letters mixed in |\n * | \"9007199254740993\" | { error: \"precision...\" } | integer exceeds safe range |\n * | \"0.1234567890123456789\" | { error: \"precision...\" } | too many digits for float64 |\n */\n\nexport type ParseResult =\n | { value: number; error?: undefined }\n | { value?: undefined; error: string }\n | { value?: undefined; error?: undefined };\n\nconst EXP_RE = /^-?\\d+(\\.\\d+)?e[+-]?\\d+$/i;\nconst EXP_PARTIAL_RE = /^-?\\d+(\\.\\d+)?e[+-]?$/i;\n\n/** \"-\", \".\", \"-.\" — NaN for Number() but clearly in-progress typing */\nfunction isPartialInput(str: string): boolean {\n return str === \"-\" || str === \".\" || str === \"-.\";\n}\n\n/**\n * Normalize a decimal string by removing cosmetic differences:\n * leading +, leading zeros, trailing zeros after decimal, trailing dot.\n * Used to compare user input with canonical float representation.\n */\nfunction normalizeDecimalString(s: string): string {\n let sign = \"\";\n if (s.startsWith(\"-\")) {\n sign = \"-\";\n s = s.slice(1);\n } else if (s.startsWith(\"+\")) {\n s = s.slice(1);\n }\n\n // Remove leading zeros (keep one before decimal point)\n s = s.replace(/^0+(?=\\d)/, \"\");\n if (s.startsWith(\".\")) s = \"0\" + s;\n\n // Remove trailing zeros after decimal point, then trailing dot\n if (s.includes(\".\")) {\n s = s.replace(/0+$/, \"\").replace(/\\.$/, \"\");\n }\n\n if (s === \"\" || s === \"0\") return \"0\";\n\n return sign + s;\n}\n\n/** Complete partial exponential: \"1e\" \"1e+0\", \"1e-\" \"1e-0\", \"1e+\" → \"1e+0\" */\nfunction completeExponential(str: string): string {\n if (/e$/i.test(str)) return str + \"+0\";\n if (/e[+-]$/i.test(str)) return str + \"0\";\n return str;\n}\n\nexport function tryParseNumber(str: string): ParseResult {\n str = str.trim();\n if (str === \"\") return {};\n if (isPartialInput(str)) return {};\n\n // Exponential notation (full or partial)\n if (EXP_RE.test(str) || EXP_PARTIAL_RE.test(str)) {\n const completed = completeExponential(str);\n const n = Number(completed);\n if (!Number.isFinite(n)) return { error: \"Value is not a number\" };\n return { value: n };\n }\n\n const n = Number(str);\n if (!Number.isFinite(n)) {\n // Only digits, dots, commas, sign, spaces → likely a locale/format issue\n if (/^[-+]?[\\d.,\\s]+$/.test(str)) {\n return { error: \"Use dot as decimal separator, e.g. 3.14\" };\n }\n return { error: \"Value is not a number\" };\n }\n\n // Precision loss: input has more precision than float64 can represent\n const canonical = numberToDecimalString(n);\n const normalized = normalizeDecimalString(str);\n if (normalized !== canonical) {\n return { error: `Precision exceeded, actual value: ${canonical}` };\n }\n\n return { value: n };\n}\n\n/**\n * Converts a number to a plain decimal string (no exponential notation).\n * E.g. 1e-7 → \"0.0000001\", 2e+21 → \"2000000000000000000000\"\n */\nexport function numberToDecimalString(n: number | undefined): string {\n if (n === undefined) return \"\";\n const s = String(n);\n if (!s.includes(\"e\") && !s.includes(\"E\")) return s;\n try {\n return n.toFixed(20).replace(/\\.?0+$/, \"\");\n } catch {\n return s;\n }\n}\n\nexport function validateNumber(\n value: number,\n props: {\n minValue?: number;\n maxValue?: number;\n validate?: (v: number) => string | undefined;\n },\n): string | undefined {\n if (props.minValue !== undefined && value < props.minValue) {\n return `Value must be higher than ${props.minValue}`;\n }\n if (props.maxValue !== undefined && value > props.maxValue) {\n return `Value must be less than ${props.maxValue}`;\n }\n return props.validate?.(value);\n}\n"],"mappings":"AA6CA,IAAM,IAAS,6BACT,IAAiB;;AAGvB,SAAS,EAAe,GAAsB;AAC5C,QAAO,MAAQ,OAAO,MAAQ,OAAO,MAAQ;;;;;;;AAQ/C,SAAS,EAAuB,GAAmB;CACjD,IAAI,IAAO;AAmBX,QAlBI,EAAE,WAAW,IAAI,IACnB,IAAO,KACP,IAAI,EAAE,MAAM,EAAE,IACL,EAAE,WAAW,IAAI,KAC1B,IAAI,EAAE,MAAM,EAAE,GAIhB,IAAI,EAAE,QAAQ,aAAa,GAAG,EAC1B,EAAE,WAAW,IAAI,KAAE,IAAI,MAAM,IAG7B,EAAE,SAAS,IAAI,KACjB,IAAI,EAAE,QAAQ,OAAO,GAAG,CAAC,QAAQ,OAAO,GAAG,GAGzC,MAAM,MAAM,MAAM,MAAY,MAE3B,IAAO;;;AAIhB,SAAS,EAAoB,GAAqB;AAGhD,QAFI,MAAM,KAAK,EAAI,GAAS,IAAM,OAC9B,UAAU,KAAK,EAAI,GAAS,IAAM,MAC/B;;AAGT,SAAgB,EAAe,GAA0B;AAGvD,KAFA,IAAM,EAAI,MAAM,EACZ,MAAQ,MACR,EAAe,EAAI,CAAE,QAAO,EAAE;AAGlC,KAAI,EAAO,KAAK,EAAI,IAAI,EAAe,KAAK,EAAI,EAAE;EAChD,IAAM,IAAY,EAAoB,EAAI,EACpC,IAAI,OAAO,EAAU;AAE3B,SADK,OAAO,SAAS,EAAE,GAChB,EAAE,OAAO,GAAG,GADa,EAAE,OAAO,yBAAyB;;CAIpE,IAAM,IAAI,OAAO,EAAI;AACrB,KAAI,CAAC,OAAO,SAAS,EAAE,CAKrB,QAHI,mBAAmB,KAAK,EAAI,GACvB,EAAE,OAAO,2CAA2C,GAEtD,EAAE,OAAO,yBAAyB;CAI3C,IAAM,IAAY,EAAsB,EAAE;AAM1C,QALmB,EAAuB,EAAI,KAC3B,IAIZ,EAAE,OAAO,GAAG,GAHV,EAAE,OAAO,qCAAqC,KAAa;;;;;;AAUtE,SAAgB,EAAsB,GAA+B;AACnE,KAAI,MAAM,KAAA,EAAW,QAAO;CAC5B,IAAM,IAAI,OAAO,EAAE;AACnB,KAAI,CAAC,EAAE,SAAS,IAAI,IAAI,CAAC,EAAE,SAAS,IAAI,CAAE,QAAO;AACjD,KAAI;AACF,SAAO,EAAE,QAAQ,GAAG,CAAC,QAAQ,UAAU,GAAG;SACpC;AACN,SAAO;;;AAIX,SAAgB,EACd,GACA,GAKoB;AAOpB,QANI,EAAM,aAAa,KAAA,KAAa,IAAQ,EAAM,WACzC,6BAA6B,EAAM,aAExC,EAAM,aAAa,KAAA,KAAa,IAAQ,EAAM,WACzC,2BAA2B,EAAM,aAEnC,EAAM,WAAW,EAAM"}