@eristack/percent 0.1.1 → 0.1.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.
@@ -0,0 +1,83 @@
1
+ // src/core/percent.ts
2
+ import Decimal from "decimal.js";
3
+ var PercentParseError = class extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "PercentParseError";
7
+ }
8
+ };
9
+ function normalizeRatio(ratio) {
10
+ if (ratio.isNegative()) {
11
+ throw new PercentParseError("Percent ratio cannot be negative");
12
+ }
13
+ return { ratio: ratio.toFixed() };
14
+ }
15
+ function parsePercent(input) {
16
+ if (typeof input === "string") {
17
+ const trimmed = input.trim();
18
+ if (!trimmed) {
19
+ throw new PercentParseError("Percent input cannot be empty");
20
+ }
21
+ try {
22
+ if (trimmed.endsWith("%")) {
23
+ const n = trimmed.slice(0, -1).trim();
24
+ if (!n) throw new PercentParseError('Percent symbol missing value before "%"');
25
+ return normalizeRatio(new Decimal(n).div(100));
26
+ }
27
+ return normalizeRatio(new Decimal(trimmed));
28
+ } catch (err) {
29
+ if (err instanceof PercentParseError) throw err;
30
+ throw new PercentParseError(`Invalid percent "${input}"`);
31
+ }
32
+ }
33
+ switch (input.kind) {
34
+ case "ratio":
35
+ return normalizeRatio(new Decimal(input.value));
36
+ case "percent":
37
+ return normalizeRatio(new Decimal(input.value).div(100));
38
+ case "basisPoints":
39
+ return normalizeRatio(new Decimal(input.value).div(1e4));
40
+ default: {
41
+ const _exhaustive = input;
42
+ throw new PercentParseError(`Unknown percent input ${String(_exhaustive)}`);
43
+ }
44
+ }
45
+ }
46
+ function fromBasisPoints(bps) {
47
+ return parsePercent({ kind: "basisPoints", value: bps });
48
+ }
49
+ function fromPercentSymbol(value) {
50
+ return parsePercent({ kind: "percent", value });
51
+ }
52
+ function toBasisPoints(p) {
53
+ return new Decimal(p.ratio).times(1e4).toFixed(0);
54
+ }
55
+ function toPercentSymbol(p) {
56
+ return `${new Decimal(p.ratio).times(100).toFixed()}%`;
57
+ }
58
+ function percentOf(amount, percent) {
59
+ return new Decimal(amount).times(percent.ratio).toFixed();
60
+ }
61
+ function plusPercent(amount, percent) {
62
+ return new Decimal(amount).times(new Decimal(1).plus(percent.ratio)).toFixed();
63
+ }
64
+ function minusPercent(amount, percent) {
65
+ return new Decimal(amount).times(new Decimal(1).minus(percent.ratio)).toFixed();
66
+ }
67
+ function addPercents(a, b) {
68
+ return { ratio: new Decimal(a.ratio).plus(b.ratio).toFixed() };
69
+ }
70
+
71
+ export {
72
+ PercentParseError,
73
+ parsePercent,
74
+ fromBasisPoints,
75
+ fromPercentSymbol,
76
+ toBasisPoints,
77
+ toPercentSymbol,
78
+ percentOf,
79
+ plusPercent,
80
+ minusPercent,
81
+ addPercents
82
+ };
83
+ //# sourceMappingURL=chunk-MW4KRZDX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/percent.ts"],"sourcesContent":["import Decimal from \"decimal.js\";\nimport type { Percent, PercentInput } from \"./types.js\";\n\nexport class PercentParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PercentParseError\";\n }\n}\n\nfunction normalizeRatio(ratio: Decimal): Percent {\n if (ratio.isNegative()) {\n throw new PercentParseError(\"Percent ratio cannot be negative\");\n }\n return { ratio: ratio.toFixed() };\n}\n\n/** Parse \"11%\", \"0.11\", or basis points via PercentInput. */\nexport function parsePercent(input: PercentInput | string): Percent {\n if (typeof input === \"string\") {\n const trimmed = input.trim();\n if (!trimmed) {\n throw new PercentParseError(\"Percent input cannot be empty\");\n }\n try {\n if (trimmed.endsWith(\"%\")) {\n const n = trimmed.slice(0, -1).trim();\n if (!n) throw new PercentParseError('Percent symbol missing value before \"%\"');\n return normalizeRatio(new Decimal(n).div(100));\n }\n return normalizeRatio(new Decimal(trimmed));\n } catch (err) {\n if (err instanceof PercentParseError) throw err;\n throw new PercentParseError(`Invalid percent \"${input}\"`);\n }\n }\n switch (input.kind) {\n case \"ratio\":\n return normalizeRatio(new Decimal(input.value));\n case \"percent\":\n return normalizeRatio(new Decimal(input.value).div(100));\n case \"basisPoints\":\n return normalizeRatio(new Decimal(input.value).div(10_000));\n default: {\n const _exhaustive: never = input;\n throw new PercentParseError(`Unknown percent input ${String(_exhaustive)}`);\n }\n }\n}\n\nexport function fromBasisPoints(bps: string): Percent {\n return parsePercent({ kind: \"basisPoints\", value: bps });\n}\n\nexport function fromPercentSymbol(value: string): Percent {\n return parsePercent({ kind: \"percent\", value });\n}\n\nexport function toBasisPoints(p: Percent): string {\n return new Decimal(p.ratio).times(10_000).toFixed(0);\n}\n\nexport function toPercentSymbol(p: Percent): string {\n return `${new Decimal(p.ratio).times(100).toFixed()}%`;\n}\n\n/** amount * ratio — both decimal strings. */\nexport function percentOf(amount: string, percent: Percent): string {\n return new Decimal(amount).times(percent.ratio).toFixed();\n}\n\nexport function plusPercent(amount: string, percent: Percent): string {\n return new Decimal(amount).times(new Decimal(1).plus(percent.ratio)).toFixed();\n}\n\nexport function minusPercent(amount: string, percent: Percent): string {\n return new Decimal(amount).times(new Decimal(1).minus(percent.ratio)).toFixed();\n}\n\nexport function addPercents(a: Percent, b: Percent): Percent {\n return { ratio: new Decimal(a.ratio).plus(b.ratio).toFixed() };\n}\n"],"mappings":";AAAA,OAAO,aAAa;AAGb,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEA,SAAS,eAAe,OAAyB;AAC/C,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,kBAAkB,kCAAkC;AAAA,EAChE;AACA,SAAO,EAAE,OAAO,MAAM,QAAQ,EAAE;AAClC;AAGO,SAAS,aAAa,OAAuC;AAClE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,kBAAkB,+BAA+B;AAAA,IAC7D;AACA,QAAI;AACF,UAAI,QAAQ,SAAS,GAAG,GAAG;AACzB,cAAM,IAAI,QAAQ,MAAM,GAAG,EAAE,EAAE,KAAK;AACpC,YAAI,CAAC,EAAG,OAAM,IAAI,kBAAkB,yCAAyC;AAC7E,eAAO,eAAe,IAAI,QAAQ,CAAC,EAAE,IAAI,GAAG,CAAC;AAAA,MAC/C;AACA,aAAO,eAAe,IAAI,QAAQ,OAAO,CAAC;AAAA,IAC5C,SAAS,KAAK;AACZ,UAAI,eAAe,kBAAmB,OAAM;AAC5C,YAAM,IAAI,kBAAkB,oBAAoB,KAAK,GAAG;AAAA,IAC1D;AAAA,EACF;AACA,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,eAAe,IAAI,QAAQ,MAAM,KAAK,CAAC;AAAA,IAChD,KAAK;AACH,aAAO,eAAe,IAAI,QAAQ,MAAM,KAAK,EAAE,IAAI,GAAG,CAAC;AAAA,IACzD,KAAK;AACH,aAAO,eAAe,IAAI,QAAQ,MAAM,KAAK,EAAE,IAAI,GAAM,CAAC;AAAA,IAC5D,SAAS;AACP,YAAM,cAAqB;AAC3B,YAAM,IAAI,kBAAkB,yBAAyB,OAAO,WAAW,CAAC,EAAE;AAAA,IAC5E;AAAA,EACF;AACF;AAEO,SAAS,gBAAgB,KAAsB;AACpD,SAAO,aAAa,EAAE,MAAM,eAAe,OAAO,IAAI,CAAC;AACzD;AAEO,SAAS,kBAAkB,OAAwB;AACxD,SAAO,aAAa,EAAE,MAAM,WAAW,MAAM,CAAC;AAChD;AAEO,SAAS,cAAc,GAAoB;AAChD,SAAO,IAAI,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAM,EAAE,QAAQ,CAAC;AACrD;AAEO,SAAS,gBAAgB,GAAoB;AAClD,SAAO,GAAG,IAAI,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE,QAAQ,CAAC;AACrD;AAGO,SAAS,UAAU,QAAgB,SAA0B;AAClE,SAAO,IAAI,QAAQ,MAAM,EAAE,MAAM,QAAQ,KAAK,EAAE,QAAQ;AAC1D;AAEO,SAAS,YAAY,QAAgB,SAA0B;AACpE,SAAO,IAAI,QAAQ,MAAM,EAAE,MAAM,IAAI,QAAQ,CAAC,EAAE,KAAK,QAAQ,KAAK,CAAC,EAAE,QAAQ;AAC/E;AAEO,SAAS,aAAa,QAAgB,SAA0B;AACrE,SAAO,IAAI,QAAQ,MAAM,EAAE,MAAM,IAAI,QAAQ,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,EAAE,QAAQ;AAChF;AAEO,SAAS,YAAY,GAAY,GAAqB;AAC3D,SAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE;AAC/D;","names":[]}
package/dist/index.js CHANGED
@@ -1,72 +1,15 @@
1
- // src/core/percent.ts
2
- import Decimal from "decimal.js";
3
- var PercentParseError = class extends Error {
4
- constructor(message) {
5
- super(message);
6
- this.name = "PercentParseError";
7
- }
8
- };
9
- function normalizeRatio(ratio) {
10
- if (ratio.isNegative()) {
11
- throw new PercentParseError("Percent ratio cannot be negative");
12
- }
13
- return { ratio: ratio.toFixed() };
14
- }
15
- function parsePercent(input) {
16
- if (typeof input === "string") {
17
- const trimmed = input.trim();
18
- if (!trimmed) {
19
- throw new PercentParseError("Percent input cannot be empty");
20
- }
21
- try {
22
- if (trimmed.endsWith("%")) {
23
- const n = trimmed.slice(0, -1).trim();
24
- if (!n) throw new PercentParseError('Percent symbol missing value before "%"');
25
- return normalizeRatio(new Decimal(n).div(100));
26
- }
27
- return normalizeRatio(new Decimal(trimmed));
28
- } catch (err) {
29
- if (err instanceof PercentParseError) throw err;
30
- throw new PercentParseError(`Invalid percent "${input}"`);
31
- }
32
- }
33
- switch (input.kind) {
34
- case "ratio":
35
- return normalizeRatio(new Decimal(input.value));
36
- case "percent":
37
- return normalizeRatio(new Decimal(input.value).div(100));
38
- case "basisPoints":
39
- return normalizeRatio(new Decimal(input.value).div(1e4));
40
- default: {
41
- const _exhaustive = input;
42
- throw new PercentParseError(`Unknown percent input ${String(_exhaustive)}`);
43
- }
44
- }
45
- }
46
- function fromBasisPoints(bps) {
47
- return parsePercent({ kind: "basisPoints", value: bps });
48
- }
49
- function fromPercentSymbol(value) {
50
- return parsePercent({ kind: "percent", value });
51
- }
52
- function toBasisPoints(p) {
53
- return new Decimal(p.ratio).times(1e4).toFixed(0);
54
- }
55
- function toPercentSymbol(p) {
56
- return `${new Decimal(p.ratio).times(100).toFixed()}%`;
57
- }
58
- function percentOf(amount, percent) {
59
- return new Decimal(amount).times(percent.ratio).toFixed();
60
- }
61
- function plusPercent(amount, percent) {
62
- return new Decimal(amount).times(new Decimal(1).plus(percent.ratio)).toFixed();
63
- }
64
- function minusPercent(amount, percent) {
65
- return new Decimal(amount).times(new Decimal(1).minus(percent.ratio)).toFixed();
66
- }
67
- function addPercents(a, b) {
68
- return { ratio: new Decimal(a.ratio).plus(b.ratio).toFixed() };
69
- }
1
+ import {
2
+ PercentParseError,
3
+ addPercents,
4
+ fromBasisPoints,
5
+ fromPercentSymbol,
6
+ minusPercent,
7
+ parsePercent,
8
+ percentOf,
9
+ plusPercent,
10
+ toBasisPoints,
11
+ toPercentSymbol
12
+ } from "./chunk-MW4KRZDX.js";
70
13
  export {
71
14
  PercentParseError,
72
15
  addPercents,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/percent.ts"],"sourcesContent":["import Decimal from \"decimal.js\";\nimport type { Percent, PercentInput } from \"./types.js\";\n\nexport class PercentParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PercentParseError\";\n }\n}\n\nfunction normalizeRatio(ratio: Decimal): Percent {\n if (ratio.isNegative()) {\n throw new PercentParseError(\"Percent ratio cannot be negative\");\n }\n return { ratio: ratio.toFixed() };\n}\n\n/** Parse \"11%\", \"0.11\", or basis points via PercentInput. */\nexport function parsePercent(input: PercentInput | string): Percent {\n if (typeof input === \"string\") {\n const trimmed = input.trim();\n if (!trimmed) {\n throw new PercentParseError(\"Percent input cannot be empty\");\n }\n try {\n if (trimmed.endsWith(\"%\")) {\n const n = trimmed.slice(0, -1).trim();\n if (!n) throw new PercentParseError('Percent symbol missing value before \"%\"');\n return normalizeRatio(new Decimal(n).div(100));\n }\n return normalizeRatio(new Decimal(trimmed));\n } catch (err) {\n if (err instanceof PercentParseError) throw err;\n throw new PercentParseError(`Invalid percent \"${input}\"`);\n }\n }\n switch (input.kind) {\n case \"ratio\":\n return normalizeRatio(new Decimal(input.value));\n case \"percent\":\n return normalizeRatio(new Decimal(input.value).div(100));\n case \"basisPoints\":\n return normalizeRatio(new Decimal(input.value).div(10_000));\n default: {\n const _exhaustive: never = input;\n throw new PercentParseError(`Unknown percent input ${String(_exhaustive)}`);\n }\n }\n}\n\nexport function fromBasisPoints(bps: string): Percent {\n return parsePercent({ kind: \"basisPoints\", value: bps });\n}\n\nexport function fromPercentSymbol(value: string): Percent {\n return parsePercent({ kind: \"percent\", value });\n}\n\nexport function toBasisPoints(p: Percent): string {\n return new Decimal(p.ratio).times(10_000).toFixed(0);\n}\n\nexport function toPercentSymbol(p: Percent): string {\n return `${new Decimal(p.ratio).times(100).toFixed()}%`;\n}\n\n/** amount * ratio — both decimal strings. */\nexport function percentOf(amount: string, percent: Percent): string {\n return new Decimal(amount).times(percent.ratio).toFixed();\n}\n\nexport function plusPercent(amount: string, percent: Percent): string {\n return new Decimal(amount).times(new Decimal(1).plus(percent.ratio)).toFixed();\n}\n\nexport function minusPercent(amount: string, percent: Percent): string {\n return new Decimal(amount).times(new Decimal(1).minus(percent.ratio)).toFixed();\n}\n\nexport function addPercents(a: Percent, b: Percent): Percent {\n return { ratio: new Decimal(a.ratio).plus(b.ratio).toFixed() };\n}\n"],"mappings":";AAAA,OAAO,aAAa;AAGb,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEA,SAAS,eAAe,OAAyB;AAC/C,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,kBAAkB,kCAAkC;AAAA,EAChE;AACA,SAAO,EAAE,OAAO,MAAM,QAAQ,EAAE;AAClC;AAGO,SAAS,aAAa,OAAuC;AAClE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,kBAAkB,+BAA+B;AAAA,IAC7D;AACA,QAAI;AACF,UAAI,QAAQ,SAAS,GAAG,GAAG;AACzB,cAAM,IAAI,QAAQ,MAAM,GAAG,EAAE,EAAE,KAAK;AACpC,YAAI,CAAC,EAAG,OAAM,IAAI,kBAAkB,yCAAyC;AAC7E,eAAO,eAAe,IAAI,QAAQ,CAAC,EAAE,IAAI,GAAG,CAAC;AAAA,MAC/C;AACA,aAAO,eAAe,IAAI,QAAQ,OAAO,CAAC;AAAA,IAC5C,SAAS,KAAK;AACZ,UAAI,eAAe,kBAAmB,OAAM;AAC5C,YAAM,IAAI,kBAAkB,oBAAoB,KAAK,GAAG;AAAA,IAC1D;AAAA,EACF;AACA,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,eAAe,IAAI,QAAQ,MAAM,KAAK,CAAC;AAAA,IAChD,KAAK;AACH,aAAO,eAAe,IAAI,QAAQ,MAAM,KAAK,EAAE,IAAI,GAAG,CAAC;AAAA,IACzD,KAAK;AACH,aAAO,eAAe,IAAI,QAAQ,MAAM,KAAK,EAAE,IAAI,GAAM,CAAC;AAAA,IAC5D,SAAS;AACP,YAAM,cAAqB;AAC3B,YAAM,IAAI,kBAAkB,yBAAyB,OAAO,WAAW,CAAC,EAAE;AAAA,IAC5E;AAAA,EACF;AACF;AAEO,SAAS,gBAAgB,KAAsB;AACpD,SAAO,aAAa,EAAE,MAAM,eAAe,OAAO,IAAI,CAAC;AACzD;AAEO,SAAS,kBAAkB,OAAwB;AACxD,SAAO,aAAa,EAAE,MAAM,WAAW,MAAM,CAAC;AAChD;AAEO,SAAS,cAAc,GAAoB;AAChD,SAAO,IAAI,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAM,EAAE,QAAQ,CAAC;AACrD;AAEO,SAAS,gBAAgB,GAAoB;AAClD,SAAO,GAAG,IAAI,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE,QAAQ,CAAC;AACrD;AAGO,SAAS,UAAU,QAAgB,SAA0B;AAClE,SAAO,IAAI,QAAQ,MAAM,EAAE,MAAM,QAAQ,KAAK,EAAE,QAAQ;AAC1D;AAEO,SAAS,YAAY,QAAgB,SAA0B;AACpE,SAAO,IAAI,QAAQ,MAAM,EAAE,MAAM,IAAI,QAAQ,CAAC,EAAE,KAAK,QAAQ,KAAK,CAAC,EAAE,QAAQ;AAC/E;AAEO,SAAS,aAAa,QAAgB,SAA0B;AACrE,SAAO,IAAI,QAAQ,MAAM,EAAE,MAAM,IAAI,QAAQ,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,EAAE,QAAQ;AAChF;AAEO,SAAS,YAAY,GAAY,GAAqB;AAC3D,SAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE;AAC/D;","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,16 @@
