frontacles 0.2.2 → 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,7 +8,38 @@ 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.2...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.
12
43
 
13
44
  ## v0.2.2 (2025-03-01)
14
45
 
@@ -40,7 +71,7 @@ Compare with [previous version](https://github.com/frontacles/frontacles/compare
40
71
 
41
72
  ### New
42
73
 
43
- - 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.
44
75
 
45
76
  ## v0.1.1 (2021-03-13)
46
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 [221 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'
@@ -29,14 +121,14 @@ email.hostname = 'newdomain.tld' // ✅ domain migrated
29
121
  const { username, hostname } = new Email('someone@domain.tld')
30
122
  ```
31
123
 
32
- 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`.
33
125
 
34
126
  ```js
35
127
  console.log(`email: ${email}`) // 'email: someone@newdomain.tld'
36
128
  console.log(email.toString()) // 'someone@newdomain.tld'
37
129
  ```
38
130
 
39
- 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.
40
132
 
41
133
  ```js
42
134
  Email.canParse('someone@domain.tld') // true
@@ -49,7 +141,7 @@ Trying to instantiate an Email with an invalid address will throw. This behaviou
49
141
  new Email('double@at@sign.com') // ❌ throw TypeError
50
142
  ```
51
143
 
52
- 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).
53
145
 
54
146
  ```js
55
147
  const email = new Email('someone@domain.tld')
@@ -61,11 +153,11 @@ Email.canParse(email) // ✅ true
61
153
 
62
154
  ## Changelog
63
155
 
64
- 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).
65
157
 
66
158
  ## Browser and tooling support
67
159
 
68
- `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:
69
161
  - it is up to you to transpile it for legacy browsers;
70
162
  - you can’t import it using `require('frontacles')`.
71
163
 
@@ -73,12 +165,12 @@ See [CHANGELOG.md](https://github.com/frontacles/frontacles/blob/main/CHANGELOG.
73
165
 
74
166
  ## Security
75
167
 
76
- See the [security policy](https://github.com/frontacles/frontacles/blob/main/SECURITY.md).
168
+ See the [security policy](./SECURITY.md).
77
169
 
78
170
  ## Contributing
79
171
 
80
- See the [contributing guidelines](https://github.com/frontacles/frontacles/blob/main/CONTRIBUTING.md).
172
+ See the [contributing guidelines](./CONTRIBUTING.md).
81
173
 
82
174
  ## License
83
175
 
84
- 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.2",
3
+ "version": "0.3.0",
4
4
  "description": "Front-end utilities for artisans",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -21,6 +21,7 @@
21
21
  "test:types": "npm exec tsd",
22
22
  "test:ui": "vitest --ui --coverage.enabled --coverage.exclude=types",
23
23
  "coverage": "vitest run --coverage --coverage.exclude=types",
24
+ "bench": "vitest bench",
24
25
  "watch": "vitest watch",
25
26
  "build": "echo \"Nothing to build, this command is only here to please size-limit GitHub action\" && exit 0",
26
27
  "size": "size-limit",
@@ -30,6 +31,8 @@
30
31
  "files": [
31
32
  "CHANGELOG.md",
32
33
  "src/**/*.js",
34
+ "!src/**/bench",
35
+ "!src/**/test-utils",
33
36
  "!src/**/*.test.js",
34
37
  "types/index.d.ts"
35
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
@@ -31,7 +31,8 @@ export class Email extends URL {
31
31
  * @param {string} address
32
32
  */
33
33
  #validate (address) {
34
- return this.toString() == address
34
+ // `username` is tested because `@domain.tld` is valid in a `ftp` context
35
+ return this.toString() == address && this.username
35
36
  }
36
37
 
37
38
  /**
package/types/index.d.ts CHANGED
@@ -1,4 +1,6 @@
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
6
  * The domain name and extension of the email.
@@ -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
- ]