frontacles 0.2.1 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -8,12 +8,59 @@ Nothing for now.
8
8
 
9
9
  <!-- ⚠️ Before a new release, make sure the documentation doesn't contain any **unreleased** mention. -->
10
10
 
11
- Compare with [last published version](https://github.com/frontacles/frontacles/compare/0.2.0...main).
11
+ Compare with [last published version](https://github.com/frontacles/frontacles/compare/0.2.3...main).
12
+
13
+ ## v0.3.0 (2025-03-06)
14
+
15
+ Compare with [last published version](https://github.com/frontacles/frontacles/compare/0.2.2...0.2.3).
16
+
17
+ ### New
18
+
19
+ #### Math
20
+
21
+ - Add [`clamp`](./README.md#clamp), a function to clamp a number in a given range.
22
+ - Add support for `Infinity` precision to the [`round` function](./README.md#round).
23
+
24
+ #### String
25
+
26
+ - Add [`capitalize`](./README.md#capitalize), a function to put the first letter of a word in uppercase.
27
+
28
+ ### Fixed
29
+
30
+ - `Email` was considering as valid an email without username (e.g. `@domain.tld`). This is now fixed.
31
+
32
+ ### Under the hood
33
+
34
+ - Shorten `round` by a couple of Bytes.
35
+ - Benchmark [`round` implementations](./src/math/bench)
36
+ - Add [Valibot test suite](./src/url/test-utils/valibot-suite.js) to `Email` (all tests are passing!).
37
+
38
+ ### Documentation
39
+
40
+ - Gather sizes in the documentation introduction.
41
+ - Group utils by categories (_Math_, _String_, _URL_) to the documentation.
42
+ - Add pull request template.
43
+
44
+ ## v0.2.2 (2025-03-01)
45
+
46
+ Compare with [last published version](https://github.com/frontacles/frontacles/compare/0.2.1...0.2.2).
47
+
48
+ ### Breaking
49
+
50
+ - Make `Email.username` and `Email.hostname` mutable to follow `URL.username` and `URL.hostname` behavior.
51
+
52
+ ### Improved
53
+
54
+ - Override documentation inherited from `URL` for `Email.username` and `Email.hostname`.
12
55
 
13
56
  ## v0.2.1 (2025-02-28)
14
57
 
15
58
  Compare with [previous version](https://github.com/frontacles/frontacles/compare/0.2.0...0.2.1).
16
59
 
60
+ ### Breaking
61
+
62
+ - Rename `Email.host` into `Email.hostname` to better match `URL`.
63
+
17
64
  ### Improved
18
65
 
19
66
  - `Email` is now a couple of bytes lighter.
@@ -24,7 +71,7 @@ Compare with [previous version](https://github.com/frontacles/frontacles/compare
24
71
 
25
72
  ### New
26
73
 
27
- - Add [`Email`](https://github.com/frontacles/frontacles#email), a class to validate emails the same way browsers do, which is rock solid.
74
+ - Add [`Email`](./README.md#email), a class to validate emails the same way browsers do, which is rock solid.
28
75
 
29
76
  ## v0.1.1 (2021-03-13)
30
77
 
package/README.md CHANGED
@@ -5,11 +5,103 @@ Cool utilities for front-end development (and potentially for Node).
5
5
  > [!WARNING]
6
6
  > Under heavy development. We are only starting!
7
7
 
8
- ## Email
8
+ We love tiny bits (using brotli compression):
9
9
 
10
- A class to instantiate an `Email` object or validate emails. It’s only [222 B compressed](https://bundlejs.com/?q=frontacles&treeshake=[{Email}]&config={%22compression%22%3A%22brotli%22}&bundle).
10
+ | categories | util | size |
11
+ | --- | --- | --- |
12
+ | math | [`clamp`](#clamp) | 35 B |
13
+ | math | [`round`](#round) | 38 B |
14
+ | string | [`capitalize`](#capitalize) | 40 B |
15
+ | url | [`Email`](#email) | 173 B |
16
+ | | **everything** | 252 B |
11
17
 
12
- Unlike most libraries using [RegEx for emails](https://github.com/colinhacks/zod/blob/e2b9a5f9ac67d13ada61cd8e4b1385eb850c7592/src/types.ts#L648-L663) (and prone to [bugs](https://github.com/colinhacks/zod/issues/3913)), Frontacles `Email` class relies on the same mechanism as your browser to validate email addresses, making it robust, and very likely RFC compliant.
18
+ ## Math utils
19
+
20
+ ### `clamp`
21
+
22
+ Clamp a number between two values. A clamped number stays within a specified range. If the number is lower than the minimum, the minimum value is returned. If the number is higher than the maximum, the maximum value is returned.
23
+
24
+ `clamp` needs 3 parameters:
25
+
26
+ 1. `number`: the number to clamp
27
+ 2. `min`: the smallest value your number can have
28
+ 3. `max`: the highest value your number can have
29
+
30
+ ```js
31
+ import { clamp } from 'frontacles/math'
32
+
33
+ clamp(17, 3, 8) // 8
34
+ clamp(-3, 3, 8) // 3
35
+ clamp(5, 3, 8) // 5
36
+ ```
37
+
38
+ `-0` and `Infinity` are accepted:
39
+
40
+ ```js
41
+ clamp(-2, -0, 10) // -0
42
+ clamp(5, 0, Infinity) // 5
43
+ clamp(Infinity, 0, 10) // 10
44
+ ```
45
+
46
+ > [!NOTE]
47
+ > `clamp` mostly follows [`Math.clamp` TC39 proposal](https://github.com/tc39/proposal-math-clamp), except it doesn’t throw if you flip the order of the _min_ (2nd parameter) and _max_ (3rd parameter) numbers.
48
+
49
+
50
+ ### `round`
51
+
52
+ Round a number to the (optionally) provided precision.
53
+
54
+ ```js
55
+ import { round } from 'frontacles/math'
56
+
57
+ round(687.3456) // 687
58
+ round(687.3456, 0) // 687
59
+ round(687.3456, 2) // 687.35
60
+ ```
61
+
62
+ A negative precision will round up to multiple of tens:
63
+
64
+ ```js
65
+ round(687.3456, -1) // 690
66
+ round(687.3456, -2) // 700
67
+ ```
68
+
69
+ Trying to round `Infinity` or to round a number to an _infinite_ precision is also possible:
70
+
71
+ ```js
72
+ round(Infinity, -2) // Infinity
73
+ round(17.853, Infinity // 17.853
74
+ ```
75
+
76
+ ## String utils
77
+
78
+ ### `capitalize`
79
+
80
+ Put the first letter of a word in uppercase. It works for Latin, Greek and Cyrillic alphabets.
81
+
82
+ ```js
83
+ capitalize('jean-roger')) // 'Jean-roger' (Latin)
84
+ capitalize('έρημος')) // 'Έρημος' (Greek)
85
+ capitalize('0 books') // 0 books
86
+ capitalize('صحراء') // 'صحراء' (Arabic)
87
+ ```
88
+
89
+ > [!TIP]
90
+ > Before using `capitalize`, evaluate if [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/::first-letter) could be used instead.
91
+ >
92
+ > ```css
93
+ > .my-class::first-letter {
94
+ > text-transform: uppercase;
95
+ > }
96
+ > ```
97
+
98
+ ## URL utils
99
+
100
+ ### `Email`
101
+
102
+ A class to instantiate an `Email` object or validate email addresses.
103
+
104
+ Unlike most libraries using [RegEx to validate a string is an email](https://github.com/colinhacks/zod/blob/e2b9a5f9ac67d13ada61cd8e4b1385eb850c7592/src/types.ts#L648-L663) (which is prone to [bugs](https://github.com/colinhacks/zod/issues/3913)), Frontacles `Email` relies on the same mechanism as your browser, making it robust, and very likely RFC compliant.
13
105
 
14
106
  ```js
15
107
  import { Email } from 'frontacles/url/email'
@@ -17,21 +109,26 @@ import { Email } from 'frontacles/url/email'
17
109
  const email = new Email('someone@domain.tld')
18
110
  ```
19
111
 
20
- Get the username and the hostname separately.
112
+ Get or set the username and the hostname separately.
21
113
 
22
114
  ```js
23
115
  email.username // 'someone'
24
116
  email.hostname // 'domain.tld'
117
+
118
+ email.hostname = 'newdomain.tld' // ✅ domain migrated
119
+
120
+ // destructuring also works
121
+ const { username, hostname } = new Email('someone@domain.tld')
25
122
  ```
26
123
 
27
- Turn the email object into a string by using it along another string, or use `toString`.
124
+ An `Email` object is converted to a string when used along another string, or by directly calling `toString`.
28
125
 
29
126
  ```js
30
- console.log(`email: ${email}`) // 'email: someone@domain.tld'
31
- console.log(email.toString()) // 'someone@domain.tld'
127
+ console.log(`email: ${email}`) // 'email: someone@newdomain.tld'
128
+ console.log(email.toString()) // 'someone@newdomain.tld'
32
129
  ```
33
130
 
34
- Validate that a string is a valid email address. It passes the complete Zod test suites, and beyond.
131
+ Validate an email address with `Email.canParse`. It passes the complete Zod test suites, and beyond.
35
132
 
36
133
  ```js
37
134
  Email.canParse('someone@domain.tld') // true
@@ -44,7 +141,7 @@ Trying to instantiate an Email with an invalid address will throw. This behaviou
44
141
  new Email('double@at@sign.com') // ❌ throw TypeError
45
142
  ```
46
143
 
47
- Another behaviour from the `URL` class: you can pass an `Email` object to the `Email` constructor or to `Email.canParse`.
144
+ Another behaviour from the `URL` class: you can pass an `Email` object to the `Email` constructor (or to `Email.canParse`, but it doesn’t really make sense).
48
145
 
49
146
  ```js
50
147
  const email = new Email('someone@domain.tld')
@@ -56,11 +153,11 @@ Email.canParse(email) // ✅ true
56
153
 
57
154
  ## Changelog
58
155
 
59
- See [CHANGELOG.md](https://github.com/frontacles/frontacles/blob/main/CHANGELOG.md) or the [releases](https://github.com/frontacles/frontacles/releases).
156
+ See [CHANGELOG.md](./CHANGELOG.md) or the [releases](https://github.com/frontacles/frontacles/releases).
60
157
 
61
158
  ## Browser and tooling support
62
159
 
63
- `frontacles` is provided [as module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#browser_compatibility) for [modern browsers usage](https://github.com/frontacles/frontacles/blob/main/browserslist) with standard JavaScript syntax:
160
+ `frontacles` is provided [as module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#browser_compatibility) for [modern browsers usage](./browserslist) with standard JavaScript syntax:
64
161
  - it is up to you to transpile it for legacy browsers;
65
162
  - you can’t import it using `require('frontacles')`.
66
163
 
@@ -68,12 +165,12 @@ See [CHANGELOG.md](https://github.com/frontacles/frontacles/blob/main/CHANGELOG.
68
165
 
69
166
  ## Security
70
167
 
71
- See the [security policy](https://github.com/frontacles/frontacles/blob/main/SECURITY.md).
168
+ See the [security policy](./SECURITY.md).
72
169
 
73
170
  ## Contributing
74
171
 
75
- See the [contributing guidelines](https://github.com/frontacles/frontacles/blob/main/CONTRIBUTING.md).
172
+ See the [contributing guidelines](./CONTRIBUTING.md).
76
173
 
77
174
  ## License
78
175
 
79
- The _datetime-attribute_ package is open-sourced software licensed under the [DWTFYWTPL](https://github.com/frontacles/frontacles/blob/main/LICENSE).
176
+ The _datetime-attribute_ package is open-sourced software licensed under the [DWTFYWTPL](./LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontacles",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Front-end utilities for artisans",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -16,10 +16,12 @@
16
16
  },
17
17
  "scripts": {
18
18
  "types": "tsc && dts-bundle-generator --silent --no-banner=true -o types/index.d.ts types-transitive/index.d.ts",
19
+ "posttypes": "node scripts/inject-jsdoc.mjs",
19
20
  "test": "vitest run",
20
21
  "test:types": "npm exec tsd",
21
22
  "test:ui": "vitest --ui --coverage.enabled --coverage.exclude=types",
22
23
  "coverage": "vitest run --coverage --coverage.exclude=types",
24
+ "bench": "vitest bench",
23
25
  "watch": "vitest watch",
24
26
  "build": "echo \"Nothing to build, this command is only here to please size-limit GitHub action\" && exit 0",
25
27
  "size": "size-limit",
@@ -29,6 +31,8 @@
29
31
  "files": [
30
32
  "CHANGELOG.md",
31
33
  "src/**/*.js",
34
+ "!src/**/bench",
35
+ "!src/**/test-utils",
32
36
  "!src/**/*.test.js",
33
37
  "types/index.d.ts"
34
38
  ],
package/src/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './math/index.js'
2
+ export * from './string/index.js'
2
3
  export * from './url/email.js'
package/src/math/index.js CHANGED
@@ -1,5 +1,23 @@
1
1
  /**
2
- * Round a number to the (optionnally) provided precision.
2
+ * Make sure a number stays between boundaries.
3
+ * Follows {@link https://github.com/tc39/proposal-math-clamp TC39 proposal}.
4
+ *
5
+ * Examples:
6
+ *
7
+ * ```
8
+ * clamp(17, 3, 8) // 8
9
+ * clamp(-3, 3, 8) // 3
10
+ * clamp(5, 3, 8) // 5
11
+ * ```
12
+ *
13
+ * @param {number} val The number you want to maintain inside boundaries.
14
+ * @param {number} min The lowest permitted value.
15
+ * @param {number} max The highest permitted value.
16
+ */
17
+ export const clamp = (val, min, max) => Math.max(min, Math.min(max, val))
18
+
19
+ /**
20
+ * Round a number to the (optionally) provided precision.
3
21
  *
4
22
  * Examples:
5
23
  * - round(687.3456, 2) // 687.35
@@ -11,6 +29,9 @@
11
29
  * @param {number} [precision=0] Rounding precision
12
30
  */
13
31
  export const round = (number, precision = 0) => {
14
- precision = 10 ** precision
15
- return Math.round(number * precision) / precision
32
+
33
+ // Testing precision avoids a crash because `10 ** Infinity` gives `NaN`.
34
+ return precision == Infinity
35
+ ? number
36
+ : Math.round(number * 10 ** precision) / 10 ** precision
16
37
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Capitalize the first letter of a string.
3
+ *
4
+ * Before using it, evaluate if CSS could be use instead
5
+ * (`::first-letter { text-transform: uppercase; }`).
6
+ *
7
+ * Examples:
8
+ * - capitalize('jean-roger') // 'Jean-roger'
9
+ * - capitalize('0 books') // '0 books'
10
+ *
11
+ * @param {string} str
12
+ */
13
+ export const capitalize = str => str[0].toUpperCase() + str.slice(1)
package/src/url/email.js CHANGED
@@ -2,7 +2,6 @@
2
2
  export class Email extends URL {
3
3
 
4
4
  /**
5
- *
6
5
  * @param {string|Email} address An email address like `someone@domain.tld`.
7
6
  */
8
7
  constructor(address) {
@@ -13,24 +12,6 @@ export class Email extends URL {
13
12
  }
14
13
  }
15
14
 
16
- /**
17
- * The domain name and extension of the email.
18
- *
19
- * In `username@domain.tld`, it is `domain.tld`.
20
- */
21
- get hostname() {
22
- return super.hostname
23
- }
24
-
25
- /**
26
- * The username of the email.
27
- *
28
- * In `username@domain.tld`, it is `username`.
29
- */
30
- get username() {
31
- return super.username
32
- }
33
-
34
15
  toJSON () {
35
16
  return this.toString()
36
17
  }
@@ -50,7 +31,8 @@ export class Email extends URL {
50
31
  * @param {string} address
51
32
  */
52
33
  #validate (address) {
53
- return this.toString() == address
34
+ // `username` is tested because `@domain.tld` is valid in a `ftp` context
35
+ return this.toString() == address && this.username
54
36
  }
55
37
 
56
38
  /**
package/types/index.d.ts CHANGED
@@ -1,28 +1,29 @@
1
+ export function clamp(val: number, min: number, max: number): number;
1
2
  export function round(number: number, precision?: number): number;
3
+ export function capitalize(str: string): string;
2
4
  export class Email extends URL {
3
5
  /**
4
- * Whether or not an email address is parsable and valid.
6
+ * The domain name and extension of the email.
5
7
  *
6
- * @param {any|Email} address
8
+ * In `username@domain.tld`, it is `domain.tld`.
7
9
  */
8
- static canParse(address: any | Email): boolean;
10
+ declare hostname: string;
9
11
  /**
12
+ * The username of the email.
10
13
  *
11
- * @param {string|Email} address An email address like `someone@domain.tld`.
14
+ * In `username@domain.tld`, it is `username`.
12
15
  */
13
- constructor(address: string | Email);
16
+ declare username: string;
14
17
  /**
15
- * The domain name and extension of the email.
18
+ * Whether or not an email address is parsable and valid.
16
19
  *
17
- * In `username@domain.tld`, it is `domain.tld`.
20
+ * @param {any|Email} address
18
21
  */
19
- get hostname(): string;
22
+ static canParse(address: any | Email): boolean;
20
23
  /**
21
- * The username of the email.
22
- *
23
- * In `username@domain.tld`, it is `username`.
24
+ * @param {string|Email} address An email address like `someone@domain.tld`.
24
25
  */
25
- get username(): string;
26
+ constructor(address: string | Email);
26
27
  #private;
27
28
  }
28
29
 
@@ -1,117 +0,0 @@
1
- /* eslint-disable no-useless-escape */
2
- // https://github.com/colinhacks/zod/blob/e2b9a5f9ac67d13ada61cd8e4b1385eb850c7592/src/types.ts#L648-L663
3
- // https://github.com/colinhacks/zod/blob/e2b9a5f9ac67d13ada61cd8e4b1385eb850c7592/deno/lib/__tests__/string.test.ts#L42-L154
4
-
5
- export const validEmailsFromZod = [
6
- `email@domain.com`,
7
- `firstname.lastname@domain.com`,
8
- `email@subdomain.domain.com`,
9
- `firstname+lastname@domain.com`,
10
- `1234567890@domain.com`,
11
- `email@domain-one.com`,
12
- `_______@domain.com`,
13
- `email@domain.name`,
14
- `email@domain.co.jp`,
15
- `firstname-lastname@domain.com`,
16
- `very.common@example.com`,
17
- `disposable.style.email.with+symbol@example.com`,
18
- `other.email-with-hyphen@example.com`,
19
- `fully-qualified-domain@example.com`,
20
- `user.name+tag+sorting@example.com`,
21
- `x@example.com`,
22
- `mojojojo@asdf.example.com`,
23
- `example-indeed@strange-example.com`,
24
- `example@s.example`,
25
- `user-@example.org`,
26
- `user@my-example.com`,
27
- `a@b.cd`,
28
- `work+user@mail.com`,
29
- `tom@test.te-st.com`,
30
- `something@subdomain.domain-with-hyphens.tld`,
31
- `common'name@domain.com`,
32
- `francois@etu.inp-n7.fr`,
33
- ]
34
-
35
- export const invalidEmailsFromZod = [
36
- // no "printable characters"
37
- // `user%example.com@example.org`,
38
- // `mailhost!username@example.org`,
39
- // `test/test@test.com`,
40
-
41
- // double @
42
- `francois@@etu.inp-n7.fr`,
43
- // do not support quotes
44
- `"email"@domain.com`,
45
- `"e asdf sadf ?<>ail"@domain.com`,
46
- `" "@example.org`,
47
- `"john..doe"@example.org`,
48
- `"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com`,
49
- // do not support comma
50
- `a,b@domain.com`,
51
-
52
- // do not support IPv4
53
- `email@123.123.123.123`,
54
- `email@[123.123.123.123]`,
55
- `postmaster@123.123.123.123`,
56
- `user@[68.185.127.196]`,
57
- `ipv4@[85.129.96.247]`,
58
- `valid@[79.208.229.53]`,
59
- `valid@[255.255.255.255]`,
60
- `valid@[255.0.55.2]`,
61
- `valid@[255.0.55.2]`,
62
-
63
- // do not support ipv6
64
- `hgrebert0@[IPv6:4dc8:ac7:ce79:8878:1290:6098:5c50:1f25]`,
65
- `bshapiro4@[IPv6:3669:c709:e981:4884:59a3:75d1:166b:9ae]`,
66
- `jsmith@[IPv6:2001:db8::1]`,
67
- `postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]`,
68
- `postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:192.168.1.1]`,
69
-
70
- // microsoft test cases
71
- `plainaddress`,
72
- `#@%^%#$@#$@#.com`,
73
- `@domain.com`,
74
- `Joe Smith &lt;email@domain.com&gt;`,
75
- `email.domain.com`,
76
- `email@domain@domain.com`,
77
- `.email@domain.com`,
78
- `email.@domain.com`,
79
- `email..email@domain.com`,
80
- `あいうえお@domain.com`,
81
- `email@domain.com (Joe Smith)`,
82
- `email@domain`,
83
- `email@-domain.com`,
84
- `email@111.222.333.44444`,
85
- `email@domain..com`,
86
- `Abc.example.com`,
87
- `A@b@c@example.com`,
88
- `colin..hacks@domain.com`,
89
- `a"b(c)d,e:f;g<h>i[j\k]l@example.com`,
90
- `just"not"right@example.com`,
91
- `this is"not\allowed@example.com`,
92
- `this\ still\"not\\allowed@example.com`,
93
-
94
- // random
95
- `i_like_underscore@but_its_not_allowed_in_this_part.example.com`,
96
- `QA[icon]CHOCOLATE[icon]@test.com`,
97
- `invalid@-start.com`,
98
- `invalid@end.com-`,
99
- `a.b@c.d`,
100
- `invalid@[1.1.1.-1]`,
101
- `invalid@[68.185.127.196.55]`,
102
- `temp@[192.168.1]`,
103
- `temp@[9.18.122.]`,
104
- `double..point@test.com`,
105
- `asdad@test..com`,
106
- `asdad@hghg...sd...au`,
107
- `asdad@hghg........au`,
108
- `invalid@[256.2.2.48]`,
109
- `invalid@[256.2.2.48]`,
110
- `invalid@[999.465.265.1]`,
111
- `jkibbey4@[IPv6:82c4:19a8::70a9:2aac:557::ea69:d985:28d]`,
112
- `mlivesay3@[9952:143f:b4df:2179:49a1:5e82:b92e:6b6]`,
113
- `gbacher0@[IPv6:bc37:4d3f:5048:2e26:37cc:248e:df8e:2f7f:af]`,
114
- `invalid@[IPv6:5348:4ed3:5d38:67fb:e9b:acd2:c13:192.168.256.1]`,
115
- `test@.com`,
116
- `aaaaaaaaaaaaaaalongemailthatcausesregexDoSvulnerability@test.c`,
117
- ]