1
+ import { type ChangeEvent } from "react";
2
+ export type UsePercentFieldOptions = {
3
+ value: string;
4
+ onChange: (next: string) => void;
5
+ required?: boolean;
6
+ };
7
+ export declare function usePercentField(options: UsePercentFieldOptions): {
8
+ error: string | undefined;
9
+ inputProps: {
10
+ value: string;
11
+ onChange: (e: ChangeEvent<HTMLInputElement>) => void;
12
+ onBlur: () => void;
13
+ };
14
+ clearError: () => void;
15
+ };
16
+ //# sourceMappingURL=use-percent-field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-percent-field.d.ts","sourceRoot":"","sources":["../../../src/react/fields/use-percent-field.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;AAGhE,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB;;;;sBAsB7C,WAAW,CAAC,gBAAgB,CAAC;;;;EAS9C"}
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/react/index.ts
31
+ var react_exports = {};
32
+ __export(react_exports, {
33
+ usePercentField: () => usePercentField
34
+ });
35
+ module.exports = __toCommonJS(react_exports);
36
+
37
+ // src/react/fields/use-percent-field.ts
38
+ var import_react = require("react");
39
+
40
+ // src/core/percent.ts
41
+ var import_decimal = __toESM(require("decimal.js"), 1);
42
+ var PercentParseError = class extends Error {
43
+ constructor(message) {
44
+ super(message);
45
+ this.name = "PercentParseError";
46
+ }
47
+ };
48
+ function normalizeRatio(ratio) {
49
+ if (ratio.isNegative()) {
50
+ throw new PercentParseError("Percent ratio cannot be negative");
51
+ }
52
+ return { ratio: ratio.toFixed() };
53
+ }
54
+ function parsePercent(input) {
55
+ if (typeof input === "string") {
56
+ const trimmed = input.trim();
57
+ if (!trimmed) {
58
+ throw new PercentParseError("Percent input cannot be empty");
59
+ }
60
+ try {
61
+ if (trimmed.endsWith("%")) {
62
+ const n = trimmed.slice(0, -1).trim();
63
+ if (!n) throw new PercentParseError('Percent symbol missing value before "%"');
64
+ return normalizeRatio(new import_decimal.default(n).div(100));
65
+ }
66
+ return normalizeRatio(new import_decimal.default(trimmed));
67
+ } catch (err) {
68
+ if (err instanceof PercentParseError) throw err;
69
+ throw new PercentParseError(`Invalid percent "${input}"`);
70
+ }
71
+ }
72
+ switch (input.kind) {
73
+ case "ratio":
74
+ return normalizeRatio(new import_decimal.default(input.value));
75
+ case "percent":
76
+ return normalizeRatio(new import_decimal.default(input.value).div(100));
77
+ case "basisPoints":
78
+ return normalizeRatio(new import_decimal.default(input.value).div(1e4));
79
+ default: {
80
+ const _exhaustive = input;
81
+ throw new PercentParseError(`Unknown percent input ${String(_exhaustive)}`);
82
+ }
83
+ }
84
+ }
85
+
86
+ // src/react/fields/use-percent-field.ts
87
+ function usePercentField(options) {
88
+ const { value, onChange, required } = options;
89
+ const [error, setError] = (0, import_react.useState)();
90
+ const validate = (0, import_react.useCallback)(
91
+ (raw) => {
92
+ if (!raw.trim()) {
93
+ if (required) return "Rate is required";
94
+ return void 0;
95
+ }
96
+ try {
97
+ parsePercent(raw);
98
+ return void 0;
99
+ } catch (e) {
100
+ return e instanceof PercentParseError ? e.message : "Invalid rate";
101
+ }
102
+ },
103
+ [required]
104
+ );
105
+ const inputProps = {
106
+ value,
107
+ onChange: (e) => {
108
+ const next = e.target.value;
109
+ onChange(next);
110
+ setError(validate(next));
111
+ },
112
+ onBlur: () => setError(validate(value))
113
+ };
114
+ return { error, inputProps, clearError: () => setError(void 0) };
115
+ }
116
+ // Annotate the CommonJS export names for ESM import in node:
117
+ 0 && (module.exports = {
118
+ usePercentField
119
+ });
120
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/index.ts","../../src/react/fields/use-percent-field.ts","../../src/core/percent.ts"],"sourcesContent":["export { usePercentField } from \"./fields/use-percent-field.js\";\nexport type { UsePercentFieldOptions } from \"./fields/use-percent-field.js\";\n","import { useCallback, useState, type ChangeEvent } from \"react\";\nimport { parsePercent, PercentParseError } from \"../../core/percent.js\";\n\nexport type UsePercentFieldOptions = {\n value: string;\n onChange: (next: string) => void;\n required?: boolean;\n};\n\nexport function usePercentField(options: UsePercentFieldOptions) {\n const { value, onChange, required } = options;\n const [error, setError] = useState<string | undefined>();\n\n const validate = useCallback(\n (raw: string) => {\n if (!raw.trim()) {\n if (required) return \"Rate is required\";\n return undefined;\n }\n try {\n parsePercent(raw);\n return undefined;\n } catch (e) {\n return e instanceof PercentParseError ? e.message : \"Invalid rate\";\n }\n },\n [required],\n );\n\n const inputProps = {\n value,\n onChange: (e: ChangeEvent<HTMLInputElement>) => {\n const next = e.target.value;\n onChange(next);\n setError(validate(next));\n },\n onBlur: () => setError(validate(value)),\n };\n\n return { error, inputProps, clearError: () => setError(undefined) };\n}\n","import Decimal from \"decimal.js\";\nimport type { Percent, PercentInput } from \"./types.js\";\n\nexport class PercentParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PercentParseError\";\n }\n}\n\nfunction normalizeRatio(ratio: Decimal): Percent {\n if (ratio.isNegative()) {\n throw new PercentParseError(\"Percent ratio cannot be negative\");\n }\n return { ratio: ratio.toFixed() };\n}\n\n/** Parse \"11%\", \"0.11\", or basis points via PercentInput. */\nexport function parsePercent(input: PercentInput | string): Percent {\n if (typeof input === \"string\") {\n const trimmed = input.trim();\n if (!trimmed) {\n throw new PercentParseError(\"Percent input cannot be empty\");\n }\n try {\n if (trimmed.endsWith(\"%\")) {\n const n = trimmed.slice(0, -1).trim();\n if (!n) throw new PercentParseError('Percent symbol missing value before \"%\"');\n return normalizeRatio(new Decimal(n).div(100));\n }\n return normalizeRatio(new Decimal(trimmed));\n } catch (err) {\n if (err instanceof PercentParseError) throw err;\n throw new PercentParseError(`Invalid percent \"${input}\"`);\n }\n }\n switch (input.kind) {\n case \"ratio\":\n return normalizeRatio(new Decimal(input.value));\n case \"percent\":\n return normalizeRatio(new Decimal(input.value).div(100));\n case \"basisPoints\":\n return normalizeRatio(new Decimal(input.value).div(10_000));\n default: {\n const _exhaustive: never = input;\n throw new PercentParseError(`Unknown percent input ${String(_exhaustive)}`);\n }\n }\n}\n\nexport function fromBasisPoints(bps: string): Percent {\n return parsePercent({ kind: \"basisPoints\", value: bps });\n}\n\nexport function fromPercentSymbol(value: string): Percent {\n return parsePercent({ kind: \"percent\", value });\n}\n\nexport function toBasisPoints(p: Percent): string {\n return new Decimal(p.ratio).times(10_000).toFixed(0);\n}\n\nexport function toPercentSymbol(p: Percent): string {\n return `${new Decimal(p.ratio).times(100).toFixed()}%`;\n}\n\n/** amount * ratio — both decimal strings. */\nexport function percentOf(amount: string, percent: Percent): string {\n return new Decimal(amount).times(percent.ratio).toFixed();\n}\n\nexport function plusPercent(amount: string, percent: Percent): string {\n return new Decimal(amount).times(new Decimal(1).plus(percent.ratio)).toFixed();\n}\n\nexport function minusPercent(amount: string, percent: Percent): string {\n return new Decimal(amount).times(new Decimal(1).minus(percent.ratio)).toFixed();\n}\n\nexport function addPercents(a: Percent, b: Percent): Percent {\n return { ratio: new Decimal(a.ratio).plus(b.ratio).toFixed() };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAwD;;;ACAxD,qBAAoB;AAGb,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEA,SAAS,eAAe,OAAyB;AAC/C,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,kBAAkB,kCAAkC;AAAA,EAChE;AACA,SAAO,EAAE,OAAO,MAAM,QAAQ,EAAE;AAClC;AAGO,SAAS,aAAa,OAAuC;AAClE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,kBAAkB,+BAA+B;AAAA,IAC7D;AACA,QAAI;AACF,UAAI,QAAQ,SAAS,GAAG,GAAG;AACzB,cAAM,IAAI,QAAQ,MAAM,GAAG,EAAE,EAAE,KAAK;AACpC,YAAI,CAAC,EAAG,OAAM,IAAI,kBAAkB,yCAAyC;AAC7E,eAAO,eAAe,IAAI,eAAAA,QAAQ,CAAC,EAAE,IAAI,GAAG,CAAC;AAAA,MAC/C;AACA,aAAO,eAAe,IAAI,eAAAA,QAAQ,OAAO,CAAC;AAAA,IAC5C,SAAS,KAAK;AACZ,UAAI,eAAe,kBAAmB,OAAM;AAC5C,YAAM,IAAI,kBAAkB,oBAAoB,KAAK,GAAG;AAAA,IAC1D;AAAA,EACF;AACA,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,eAAe,IAAI,eAAAA,QAAQ,MAAM,KAAK,CAAC;AAAA,IAChD,KAAK;AACH,aAAO,eAAe,IAAI,eAAAA,QAAQ,MAAM,KAAK,EAAE,IAAI,GAAG,CAAC;AAAA,IACzD,KAAK;AACH,aAAO,eAAe,IAAI,eAAAA,QAAQ,MAAM,KAAK,EAAE,IAAI,GAAM,CAAC;AAAA,IAC5D,SAAS;AACP,YAAM,cAAqB;AAC3B,YAAM,IAAI,kBAAkB,yBAAyB,OAAO,WAAW,CAAC,EAAE;AAAA,IAC5E;AAAA,EACF;AACF;;;ADvCO,SAAS,gBAAgB,SAAiC;AAC/D,QAAM,EAAE,OAAO,UAAU,SAAS,IAAI;AACtC,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAA6B;AAEvD,QAAM,eAAW;AAAA,IACf,CAAC,QAAgB;AACf,UAAI,CAAC,IAAI,KAAK,GAAG;AACf,YAAI,SAAU,QAAO;AACrB,eAAO;AAAA,MACT;AACA,UAAI;AACF,qBAAa,GAAG;AAChB,eAAO;AAAA,MACT,SAAS,GAAG;AACV,eAAO,aAAa,oBAAoB,EAAE,UAAU;AAAA,MACtD;AAAA,IACF;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,UAAU,CAAC,MAAqC;AAC9C,YAAM,OAAO,EAAE,OAAO;AACtB,eAAS,IAAI;AACb,eAAS,SAAS,IAAI,CAAC;AAAA,IACzB;AAAA,IACA,QAAQ,MAAM,SAAS,SAAS,KAAK,CAAC;AAAA,EACxC;AAEA,SAAO,EAAE,OAAO,YAAY,YAAY,MAAM,SAAS,MAAS,EAAE;AACpE;","names":["Decimal"]}
@@ -0,0 +1,3 @@
1
+ export { usePercentField } from "./fields/use-percent-field.js";
2
+ export type { UsePercentFieldOptions } from "./fields/use-percent-field.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,YAAY,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,40 @@
1
+ import {
2
+ PercentParseError,
3
+ parsePercent
4
+ } from "../chunk-MW4KRZDX.js";
5
+
6
+ // src/react/fields/use-percent-field.ts
7
+ import { useCallback, useState } from "react";
8
+ function usePercentField(options) {
9
+ const { value, onChange, required } = options;
10
+ const [error, setError] = useState();
11
+ const validate = useCallback(
12
+ (raw) => {
13
+ if (!raw.trim()) {
14
+ if (required) return "Rate is required";
15
+ return void 0;
16
+ }
17
+ try {
18
+ parsePercent(raw);
19
+ return void 0;
20
+ } catch (e) {
21
+ return e instanceof PercentParseError ? e.message : "Invalid rate";
22
+ }
23
+ },
24
+ [required]
25
+ );
26
+ const inputProps = {
27
+ value,
28
+ onChange: (e) => {
29
+ const next = e.target.value;
30
+ onChange(next);
31
+ setError(validate(next));
32
+ },
33
+ onBlur: () => setError(validate(value))
34
+ };
35
+ return { error, inputProps, clearError: () => setError(void 0) };
36
+ }
37
+ export {
38
+ usePercentField
39
+ };
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/fields/use-percent-field.ts"],"sourcesContent":["import { useCallback, useState, type ChangeEvent } from \"react\";\nimport { parsePercent, PercentParseError } from \"../../core/percent.js\";\n\nexport type UsePercentFieldOptions = {\n value: string;\n onChange: (next: string) => void;\n required?: boolean;\n};\n\nexport function usePercentField(options: UsePercentFieldOptions) {\n const { value, onChange, required } = options;\n const [error, setError] = useState<string | undefined>();\n\n const validate = useCallback(\n (raw: string) => {\n if (!raw.trim()) {\n if (required) return \"Rate is required\";\n return undefined;\n }\n try {\n parsePercent(raw);\n return undefined;\n } catch (e) {\n return e instanceof PercentParseError ? e.message : \"Invalid rate\";\n }\n },\n [required],\n );\n\n const inputProps = {\n value,\n onChange: (e: ChangeEvent<HTMLInputElement>) => {\n const next = e.target.value;\n onChange(next);\n setError(validate(next));\n },\n onBlur: () => setError(validate(value)),\n };\n\n return { error, inputProps, clearError: () => setError(undefined) };\n}\n"],"mappings":";;;;;;AAAA,SAAS,aAAa,gBAAkC;AASjD,SAAS,gBAAgB,SAAiC;AAC/D,QAAM,EAAE,OAAO,UAAU,SAAS,IAAI;AACtC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA6B;AAEvD,QAAM,WAAW;AAAA,IACf,CAAC,QAAgB;AACf,UAAI,CAAC,IAAI,KAAK,GAAG;AACf,YAAI,SAAU,QAAO;AACrB,eAAO;AAAA,MACT;AACA,UAAI;AACF,qBAAa,GAAG;AAChB,eAAO;AAAA,MACT,SAAS,GAAG;AACV,eAAO,aAAa,oBAAoB,EAAE,UAAU;AAAA,MACtD;AAAA,IACF;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,UAAU,CAAC,MAAqC;AAC9C,YAAM,OAAO,EAAE,OAAO;AACtB,eAAS,IAAI;AACb,eAAS,SAAS,IAAI,CAAC;AAAA,IACzB;AAAA,IACA,QAAQ,MAAM,SAAS,SAAS,KAAK,CAAC;AAAA,EACxC;AAEA,SAAO,EAAE,OAAO,YAAY,YAAY,MAAM,SAAS,MAAS,EAAE;AACpE;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eristack/percent",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Percent and basis-point ratios as strings — tax, discount, markup without float literals",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,6 +32,16 @@
32
32
  "types": "./dist/zod/index.d.ts",
33
33
  "default": "./dist/zod/index.cjs"
34
34
  }
35
+ },
36
+ "./react": {
37
+ "import": {
38
+ "types": "./dist/react/index.d.ts",
39
+ "default": "./dist/react/index.js"
40
+ },
41
+ "require": {
42
+ "types": "./dist/react/index.d.ts",
43
+ "default": "./dist/react/index.cjs"
44
+ }
35
45
  }
36
46
  },
37
47
  "files": [
@@ -56,16 +66,22 @@
56
66
  "decimal.js": "^10.6.0"
57
67
  },
58
68
  "peerDependencies": {
69
+ "react": "^18.0.0 || ^19.0.0",
59
70
  "zod": "^4.0.0"
60
71
  },
61
72
  "peerDependenciesMeta": {
73
+ "react": {
74
+ "optional": true
75
+ },
62
76
  "zod": {
63
77
  "optional": true
64
78
  }
65
79
  },
66
80
  "devDependencies": {
67
81
  "@tanstack/intent": "^0.3.6",
82
+ "@types/react": "^19.0.12",
68
83
  "@types/node": "^22.15.29",
84
+ "react": "^19.0.0",
69
85
  "tsup": "^8.5.0",
70
86
  "typescript": "^7.0.0",
71
87
  "vitest": "^4.1.10",